bitsnap-react 1.20.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BitsnapCart-73ESTTTH.mjs +2 -0
- package/dist/BitsnapCart-TWT7LVGJ.js +2 -0
- package/dist/BitsnapCart-TWT7LVGJ.js.map +1 -0
- package/dist/{CartProvider-G7FQUPXL.mjs → CartProvider-4WXJPQJB.mjs} +2 -2
- package/dist/CartProvider-CPF2E7JA.js +2 -0
- package/dist/CartProvider-CPF2E7JA.js.map +1 -0
- package/dist/PanelModelFiles-EKQRLF46.js.map +1 -1
- package/dist/analytics.js +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/analytics.mjs +1 -1
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/catalog.mjs +1 -1
- package/dist/chat.d.mts +2 -2
- package/dist/chat.d.ts +2 -2
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/chat.mjs +1 -1
- package/dist/chat.mjs.map +1 -1
- package/dist/{chunk-IXQCN7MR.mjs → chunk-27JN2GIQ.mjs} +2 -2
- package/dist/{chunk-URQBHNJK.mjs → chunk-6ALA5M44.mjs} +2 -2
- package/dist/{chunk-4Y233NII.js → chunk-6DEKVABT.js} +2 -2
- package/dist/chunk-6DEKVABT.js.map +1 -0
- package/dist/chunk-6Q3VGPSF.js.map +1 -1
- package/dist/{chunk-AQYHQY2Y.js → chunk-E5MMKBC4.js} +2 -2
- package/dist/chunk-E5MMKBC4.js.map +1 -0
- package/dist/chunk-EFN4OW2C.js.map +1 -1
- package/dist/{chunk-IRZJIPUF.js → chunk-EK3D44XW.js} +2 -2
- package/dist/chunk-EK3D44XW.js.map +1 -0
- package/dist/{chunk-BTERIY6N.mjs → chunk-EXC5Z5VM.mjs} +2 -2
- package/dist/{chunk-RJPQMDIQ.js → chunk-EZGL6DPG.js} +2 -2
- package/dist/chunk-EZGL6DPG.js.map +1 -0
- package/dist/{chunk-HP3I6D2R.js → chunk-FORZ5AH4.js} +2 -2
- package/dist/chunk-FORZ5AH4.js.map +1 -0
- package/dist/{chunk-KO7ZERN5.mjs → chunk-HLULQA3B.mjs} +2 -2
- package/dist/{chunk-YPA6MMGJ.js → chunk-I2RHVELR.js} +2 -2
- package/dist/chunk-I2RHVELR.js.map +1 -0
- package/dist/{chunk-GHBRR665.js → chunk-IKKMSLKC.js} +2 -2
- package/dist/chunk-IKKMSLKC.js.map +1 -0
- package/dist/{chunk-TW25G7XX.mjs → chunk-J6WKSO33.mjs} +2 -2
- package/dist/{chunk-TW25G7XX.mjs.map → chunk-J6WKSO33.mjs.map} +1 -1
- package/dist/chunk-JSDGZ4NY.js.map +1 -1
- package/dist/{chunk-IXX6SUMB.mjs → chunk-KO7Z3E23.mjs} +2 -2
- package/dist/chunk-KOYBZORL.js.map +1 -1
- package/dist/chunk-KYSMT6ZK.js +2 -0
- package/dist/chunk-KYSMT6ZK.js.map +1 -0
- package/dist/{chunk-LHQEM3CU.mjs → chunk-LRUBBPTV.mjs} +2 -2
- package/dist/chunk-MDAFPGPA.js.map +1 -1
- package/dist/chunk-Q337IQNA.js.map +1 -1
- package/dist/{chunk-5M3OUOMU.mjs → chunk-T5JO4SP7.mjs} +2 -2
- package/dist/{chunk-C736F727.js → chunk-TM3E3L57.js} +2 -2
- package/dist/chunk-TM3E3L57.js.map +1 -0
- package/dist/{chunk-LHNTSQYO.mjs → chunk-TMEII7BM.mjs} +2 -2
- package/dist/chunk-W3RGQYPD.js.map +1 -1
- package/dist/{chunk-A2ZDFN3J.mjs → chunk-WOA6W5RM.mjs} +2 -2
- package/dist/{chunk-KZWCVKLU.js → chunk-XMRGA5AN.js} +3 -3
- package/dist/chunk-XMRGA5AN.js.map +1 -0
- package/dist/chunk-Z3YOJ6NZ.js.map +1 -1
- package/dist/consent.js.map +1 -1
- package/dist/course.js +1 -1
- package/dist/course.js.map +1 -1
- package/dist/course.mjs +1 -1
- package/dist/embed/analytics.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/methods-43RQLLWD.mjs +2 -0
- package/dist/methods-SZVF3HMF.js +2 -0
- package/dist/methods-SZVF3HMF.js.map +1 -0
- package/dist/model-files-client-P4PORZJ6.js.map +1 -1
- package/dist/models.js.map +1 -1
- package/dist/returns.js +1 -1
- package/dist/returns.js.map +1 -1
- package/dist/returns.mjs +1 -1
- package/dist/viewer3d.js.map +1 -1
- package/dist/viewer3d.mjs +1 -1
- package/dist/{widgets-Bsojw94g.d.ts → widgets-BL6pCMdc.d.ts} +34 -2
- package/dist/{widgets-u7xMJbsY.d.mts → widgets-DmHlIbp8.d.mts} +34 -2
- package/package.json +1 -1
- package/dist/BitsnapCart-V4S4EXYV.js +0 -2
- package/dist/BitsnapCart-V4S4EXYV.js.map +0 -1
- package/dist/BitsnapCart-Z6JFYNH2.mjs +0 -2
- package/dist/CartProvider-5NP4WACK.js +0 -2
- package/dist/CartProvider-5NP4WACK.js.map +0 -1
- package/dist/chunk-4Y233NII.js.map +0 -1
- package/dist/chunk-AQYHQY2Y.js.map +0 -1
- package/dist/chunk-C736F727.js.map +0 -1
- package/dist/chunk-GHBRR665.js.map +0 -1
- package/dist/chunk-HP3I6D2R.js.map +0 -1
- package/dist/chunk-IRZJIPUF.js.map +0 -1
- package/dist/chunk-KDL35KEI.js +0 -2
- package/dist/chunk-KDL35KEI.js.map +0 -1
- package/dist/chunk-KZWCVKLU.js.map +0 -1
- package/dist/chunk-RJPQMDIQ.js.map +0 -1
- package/dist/chunk-YPA6MMGJ.js.map +0 -1
- package/dist/methods-33CWGKNA.mjs +0 -2
- package/dist/methods-4GB652MQ.js +0 -2
- package/dist/methods-4GB652MQ.js.map +0 -1
- /package/dist/{BitsnapCart-Z6JFYNH2.mjs.map → BitsnapCart-73ESTTTH.mjs.map} +0 -0
- /package/dist/{CartProvider-G7FQUPXL.mjs.map → CartProvider-4WXJPQJB.mjs.map} +0 -0
- /package/dist/{chunk-IXQCN7MR.mjs.map → chunk-27JN2GIQ.mjs.map} +0 -0
- /package/dist/{chunk-URQBHNJK.mjs.map → chunk-6ALA5M44.mjs.map} +0 -0
- /package/dist/{chunk-BTERIY6N.mjs.map → chunk-EXC5Z5VM.mjs.map} +0 -0
- /package/dist/{chunk-KO7ZERN5.mjs.map → chunk-HLULQA3B.mjs.map} +0 -0
- /package/dist/{chunk-IXX6SUMB.mjs.map → chunk-KO7Z3E23.mjs.map} +0 -0
- /package/dist/{chunk-LHQEM3CU.mjs.map → chunk-LRUBBPTV.mjs.map} +0 -0
- /package/dist/{chunk-5M3OUOMU.mjs.map → chunk-T5JO4SP7.mjs.map} +0 -0
- /package/dist/{chunk-LHNTSQYO.mjs.map → chunk-TMEII7BM.mjs.map} +0 -0
- /package/dist/{chunk-A2ZDFN3J.mjs.map → chunk-WOA6W5RM.mjs.map} +0 -0
- /package/dist/{methods-33CWGKNA.mjs.map → methods-43RQLLWD.mjs.map} +0 -0
package/dist/chat.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/chat/widgets.tsx","../src/chat/buy.ts","../src/chat/types.ts","../src/chat/client.ts","../src/chat/Conversation.tsx","../src/chat/texts.ts","../src/chat/useConversation.ts","../src/chat/realtime.ts","../src/chat/PanelMessages.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { getLocale } from \"../components/checkout/locale\";\nimport { getPanelConfig } from \"../panel/config\";\nimport { getPanelSession } from \"../panel/session-storage\";\nimport { addPackToCart } from \"./buy\";\nimport { createChatClient, type ChatClient } from \"./client\";\nimport { ConversationView } from \"./Conversation\";\nimport type { NatsModule } from \"./realtime\";\nimport { getChatTexts, type ChatTexts } from \"./texts\";\nimport { useConversation, type ConversationLock } from \"./useConversation\";\n\n/** What the lock's buy action gets. */\nexport type ChatBuyRequest = {\n /** The product (or variant) to sell: `buyProductId`, else the locked product. */\n productId: string;\n /** `replies`, `expired`, `no-pack` or `no-access`. */\n reason: ConversationLock[\"reason\"];\n /** Known for a locked channel (from the chat service). */\n priceCents?: number;\n amount?: number;\n email?: string;\n code?: string;\n turnstileToken?: string;\n};\n\n/** Props shared by `ChatThread` and `Channel`. */\nexport type ChatWidgetBaseProps = {\n /**\n * Public Bitsnap project id of the shop. The cart uses it, the panel\n * session is looked up with it, and it is the `X-Bitsnap-Project` of the\n * chat service unless `chatProjectID` is given.\n */\n projectID: string;\n /** Project id for the chat service, when it differs from `projectID`. */\n chatProjectID?: string;\n /**\n * Bearer for the chat service: a `bcht_` buyer token from your server\n * (`POST /api/v1/chat/buyer-token`). Defaults to the customer panel session\n * of this browser (after `PanelLogin`).\n */\n token?: string;\n /** Chat service base URL. Defaults to `https://chat.bitsnap.pl`. */\n apiUrl?: string;\n /** NATS WebSocket URL overriding the one of the realtime token. */\n realtimeUrl?: string;\n /** Bitsnap host of the panel session (`setHost`), when not the default. */\n host?: string;\n /** Polling interval in ms while the live connection is down. Default 8000. */\n pollIntervalMs?: number;\n /** Language of the default texts; defaults to the library locale. */\n locale?: string;\n /** Text overrides, whatever the locale. */\n texts?: Partial<ChatTexts>;\n /** Heading of the conversation. */\n title?: string;\n /** Shows a back button in the header. */\n onBack?: () => void;\n /**\n * The lock's \"Kup pakiet\" / \"Kup dostęp\". Default: add the product to the\n * page cart and open it (mount `BitsnapCheckout` on the page). Throw to\n * show the error line.\n */\n onBuy?: (request: ChatBuyRequest) => Promise<void> | void;\n /** The product or variant the buy action sells (e.g. the pack variant). */\n buyProductId?: string;\n className?: string;\n /** A ready client (tests, custom transport). Replaces token and URLs. */\n client?: ChatClient;\n /** @internal Replaces the NATS loader (tests). */\n loadNats?: () => Promise<NatsModule>;\n};\n\nexport type ChatThreadProps = ChatWidgetBaseProps & {\n /** The service product of the pack: opens its thread, creating it if needed. */\n productId?: string;\n /** An existing thread. Give this or `productId`. */\n threadId?: string;\n};\n\nexport type ChannelProps = ChatWidgetBaseProps & {\n /** The channel (its id is also the id of its conversation). */\n channelId: string;\n /** The community product sold by the lock; defaults to the channel's own. */\n productId?: string;\n};\n\n/**\n * The chat client of a widget. The panel session lives in localStorage, so\n * it is read after mounting (`null` until then: the widget stays loading and\n * the server HTML has no session-dependent content).\n */\nfunction useWidgetClient(props: ChatWidgetBaseProps): ChatClient | null | undefined {\n const [stored, setStored] = useState<string | undefined | null>(null);\n const host = props.host ?? getPanelConfig().host;\n useEffect(() => {\n if (props.client || props.token) return;\n setStored(getPanelSession(props.projectID, host));\n }, [props.client, props.token, props.projectID, host]);\n const token = props.token ?? stored;\n const projectId = props.chatProjectID ?? props.projectID;\n return useMemo(() => {\n if (props.client) return props.client;\n if (token === null) return null;\n if (!token) return undefined;\n return createChatClient({ projectId, token, baseUrl: props.apiUrl });\n }, [props.client, token, projectId, props.apiUrl]);\n}\n\nfunction useBuy(props: ChatWidgetBaseProps) {\n const claim = useRef<{ key: string; token: string; expires: number } | null>(null);\n return async (lock: ConversationLock & { email?: string; code?: string; turnstileToken?: string }) => {\n const productId = props.buyProductId ?? lock.productId;\n if (!productId) return;\n const request: ChatBuyRequest = { productId, reason: lock.reason, priceCents: lock.priceCents, ...(lock.amount != null ? { amount: lock.amount } : {}), ...(lock.email ? { email: lock.email } : {}), ...(lock.code ? { code: lock.code } : {}), ...(lock.turnstileToken != null ? { turnstileToken: lock.turnstileToken } : {}) };\n if (props.onBuy) {\n await props.onBuy(request);\n return;\n }\n const commerceBase = (props.host ?? getPanelConfig().host ?? \"https://bitsnap.pl\").replace(/\\/+$/, \"\");\n const isFreeClaim = lock.freeClaimLimit != null && (lock.priceType === \"free\" || (lock.priceType === \"pay_what_you_want\" && lock.minimumCents === 0 && request.amount === 0));\n if (isFreeClaim && request.email && request.turnstileToken != null) {\n claim.current = null;\n const response = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/free-claim/request-code`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email: request.email, turnstileToken: request.turnstileToken }),\n });\n const result = await response.json() as { code?: string };\n if (!response.ok) throw new Error(result.code ?? \"internal\");\n return;\n }\n if (isFreeClaim && request.email && request.code) {\n const key = JSON.stringify([commerceBase, props.projectID, productId, request.email, request.code]);\n if (!claim.current || claim.current.key !== key || claim.current.expires <= Date.now()) {\n claim.current = null;\n const verify = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/free-claim/verify-code`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email: request.email, code: request.code }),\n });\n const verified = await verify.json() as { claimToken?: string; code?: string; expiresIn?: number };\n if (!verify.ok || !verified.claimToken) throw new Error(verified.code ?? \"email-verification-required\");\n claim.current = { key, token: verified.claimToken, expires: Date.now() + (verified.expiresIn ?? 600) * 1000 };\n }\n const bought = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/buy`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ items: [{ id: productId, quantity: 1, amount: 0 }], details: { email: request.email }, claimToken: claim.current.token }),\n });\n const result = await bought.json() as { success?: boolean; url?: string; code?: string };\n if (!bought.ok || !result.url) throw new Error(result.code ?? \"free-order-not-supported\");\n window.location.assign(result.url);\n return;\n }\n await addPackToCart({\n projectID: props.projectID,\n productId,\n name: props.title,\n priceCents: lock.priceCents,\n amount: lock.amount,\n host: props.host,\n });\n };\n}\n\n/**\n * A buyer's pack thread for one service product: history, live updates,\n * sending with pending and refused states, report and mute, and the lock\n * with \"Kup pakiet\" when the replies are used up or the pack expired.\n * Load `bitsnap-react/dist/index.css` once.\n */\nexport function ChatThread(props: ChatThreadProps) {\n const client = useWidgetClient(props);\n const locale = props.locale ?? getLocale();\n const texts = useMemo(() => getChatTexts(props.texts, locale), [props.texts, locale]);\n const conversation = useConversation({\n client,\n target: { kind: \"thread\", threadId: props.threadId, productId: props.productId },\n pollIntervalMs: props.pollIntervalMs,\n realtimeUrl: props.realtimeUrl,\n loadNats: props.loadNats,\n });\n const buy = useBuy(props);\n return (\n <div className=\"bitsnap-react h-full\">\n <ConversationView\n host={props.host}\n conversation={conversation}\n texts={texts}\n locale={locale}\n kind=\"thread\"\n title={props.title}\n onBack={props.onBack}\n onBuy={buy}\n className={props.className}\n />\n </div>\n );\n}\n\n/**\n * A community channel: messages, live updates, posting when the channel\n * allows it (with the channel's moderation refusals shown on the message),\n * report, and the lock with \"Kup dostęp\" for a buyer without a grant.\n * Load `bitsnap-react/dist/index.css` once.\n */\nexport function Channel(props: ChannelProps) {\n const client = useWidgetClient(props);\n const locale = props.locale ?? getLocale();\n const texts = useMemo(() => getChatTexts(props.texts, locale), [props.texts, locale]);\n const conversation = useConversation({\n client,\n target: { kind: \"channel\", channelId: props.channelId, productId: props.productId },\n pollIntervalMs: props.pollIntervalMs,\n realtimeUrl: props.realtimeUrl,\n loadNats: props.loadNats,\n });\n const buy = useBuy(props);\n return (\n <div className=\"bitsnap-react h-full\">\n <ConversationView\n host={props.host}\n conversation={conversation}\n texts={texts}\n locale={locale}\n kind=\"channel\"\n title={props.title}\n onBack={props.onBack}\n onBuy={buy}\n className={props.className}\n />\n </div>\n );\n}\n","import { addProductToCart } from \"../components/products/add-to-cart\";\n\nexport type BuyPackInput = {\n /** Public project id of the shop (the cart's project). */\n projectID: string;\n /** The product (or variant) to add: the pack's service or community product. */\n productId: string;\n name?: string;\n priceCents?: number;\n amount?: number;\n /** Backend origin of the cart, when not the default. */\n host?: string;\n};\n\n/**\n * Default \"Kup pakiet\" / \"Kup dostęp\" action: adds the product to the page\n * cart (`Bitsnap.addProductToCart`) and opens the cart drawer. The page needs\n * a mounted `BitsnapCheckout`. When the page cart belongs to another project\n * or host nothing changes and the promise rejects; pass `onBuy` to the widget\n * to use your own checkout instead.\n */\nexport async function addPackToCart(input: BuyPackInput): Promise<void> {\n await addProductToCart(\n {\n id: input.productId,\n name: input.name ?? \"\",\n price: input.priceCents ?? 0,\n currency: \"\",\n amount: input.amount,\n },\n { projectID: input.projectID, host: input.host, showCart: true },\n );\n}\n","/**\n * Buyer side of the Bitsnap chat service (`bitsnap-messaging`): the shapes\n * the library works with. The wire is snake_case JSON; `client.ts` maps it to\n * these camelCase types. Times are ISO 8601 strings, `null` when absent.\n */\n\n/** Default base URL of the chat service. */\nexport const DEFAULT_CHAT_API_URL = \"https://chat.bitsnap.pl\";\n\n/** Default NATS WebSocket URL, used when the realtime token lists no server. */\nexport const DEFAULT_CHAT_REALTIME_URL =\n \"wss://bitsnap-nats-queue-production.up.railway.app\";\n\n/** One paid order that gives a pack thread creator replies. */\nexport type ChatPack = {\n orderId: string;\n productId?: string;\n replies: number;\n used: number;\n left: number;\n expiresAt: string | null;\n expired: boolean;\n};\n\n/** A stored message, or the pending answer of a send. */\nexport type ChatMessage = {\n id: string;\n threadId: string;\n authorEmail: string;\n body: string;\n attachmentKey?: string;\n attachmentKind?: string;\n createdAt: string;\n pinned: boolean;\n /** Soft deleted: the body is empty. */\n deleted: boolean;\n /** A message of the service, such as the creator's away notice. */\n system: boolean;\n /** `\"pending\"` in the answer of a send: queued, not stored yet. */\n status?: \"pending\";\n};\n\n/** The state of a conversation for the caller (`ThreadView` on the wire). */\nexport type ChatThreadSummary = {\n id: string;\n kind: \"pack\" | \"channel\";\n channelId?: string;\n /** For a buyer, the creator's e-mail (pack) or the channel name. */\n title: string;\n lastMessage?: ChatMessage;\n unread: number;\n repliesTotal: number;\n repliesUsed: number;\n repliesLeft: number;\n expiresAt: string | null;\n /** The caller cannot write: every pack has expired. */\n composerLocked: boolean;\n productId?: string;\n /** The creator's promise in hours (\"odpowiedź w ciągu 48 h\"), 0 for none. */\n responseHours: number;\n packs: ChatPack[];\n muted: boolean;\n};\n\n/** A channel the caller may read. */\nexport type ChatChannelSummary = {\n id: string;\n name: string;\n topic: string;\n welcome: string;\n whoCanPost: \"creator\" | \"members\";\n productId: string;\n /** `creator`, `member` or `muted`. */\n role: string;\n memberCount: number;\n createdAt: string;\n blockLinks: boolean;\n blockMedia: boolean;\n cooldownSeconds: number;\n};\n\n/** A pack the buyer holds and has not opened a thread for yet. */\nexport type ChatPackEntitlement = {\n productId: string;\n name: string;\n repliesLeftHint: number;\n expiresAt: string | null;\n};\n\n/** A community product the buyer holds; `channelId` once a channel is bound. */\nexport type ChatChannelEntitlement = {\n productId: string;\n channelId?: string;\n};\n\n/** `GET /v1/me`: the buyer's chat in one project. */\nexport type ChatMe = {\n email: string;\n threads: ChatThreadSummary[];\n entitlements: {\n packs: ChatPackEntitlement[];\n channels: ChatChannelEntitlement[];\n };\n channels: ChatChannelSummary[];\n};\n\n/** One page of history, newest first as the service answers it. */\nexport type ChatMessagePage = {\n messages: ChatMessage[];\n hasMore: boolean;\n};\n\n/** The thread part of a send answer. */\nexport type ChatThreadState = Pick<\n ChatThreadSummary,\n | \"id\"\n | \"kind\"\n | \"repliesTotal\"\n | \"repliesUsed\"\n | \"repliesLeft\"\n | \"expiresAt\"\n | \"composerLocked\"\n | \"responseHours\"\n>;\n\n/** `POST /v1/threads/{id}/messages` answers 202 with the queued message. */\nexport type ChatSendResult = {\n message: ChatMessage;\n thread: ChatThreadState;\n};\n\nexport type ChatReportReason = \"spam\" | \"abuse\" | \"illegal\" | \"other\";\n\n/** `POST /v1/realtime/token`. */\nexport type ChatRealtimeToken = {\n jwt: string;\n expiresAt: string;\n servers: string[];\n subjects: string[];\n truncated: boolean;\n};\n\n/** Live events on the thread or channel subject. */\nexport type ChatRealtimeEvent =\n | { type: \"message.created\"; message: ChatMessage }\n | { type: \"message.rejected\"; id: string; reason: string }\n | { type: \"message.deleted\"; id: string }\n | { type: \"message.pinned\"; id: string }\n | { type: \"message.unpinned\"; id: string };\n\n/** How a conversation hears about new messages. */\nexport type ChatLiveMode = \"connecting\" | \"live\" | \"polling\" | \"off\";\n","import {\n DEFAULT_CHAT_API_URL,\n type ChatChannelSummary,\n type ChatMe,\n type ChatMessage,\n type ChatMessagePage,\n type ChatPack,\n type ChatRealtimeEvent,\n type ChatRealtimeToken,\n type ChatReportReason,\n type ChatSendResult,\n type ChatThreadState,\n type ChatThreadSummary,\n} from \"./types\";\n\n/**\n * An error answer of the chat service (`{\"error\":\"<code>\"}`), or `network`\n * (status 0) when the request did not get an answer.\n *\n * Codes the buyer UI handles: `unauthorized` (401), `plan_required`,\n * `locked`, `muted`, `blocked`, `forbidden`, `read_only` (403), `not_found`\n * (404), `moderation_link`, `moderation_banned_word`, `moderation_media`\n * (422), `cooldown` (429, with `retryAfter`), `unavailable` (503),\n * `invalid_project` (400).\n */\nexport class ChatError extends Error {\n readonly code: string;\n readonly status: number;\n /** `cooldown`: seconds to wait before the next post. */\n readonly retryAfter?: number;\n /** Extra fields of the answer (`locked`: product, price type and bounds). */\n readonly data: Record<string, unknown>;\n\n constructor(\n code: string,\n status: number,\n data: Record<string, unknown> = {},\n ) {\n super(`chat: ${code} (${status})`);\n this.name = \"ChatError\";\n this.code = code;\n this.status = status;\n this.data = data;\n const retry = Number(data.retry_after);\n if (Number.isFinite(retry) && retry > 0) this.retryAfter = retry;\n }\n}\n\nexport type ChatClientOptions = {\n /** Project id sent as `X-Bitsnap-Project`. */\n projectId: string;\n /** Bearer credential: the panel session token or a `bcht_` buyer token. */\n token: string;\n /** Base URL of the chat service. Defaults to `https://chat.bitsnap.pl`. */\n baseUrl?: string;\n /** Replaces `fetch` (tests, server rendering). */\n fetch?: typeof fetch;\n};\n\n/** The buyer calls of the chat service HTTP API. */\nexport type ChatClient = {\n readonly baseUrl: string;\n readonly projectId: string;\n me(): Promise<ChatMe>;\n /** Opens or returns the pack thread of a service product. */\n openThread(productId: string): Promise<ChatThreadSummary>;\n /** Newest first; `before` is a message id. */\n messages(\n threadId: string,\n options?: { before?: string; limit?: number },\n ): Promise<ChatMessagePage>;\n /** Queues a message. The service answers 202 with `status: \"pending\"`. */\n send(\n threadId: string,\n input: { body: string; clientId?: string },\n ): Promise<ChatSendResult>;\n deleteMessage(threadId: string, messageId: string): Promise<void>;\n mute(threadId: string, muted: boolean): Promise<void>;\n report(input: {\n threadId: string;\n messageId?: string;\n reason: ChatReportReason;\n note?: string;\n }): Promise<void>;\n markRead(threadId: string, messageId: string): Promise<void>;\n /**\n * A NATS token for every conversation the caller may read. The answer is\n * kept until a minute and a half before it expires (the service mints one\n * per caller every 10 s); `fresh` asks for a new one.\n */\n realtimeToken(options?: { fresh?: boolean }): Promise<ChatRealtimeToken>;\n};\n\nexport function createChatClient(options: ChatClientOptions): ChatClient {\n const baseUrl = (options.baseUrl || DEFAULT_CHAT_API_URL).replace(/\\/+$/, \"\");\n const doFetch = options.fetch ?? ((...args) => fetch(...args));\n\n async function call(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<{ status: number; json: unknown }> {\n let response: Response;\n try {\n response = await doFetch(baseUrl + path, {\n method,\n headers: {\n Authorization: `Bearer ${options.token}`,\n \"X-Bitsnap-Project\": options.projectId,\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n } catch {\n throw new ChatError(\"network\", 0);\n }\n let json: unknown = null;\n try {\n const text = await response.text();\n json = text ? JSON.parse(text) : null;\n } catch {\n json = null;\n }\n if (!response.ok) {\n const data = isRecord(json) ? json : {};\n const code = typeof data.error === \"string\" ? data.error : \"internal_error\";\n throw new ChatError(code, response.status, data);\n }\n return { status: response.status, json };\n }\n\n const id = encodeURIComponent;\n let cachedToken: ChatRealtimeToken | undefined;\n\n return {\n baseUrl,\n projectId: options.projectId,\n async me() {\n const { json } = await call(\"GET\", \"/v1/me\");\n return mapMe(json);\n },\n async openThread(productId) {\n const { json } = await call(\"POST\", \"/v1/threads\", {\n product_id: productId,\n });\n return mapThread(json);\n },\n async messages(threadId, opts = {}) {\n const query = new URLSearchParams();\n if (opts.before) query.set(\"before\", opts.before);\n if (opts.limit) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n const suffix = qs ? `?${qs}` : \"\";\n const { json } = await call(\n \"GET\",\n `/v1/threads/${id(threadId)}/messages${suffix}`,\n );\n const page = isRecord(json) ? json : {};\n return {\n messages: arr(page.messages).map(mapMessage),\n hasMore: page.has_more === true,\n };\n },\n async send(threadId, input) {\n const { json } = await call(\n \"POST\",\n `/v1/threads/${id(threadId)}/messages`,\n {\n body: input.body,\n ...(input.clientId ? { client_id: input.clientId } : {}),\n },\n );\n const res = isRecord(json) ? json : {};\n return {\n message: mapMessage(res.message),\n thread: mapThreadState(res.thread),\n };\n },\n async deleteMessage(threadId, messageId) {\n await call(\n \"DELETE\",\n `/v1/threads/${id(threadId)}/messages/${id(messageId)}`,\n );\n },\n async mute(threadId, muted) {\n await call(\"POST\", `/v1/threads/${id(threadId)}/mute`, { muted });\n },\n async report(input) {\n await call(\"POST\", \"/v1/reports\", {\n thread_id: input.threadId,\n ...(input.messageId ? { message_id: input.messageId } : {}),\n reason: input.reason,\n ...(input.note ? { note: input.note } : {}),\n });\n },\n async markRead(threadId, messageId) {\n await call(\"POST\", `/v1/threads/${id(threadId)}/receipts`, {\n state: \"read\",\n message_id: messageId,\n });\n },\n async realtimeToken(opts = {}) {\n if (\n !opts.fresh &&\n cachedToken &&\n Date.parse(cachedToken.expiresAt) - Date.now() > 90_000\n ) {\n return cachedToken;\n }\n const { json } = await call(\"POST\", \"/v1/realtime/token\");\n const res = isRecord(json) ? json : {};\n return (cachedToken = {\n jwt: str(res.jwt),\n expiresAt: str(res.expires_at),\n servers: arr(res.servers).filter((s): s is string => typeof s === \"string\"),\n subjects: arr(res.subjects).filter((s): s is string => typeof s === \"string\"),\n truncated: res.truncated === true,\n });\n },\n };\n}\n\n/** Parses one live event of the thread or channel subject. */\nexport function parseRealtimeEvent(raw: unknown): ChatRealtimeEvent | null {\n if (!isRecord(raw) || typeof raw.type !== \"string\") return null;\n const eventId = str(raw.id);\n switch (raw.type) {\n case \"message.created\":\n return { type: \"message.created\", message: mapMessage(raw) };\n case \"message.rejected\":\n return { type: \"message.rejected\", id: eventId, reason: str(raw.reason) };\n case \"message.deleted\":\n case \"message.pinned\":\n case \"message.unpinned\":\n return { type: raw.type, id: eventId };\n default:\n return null;\n }\n}\n\n// --- wire mapping -------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n\nfunction arr(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction str(value: unknown): string {\n return typeof value === \"string\" ? value : value == null ? \"\" : String(value);\n}\n\nfunction num(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction optStr(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\nfunction time(value: unknown): string | null {\n return typeof value === \"string\" && value !== \"\" ? value : null;\n}\n\nexport function mapMessage(raw: unknown): ChatMessage {\n const m = isRecord(raw) ? raw : {};\n return {\n id: str(m.id),\n threadId: str(m.thread_id),\n authorEmail: str(m.author_email),\n body: str(m.body),\n attachmentKey: optStr(m.attachment_key),\n attachmentKind: optStr(m.attachment_kind),\n createdAt: str(m.created_at),\n pinned: m.pinned === true,\n deleted: m.deleted === true,\n system: m.system === true,\n ...(m.status === \"pending\" ? { status: \"pending\" as const } : {}),\n };\n}\n\nfunction mapPack(raw: unknown): ChatPack {\n const p = isRecord(raw) ? raw : {};\n return {\n orderId: str(p.order_id),\n productId: optStr(p.product_id),\n replies: num(p.replies),\n used: num(p.used),\n left: num(p.left),\n expiresAt: time(p.expires_at),\n expired: p.expired === true,\n };\n}\n\nfunction mapThreadState(raw: unknown): ChatThreadState {\n const t = isRecord(raw) ? raw : {};\n return {\n id: str(t.id),\n kind: t.kind === \"channel\" ? \"channel\" : \"pack\",\n repliesTotal: num(t.replies_total),\n repliesUsed: num(t.replies_used),\n repliesLeft: num(t.replies_left),\n expiresAt: time(t.expires_at),\n composerLocked: t.composer_locked === true,\n responseHours: num(t.response_hours),\n };\n}\n\nexport function mapThread(raw: unknown): ChatThreadSummary {\n const t = isRecord(raw) ? raw : {};\n return {\n ...mapThreadState(t),\n channelId: optStr(t.channel_id),\n title: str(t.title),\n lastMessage: isRecord(t.last_message) ? mapMessage(t.last_message) : undefined,\n unread: num(t.unread),\n productId: optStr(t.product_id),\n packs: arr(t.packs).map(mapPack),\n muted: t.muted === true,\n };\n}\n\nfunction mapChannel(raw: unknown): ChatChannelSummary {\n const c = isRecord(raw) ? raw : {};\n return {\n id: str(c.id),\n name: str(c.name),\n topic: str(c.topic),\n welcome: str(c.welcome),\n whoCanPost: c.who_can_post === \"members\" ? \"members\" : \"creator\",\n productId: str(c.product_id),\n role: str(c.role),\n memberCount: num(c.member_count),\n createdAt: str(c.created_at),\n blockLinks: c.block_links === true,\n blockMedia: c.block_media === true,\n cooldownSeconds: num(c.cooldown_seconds),\n };\n}\n\nfunction mapMe(raw: unknown): ChatMe {\n const m = isRecord(raw) ? raw : {};\n const ent = isRecord(m.entitlements) ? m.entitlements : {};\n return {\n email: str(m.email),\n threads: arr(m.threads).map(mapThread),\n entitlements: {\n packs: arr(ent.packs).map((raw) => {\n const p = isRecord(raw) ? raw : {};\n return {\n productId: str(p.product_id),\n name: str(p.name),\n repliesLeftHint: num(p.replies_left_hint),\n expiresAt: time(p.expires_at),\n };\n }),\n channels: arr(ent.channels).map((raw) => {\n const c = isRecord(raw) ? raw : {};\n return { productId: str(c.product_id), channelId: optStr(c.channel_id) };\n }),\n },\n channels: arr(m.channels).map(mapChannel),\n };\n}\n","import {\n AlertCircle,\n ArrowLeft,\n Clock,\n Lock,\n MoreHorizontal,\n Paperclip,\n Pin,\n SendHorizontal,\n} from \"lucide-react\";\nimport {\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { TurnstileChallenge } from \"../captcha/TurnstileChallenge\";\nimport {\n fill,\n formatChatDate,\n formatMessageTime,\n rejectionText,\n repliesLeftText,\n type ChatTexts,\n} from \"./texts\";\nimport type { ChatReportReason } from \"./types\";\nimport type {\n ConversationActions,\n ConversationLock,\n ConversationMessage,\n ConversationState,\n} from \"./useConversation\";\n\nexport type ConversationViewProps = {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n locale: string;\n kind: \"thread\" | \"channel\";\n /** Heading; defaults to the thread title or the channel name. */\n title?: string;\n onBack?: () => void;\n /** The lock's buy action. Rejects to show the error. */\n onBuy?: (lock: ConversationLock & { email?: string; code?: string; turnstileToken?: string }) => Promise<void> | void;\n className?: string;\n host?: string;\n};\n\nconst focusRing =\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500\";\n\nconst quietButton = cn(\n \"inline-flex min-h-11 items-center gap-2 rounded-md px-3 text-sm text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800\",\n focusRing,\n);\n\nconst primaryButton = cn(\n \"inline-flex min-h-11 items-center justify-center gap-2 rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200\",\n focusRing,\n);\n\n/** The shared body of `ChatThread`, `Channel` and the panel screen. */\nexport function ConversationView({\n host,\n conversation: c,\n texts,\n locale,\n kind,\n title,\n onBack,\n onBuy,\n className,\n}: ConversationViewProps) {\n const [menuOpen, setMenuOpen] = useState(false);\n const [reporting, setReporting] = useState<{ messageId?: string } | null>(null);\n const [notice, setNotice] = useState<string>();\n const [draft, setDraft] = useState(\"\");\n const [reportSent, setReportSent] = useState(false);\n useEffect(() => {\n if (!reportSent) return;\n const timer = setTimeout(() => setReportSent(false), 6000);\n return () => clearTimeout(timer);\n }, [reportSent]);\n const heading =\n title ||\n c.channel?.name ||\n (kind === \"channel\" ? texts.channelFallbackTitle : texts.chatFallbackTitle);\n\n const meta = metaLine(c, texts, locale);\n\n return (\n <section\n data-bitsnap-chat={kind}\n aria-label={heading}\n className={cn(\n \"flex h-full min-h-[28rem] w-full min-w-0 flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white text-neutral-900 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-50\",\n className,\n )}\n >\n <header className=\"flex items-start gap-2 border-b border-neutral-200 px-3 py-2 dark:border-neutral-800\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={texts.back}\n className={cn(quietButton, \"w-11 justify-center px-0\")}\n >\n <ArrowLeft className=\"size-5\" aria-hidden=\"true\" />\n </button>\n )}\n <div className=\"min-w-0 flex-1 py-1.5\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold\">\n <span className=\"truncate\">{heading}</span>\n <LiveDot live={c.live} texts={texts} />\n </h2>\n {meta.length > 0 && (\n <p className=\"mt-0.5 flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-neutral-600 dark:text-neutral-400\">\n {meta.map((item) => (\n <span key={item}>{item}</span>\n ))}\n </p>\n )}\n </div>\n {c.status === \"ready\" && (\n <button\n type=\"button\"\n aria-label={texts.conversationActions}\n aria-expanded={menuOpen}\n onClick={() => setMenuOpen((open) => !open)}\n className={cn(quietButton, \"w-11 justify-center px-0\")}\n >\n <MoreHorizontal className=\"size-5\" aria-hidden=\"true\" />\n </button>\n )}\n </header>\n {menuOpen && c.status === \"ready\" && (\n <div className=\"flex flex-wrap gap-1 border-b border-neutral-200 bg-neutral-50 px-3 py-1 dark:border-neutral-800 dark:bg-neutral-950\">\n {kind === \"thread\" && c.thread && (\n <button\n type=\"button\"\n className={quietButton}\n onClick={async () => {\n setMenuOpen(false);\n try {\n await c.setMuted(!c.thread!.muted);\n setNotice(undefined);\n } catch {\n setNotice(texts.muteError);\n }\n }}\n >\n {c.thread.muted ? texts.unmuteThread : texts.muteThread}\n </button>\n )}\n <button\n type=\"button\"\n className={quietButton}\n onClick={() => {\n setMenuOpen(false);\n setReporting({});\n }}\n >\n {texts.reportConversation}\n </button>\n </div>\n )}\n\n {c.status === \"loading\" && <LoadingBody texts={texts} />}\n {c.status === \"error\" && <ErrorBody conversation={c} texts={texts} />}\n {c.status === \"locked\" && c.lock && (\n <div className=\"flex flex-1 flex-col justify-center overflow-y-auto p-4\">\n <LockCard host={host} lock={c.lock} kind={kind} texts={texts} locale={locale} onBuy={onBuy} prominent />\n </div>\n )}\n {c.status === \"ready\" && (\n <>\n <MessageList\n conversation={c}\n texts={texts}\n locale={locale}\n kind={kind}\n onReport={(messageId) => setReporting({ messageId })}\n onEdit={(message) => {\n setDraft(message.body);\n c.discard(message.id);\n }}\n />\n {notice && (\n <p role=\"status\" className=\"px-4 pb-2 text-xs text-red-700 dark:text-red-400\">\n {notice}\n </p>\n )}\n {c.lock && (\n <div className=\"border-t border-neutral-200 p-3 dark:border-neutral-800\">\n <LockCard host={host} lock={c.lock} kind={kind} texts={texts} locale={locale} onBuy={onBuy} />\n </div>\n )}\n {reporting ? (\n <ReportForm\n texts={texts}\n messageId={reporting.messageId}\n onCancel={() => setReporting(null)}\n onSubmit={async (reason, note) => {\n await c.report({ messageId: reporting.messageId, reason, note });\n setReporting(null);\n setNotice(undefined);\n setReportSent(true);\n }}\n />\n ) : (\n <Composer\n conversation={c}\n texts={texts}\n kind={kind}\n draft={draft}\n setDraft={setDraft}\n />\n )}\n </>\n )}\n {reportSent && (\n <p role=\"status\" className=\"border-t border-neutral-200 px-4 py-2 text-xs text-neutral-700 dark:border-neutral-800 dark:text-neutral-300\">\n {texts.reportSent}\n </p>\n )}\n </section>\n );\n}\n\nfunction metaLine(c: ConversationState, texts: ChatTexts, locale: string): string[] {\n const items: string[] = [];\n if (c.thread && c.thread.kind === \"pack\") {\n items.push(repliesLeftText(texts, c.thread.repliesLeft, locale));\n if (c.thread.expiresAt) {\n const date = formatChatDate(c.thread.expiresAt, locale);\n items.push(\n fill(c.thread.composerLocked ? texts.expiredOn : texts.expiresOn, { date }),\n );\n }\n if (c.thread.responseHours > 0)\n items.push(fill(texts.responseHours, { hours: c.thread.responseHours }));\n if (c.thread.muted) items.push(texts.mutedBadge);\n }\n if (c.channel) {\n if (c.channel.topic) items.push(c.channel.topic);\n if (c.channel.memberCount > 0)\n items.push(fill(texts.members, { count: c.channel.memberCount }));\n }\n return items;\n}\n\nfunction LiveDot({ live, texts }: { live: ConversationState[\"live\"]; texts: ChatTexts }) {\n if (live !== \"live\" && live !== \"polling\") return null;\n const label = live === \"live\" ? texts.liveLabel : texts.pollingLabel;\n return (\n <span\n title={label}\n data-live={live}\n className={cn(\n \"inline-block size-2 shrink-0 rounded-full\",\n live === \"live\" ? \"bg-emerald-500\" : \"bg-neutral-400 dark:bg-neutral-500\",\n )}\n >\n <span className=\"sr-only\">{label}</span>\n </span>\n );\n}\n\nfunction LoadingBody({ texts }: { texts: ChatTexts }) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label={texts.loading} className=\"flex flex-1 flex-col gap-3 p-4\">\n {[60, 40, 70].map((width, index) => (\n <div\n key={index}\n aria-hidden=\"true\"\n style={{ width: `${width}%` }}\n className={cn(\n \"h-10 rounded-2xl bg-neutral-100 motion-safe:animate-pulse dark:bg-neutral-800\",\n index === 1 && \"self-end\",\n )}\n />\n ))}\n </div>\n );\n}\n\nfunction errorText(code: string, texts: ChatTexts): string {\n switch (code) {\n case \"no_token\":\n return texts.errorNoToken;\n case \"unauthorized\":\n return texts.errorUnauthorized;\n case \"plan_required\":\n return texts.errorPlan;\n case \"not_found\":\n return texts.errorNotFound;\n case \"unavailable\":\n case \"network\":\n return texts.errorUnavailable;\n default:\n return texts.errorGeneric;\n }\n}\n\nfunction ErrorBody({\n conversation: c,\n texts,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n}) {\n const code = c.error?.code ?? \"\";\n const canRetry = code !== \"no_token\" && code !== \"plan_required\" && code !== \"unauthorized\";\n return (\n <div role=\"alert\" className=\"flex flex-1 flex-col items-start justify-center gap-3 p-4\">\n <p className=\"text-sm text-neutral-700 dark:text-neutral-300\">{errorText(code, texts)}</p>\n {canRetry && (\n <button type=\"button\" className={primaryButton} onClick={c.reload}>\n {texts.retry}\n </button>\n )}\n </div>\n );\n}\n\nfunction LockCard({\n host,\n lock,\n kind,\n texts,\n locale,\n onBuy,\n prominent,\n}: {\n host?: string;\n lock: ConversationLock;\n kind: \"thread\" | \"channel\";\n texts: ChatTexts;\n locale: string;\n onBuy?: ConversationViewProps[\"onBuy\"];\n prominent?: boolean;\n}) {\n const [buying, setBuying] = useState(false);\n const [failed, setFailed] = useState(\"\");\n const [amountText, setAmountText] = useState(String((lock.priceCents ?? 0) / 100));\n const normalized = amountText.trim().replace(\",\", \".\");\n const amount = /^\\d+(\\.\\d{0,2})?$/.test(normalized) ? Math.round(Number(normalized) * 100) : NaN;\n const isPwyw = lock.priceType === \"pay_what_you_want\";\n const min = lock.minimumCents ?? 0;\n const max = lock.maximumCents ?? Number.MAX_SAFE_INTEGER;\n const validAmount = Number.isInteger(amount) && amount >= min && amount <= max && (amount > 0 || lock.freeClaimLimit != null);\n const isFreeClaim = lock.freeClaimLimit != null && (lock.priceType === \"free\" || (isPwyw && min === 0 && amount === 0));\n const [email, setEmail] = useState(\"\");\n const [code, setCode] = useState(\"\");\n const [codeSent, setCodeSent] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState<string | null>(null);\n const [challengeAttempt, setChallengeAttempt] = useState(0);\n const [cooldown, setCooldown] = useState(0);\n const codeInput = useRef<HTMLInputElement>(null);\n useEffect(() => { if (codeSent) codeInput.current?.focus(); }, [codeSent]);\n useEffect(() => {\n if (cooldown <= 0) return;\n const timer = window.setTimeout(() => setCooldown(cooldown - 1), 1000);\n return () => window.clearTimeout(timer);\n }, [cooldown]);\n const restart = () => { setCodeSent(false); setCode(\"\"); setTurnstileToken(null); setChallengeAttempt((n) => n + 1); setFailed(\"\"); };\n const english = locale.toLowerCase().startsWith(\"en\");\n const [title, body] =\n lock.reason === \"replies\"\n ? [texts.lockRepliesTitle, texts.lockRepliesBody]\n : lock.reason === \"expired\"\n ? [texts.lockExpiredTitle, texts.lockExpiredBody]\n : kind === \"channel\"\n ? [texts.lockChannelTitle, texts.lockChannelBody]\n : [texts.lockThreadTitle, texts.lockThreadBody];\n const action = kind === \"channel\" ? texts.buyAccess : texts.buyPack;\n const canBuy = onBuy != null && lock.productId != null;\n\n return (\n <div\n data-testid=\"chat-lock\"\n data-lock={lock.reason}\n className={cn(\n \"flex gap-3 rounded-xl bg-neutral-100 p-3 dark:bg-neutral-800\",\n prominent ? \"flex-col items-start p-5\" : \"flex-col sm:flex-row sm:items-center\",\n )}\n >\n <Lock\n className={cn(\"shrink-0 text-neutral-500 dark:text-neutral-400\", prominent ? \"size-6\" : \"hidden size-5 sm:block\")}\n aria-hidden=\"true\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className={cn(\"font-semibold\", prominent ? \"text-lg\" : \"text-sm\")}>{title}</p>\n <p className=\"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300\">{body}</p>\n {isFreeClaim && (\n <div className=\"mt-3 grid max-w-sm gap-2\">\n <label className=\"grid gap-1 text-sm\">\n <span>{english ? \"Email\" : \"E-mail\"}</span>\n <input type=\"email\" autoComplete=\"email\" value={email} onChange={(event) => { setEmail(event.currentTarget.value); restart(); }} disabled={buying} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" />\n </label>\n {codeSent ? (\n <label className=\"grid gap-1 text-sm\"><span>{english ? \"Email code\" : \"Kod z e-maila\"}</span><input ref={codeInput} inputMode=\"numeric\" autoComplete=\"one-time-code\" value={code} onChange={(event) => setCode(event.currentTarget.value)} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" /></label>\n ) : email.includes(\"@\") ? <TurnstileChallenge host={host} key={challengeAttempt} action=\"free_chat_claim\" locale={locale} onToken={setTurnstileToken} onFailure={() => setTurnstileToken(null)} /> : null}\n {codeSent && <button type=\"button\" disabled={buying || cooldown > 0} className=\"min-h-11 text-sm underline disabled:opacity-50\" onClick={restart}>{english ? \"Request a new code\" : \"Poproś o nowy kod\"}{cooldown > 0 ? ` (${cooldown}s)` : \"\"}</button>}\n </div>\n )}\n {isPwyw && (\n <label className=\"mt-3 grid max-w-xs gap-1 text-sm\">\n <span>{english ? `Amount (${(min / 100).toFixed(2)}–${Number.isFinite(max) ? (max / 100).toFixed(2) : \"∞\"} PLN)` : `Kwota (${(min / 100).toFixed(2)}–${Number.isFinite(max) ? (max / 100).toFixed(2) : \"∞\"} zł)`}</span>\n <input aria-label={english ? \"Amount\" : \"Kwota\"} inputMode=\"decimal\" type=\"text\" min={min / 100} max={Number.isFinite(max) ? max / 100 : undefined} step=\"0.01\" value={amountText} onChange={(event) => setAmountText(event.currentTarget.value)} onBlur={() => { if (Number.isFinite(amount)) setAmountText((amount / 100).toFixed(2)); }} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" />\n {!validAmount && <span role=\"alert\" className=\"text-xs text-red-700 dark:text-red-400\">{english ? (amount === 0 && lock.freeClaimLimit == null ? \"Free claims are unavailable. Enter a positive amount.\" : \"Enter an amount within the range.\") : (amount === 0 && lock.freeClaimLimit == null ? \"Darmowy odbiór jest niedostępny. Wpisz kwotę większą od zera.\" : \"Kwota musi mieścić się w podanym zakresie.\")}</span>}\n </label>\n )}\n {lock.welcome && (\n <p className=\"mt-3 whitespace-pre-wrap text-sm\">{lock.welcome}</p>\n )}\n {failed && (\n <p role=\"alert\" className=\"mt-2 text-sm text-red-700 dark:text-red-400\">\n {failed}\n </p>\n )}\n </div>\n {canBuy && (\n <button\n type=\"button\"\n className={cn(primaryButton, \"shrink-0\")}\n disabled={buying || (isPwyw && !validAmount) || (isFreeClaim && (!email.includes(\"@\") || (!codeSent && turnstileToken === null) || (codeSent && !code)))}\n aria-busy={buying || undefined}\n onClick={async () => {\n setBuying(true);\n setFailed(\"\");\n try {\n if (isFreeClaim) {\n await onBuy!({ ...lock, ...(isPwyw ? { amount } : {}), email, ...(codeSent ? { code } : { turnstileToken }) });\n if (!codeSent) { setCodeSent(true); setCooldown(60); }\n } else {\n await onBuy!({ ...lock, ...(isPwyw ? { amount } : {}) });\n }\n } catch (error) {\n setFailed(chatBuyError(error, locale, texts.buyError));\n if (!codeSent) { setTurnstileToken(null); setChallengeAttempt((n) => n + 1); }\n } finally {\n setBuying(false);\n }\n }}\n >\n {buying ? texts.buying : isPwyw ? (english ? \"Choose what you pay\" : \"Kup pakiet, płacąc, ile chcesz\") : isFreeClaim ? (codeSent ? (english ? \"Claim for free\" : \"Odbierz za darmo\") : (english ? \"Send code\" : \"Wyślij kod\")) : action}\n </button>\n )}\n </div>\n );\n}\n\nfunction authorLabel(message: ConversationMessage, mine: boolean, kind: \"thread\" | \"channel\", texts: ChatTexts) {\n if (mine) return texts.you;\n if (kind === \"thread\") return texts.creator;\n // Channels have no display names yet: the part of the e-mail before \"@\".\n return message.authorEmail.split(\"@\")[0] || texts.creator;\n}\n\nfunction MessageList({\n conversation: c,\n texts,\n locale,\n kind,\n onReport,\n onEdit,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n locale: string;\n kind: \"thread\" | \"channel\";\n onReport: (messageId: string) => void;\n onEdit: (message: ConversationMessage) => void;\n}) {\n const scroller = useRef<HTMLDivElement>(null);\n const stick = useRef(true);\n const count = c.messages.length;\n const lastId = c.messages[count - 1]?.id;\n\n useLayoutEffect(() => {\n const el = scroller.current;\n if (el && stick.current) el.scrollTop = el.scrollHeight;\n }, [count, lastId]);\n\n return (\n <div\n ref={scroller}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n onScroll={(event) => {\n const el = event.currentTarget;\n stick.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;\n }}\n className=\"flex flex-1 flex-col gap-3 overflow-y-auto overscroll-contain px-3 py-4 sm:px-4\"\n >\n {c.hasMore && (\n <button\n type=\"button\"\n onClick={() => {\n stick.current = false;\n void c.loadOlder();\n }}\n disabled={c.loadingOlder}\n className={cn(quietButton, \"self-center text-xs\")}\n >\n {c.loadingOlder ? texts.loading : texts.loadOlder}\n </button>\n )}\n {count === 0 && (\n <div className=\"m-auto max-w-sm text-center\" data-testid=\"chat-empty\">\n <p className=\"text-sm font-medium\">{texts.noMessagesYet}</p>\n <p className=\"mt-1 text-sm text-neutral-600 dark:text-neutral-400\">\n {kind === \"channel\" ? texts.emptyChannel : texts.emptyThread}\n </p>\n {c.channel?.welcome && (\n <p className=\"mt-3 whitespace-pre-wrap text-sm\">{c.channel.welcome}</p>\n )}\n </div>\n )}\n {c.messages.map((message) => (\n <MessageRow\n key={message.id}\n message={message}\n mine={message.authorEmail.toLowerCase() === c.me}\n kind={kind}\n texts={texts}\n locale={locale}\n onDelete={() => c.deleteMessage(message.id)}\n onReport={() => onReport(message.id)}\n onRetry={() => void c.retry(message.id)}\n onDiscard={() => c.discard(message.id)}\n onEdit={() => onEdit(message)}\n />\n ))}\n </div>\n );\n}\n\nfunction MessageRow({\n message,\n mine,\n kind,\n texts,\n locale,\n onDelete,\n onReport,\n onRetry,\n onDiscard,\n onEdit,\n}: {\n message: ConversationMessage;\n mine: boolean;\n kind: \"thread\" | \"channel\";\n texts: ChatTexts;\n locale: string;\n onDelete: () => Promise<void>;\n onReport: () => void;\n onRetry: () => void;\n onDiscard: () => void;\n onEdit: () => void;\n}) {\n const [actions, setActions] = useState(false);\n const [confirmDelete, setConfirmDelete] = useState(false);\n const local = message.local;\n\n if (message.system) {\n return (\n <div data-message-id={message.id} className=\"mx-auto max-w-md text-center text-xs text-neutral-600 dark:text-neutral-400\">\n <span className=\"font-medium\">{texts.systemLabel}</span>\n {message.body && <p className=\"mt-0.5 whitespace-pre-wrap\">{message.body}</p>}\n </div>\n );\n }\n\n const refused = local?.state === \"rejected\" || local?.state === \"failed\";\n const waiting = local?.state === \"pending\" || local?.state === \"unconfirmed\";\n const canAct = !local && !message.deleted;\n\n return (\n <div\n data-message-id={message.id}\n data-state={local?.state ?? (message.deleted ? \"deleted\" : \"stored\")}\n className={cn(\"group flex max-w-[85%] flex-col gap-1 sm:max-w-[75%]\", mine ? \"items-end self-end\" : \"items-start self-start\")}\n >\n <div className={cn(\"flex items-end gap-1\", mine && \"flex-row-reverse\")}>\n <div\n className={cn(\n \"min-w-0 rounded-2xl px-3.5 py-2 text-sm leading-relaxed\",\n message.deleted\n ? \"border border-dashed border-neutral-300 bg-transparent italic text-neutral-500 dark:border-neutral-700 dark:text-neutral-400\"\n : mine\n ? \"bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900\"\n : \"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50\",\n waiting && \"opacity-70\",\n refused && \"border border-red-600 dark:border-red-400\",\n )}\n >\n {message.deleted ? (\n texts.deletedPlaceholder\n ) : (\n <>\n <p className=\"whitespace-pre-wrap break-words\">{message.body}</p>\n {message.attachmentKey && (\n <span className=\"mt-1 inline-flex items-center gap-1 text-xs opacity-80\">\n <Paperclip className=\"size-3.5\" aria-hidden=\"true\" />\n {texts.attachment}\n </span>\n )}\n </>\n )}\n </div>\n {canAct && (\n <button\n type=\"button\"\n aria-label={texts.messageActions}\n aria-expanded={actions}\n onClick={() => {\n setActions((open) => !open);\n setConfirmDelete(false);\n }}\n className={cn(\n \"inline-flex size-11 shrink-0 items-center justify-center rounded-md text-neutral-500 opacity-100 hover:bg-neutral-100 sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100 dark:text-neutral-400 dark:hover:bg-neutral-800\",\n actions && \"sm:opacity-100\",\n focusRing,\n )}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n <p className=\"flex items-center gap-1.5 px-1 text-[11px] text-neutral-500 dark:text-neutral-400\">\n {message.pinned && (\n <span className=\"inline-flex items-center gap-0.5\">\n <Pin className=\"size-3\" aria-hidden=\"true\" />\n {texts.pinned}\n </span>\n )}\n <span>{authorLabel(message, mine, kind, texts)}</span>\n <span aria-hidden=\"true\">·</span>\n {waiting ? (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3\" aria-hidden=\"true\" />\n {local?.state === \"pending\" ? texts.pending : texts.notConfirmed}\n </span>\n ) : (\n <time dateTime={message.createdAt}>{formatMessageTime(message.createdAt, locale)}</time>\n )}\n </p>\n {refused && (\n <div role=\"alert\" className={cn(\"flex flex-col gap-1\", mine ? \"items-end\" : \"items-start\")}>\n <p className=\"flex items-start gap-1 text-xs text-red-700 dark:text-red-400\">\n <AlertCircle className=\"mt-px size-3.5 shrink-0\" aria-hidden=\"true\" />\n {rejectionText(texts, local?.reason ?? \"\", local?.retryAfter)}\n </p>\n <div className=\"flex flex-wrap gap-1\">\n {local?.state === \"failed\" || local?.reason === \"cooldown\" ? (\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onRetry}>\n {texts.retrySend}\n </button>\n ) : (\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onEdit}>\n {texts.editMessage}\n </button>\n )}\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onDiscard}>\n {texts.dismiss}\n </button>\n </div>\n </div>\n )}\n {actions && canAct && (\n <div className=\"flex flex-wrap gap-1\">\n {mine ? (\n <button\n type=\"button\"\n className={cn(quietButton, \"text-xs text-red-700 dark:text-red-400\")}\n onClick={async () => {\n if (!confirmDelete) {\n setConfirmDelete(true);\n return;\n }\n setActions(false);\n await onDelete().catch(() => undefined);\n }}\n >\n {confirmDelete ? texts.deleteConfirm : texts.deleteMessage}\n </button>\n ) : (\n <button\n type=\"button\"\n className={cn(quietButton, \"text-xs\")}\n onClick={() => {\n setActions(false);\n onReport();\n }}\n >\n {texts.reportMessage}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction composerBlock(c: ConversationState, kind: \"thread\" | \"channel\", texts: ChatTexts): string | undefined {\n if (kind === \"thread\" && c.thread?.composerLocked) return texts.composerExpired;\n if (kind === \"channel\" && c.channel) {\n if (c.channel.role === \"muted\") return texts.composerMuted;\n if (c.channel.whoCanPost === \"creator\" && c.channel.role !== \"creator\")\n return texts.composerCreatorOnly;\n }\n return undefined;\n}\n\nfunction Composer({\n conversation: c,\n texts,\n kind,\n draft,\n setDraft,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n kind: \"thread\" | \"channel\";\n draft: string;\n setDraft: (value: string) => void;\n}) {\n const id = useId();\n const area = useRef<HTMLTextAreaElement>(null);\n const blocked = composerBlock(c, kind, texts);\n\n useLayoutEffect(() => {\n const el = area.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 160)}px`;\n }, [draft]);\n\n if (blocked) {\n return (\n <p data-testid=\"chat-composer-blocked\" className=\"border-t border-neutral-200 px-4 py-3 text-sm text-neutral-600 dark:border-neutral-800 dark:text-neutral-400\">\n {blocked}\n </p>\n );\n }\n\n const submit = (event?: FormEvent) => {\n event?.preventDefault();\n const text = draft.trim();\n if (!text) return;\n setDraft(\"\");\n void c.send(text);\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === \"Enter\" && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n submit();\n }\n };\n\n return (\n <form onSubmit={submit} className=\"border-t border-neutral-200 px-3 pb-3 pt-2 dark:border-neutral-800\">\n {c.messages.length === 0 && (\n <p className=\"pb-2 text-xs text-neutral-500 dark:text-neutral-400\">{texts.privacyNote}</p>\n )}\n <div className=\"flex items-end gap-2\">\n <label htmlFor={id} className=\"sr-only\">\n {texts.composerLabel}\n </label>\n <textarea\n id={id}\n ref={area}\n rows={1}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n placeholder={texts.composerPlaceholder}\n className=\"max-h-40 min-h-11 flex-1 resize-none rounded-xl border border-neutral-300 bg-white px-3 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50 dark:placeholder:text-neutral-400\"\n />\n <button\n type=\"submit\"\n aria-label={texts.send}\n disabled={draft.trim().length === 0}\n className={cn(primaryButton, \"w-11 px-0\")}\n >\n <SendHorizontal className=\"size-5\" aria-hidden=\"true\" />\n </button>\n </div>\n </form>\n );\n}\n\nconst REASONS: ChatReportReason[] = [\"spam\", \"abuse\", \"illegal\", \"other\"];\n\nfunction ReportForm({\n texts,\n messageId,\n onCancel,\n onSubmit,\n}: {\n texts: ChatTexts;\n messageId?: string;\n onCancel: () => void;\n onSubmit: (reason: ChatReportReason, note: string) => Promise<void>;\n}) {\n const [reason, setReason] = useState<ChatReportReason>(\"spam\");\n const [note, setNote] = useState(\"\");\n const [sending, setSending] = useState(false);\n const [failed, setFailed] = useState(false);\n const noteId = useId();\n const label: Record<ChatReportReason, string> = {\n spam: texts.reasonSpam,\n abuse: texts.reasonAbuse,\n illegal: texts.reasonIllegal,\n other: texts.reasonOther,\n };\n return (\n <form\n data-testid=\"chat-report\"\n className=\"flex flex-col gap-3 border-t border-neutral-200 p-4 dark:border-neutral-800\"\n onSubmit={async (event) => {\n event.preventDefault();\n setSending(true);\n setFailed(false);\n try {\n await onSubmit(reason, note.trim());\n } catch {\n setFailed(true);\n } finally {\n setSending(false);\n }\n }}\n >\n <p className=\"text-sm font-semibold\">\n {messageId ? texts.reportTitle : texts.reportConversationTitle}\n </p>\n <fieldset className=\"flex flex-col gap-1\">\n <legend className=\"mb-1 text-xs text-neutral-600 dark:text-neutral-400\">{texts.reportReason}</legend>\n {REASONS.map((value) => (\n <label key={value} className=\"flex min-h-9 items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"chat-report-reason\"\n value={value}\n checked={reason === value}\n onChange={() => setReason(value)}\n className=\"size-4 accent-neutral-900 dark:accent-white\"\n />\n {label[value]}\n </label>\n ))}\n </fieldset>\n <div className=\"flex flex-col gap-1\">\n <label htmlFor={noteId} className=\"text-xs text-neutral-600 dark:text-neutral-400\">\n {texts.reportNote}\n </label>\n <textarea\n id={noteId}\n rows={2}\n maxLength={1000}\n value={note}\n onChange={(event) => setNote(event.target.value)}\n className=\"resize-none rounded-xl border border-neutral-300 bg-white px-3 py-2 text-sm focus-visible:border-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-950\"\n />\n </div>\n {failed && (\n <p role=\"alert\" className=\"text-sm text-red-700 dark:text-red-400\">\n {texts.reportError}\n </p>\n )}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <button type=\"button\" className={quietButton} onClick={onCancel}>\n {texts.reportCancel}\n </button>\n <button type=\"submit\" className={primaryButton} disabled={sending} aria-busy={sending || undefined}>\n {texts.reportSend}\n </button>\n </div>\n </form>\n );\n}\n\nfunction chatBuyError(error: unknown, locale: string, fallback: string): string {\n const code = error instanceof Error ? error.message : \"\";\n const en = locale.toLowerCase().startsWith(\"en\");\n const messages: Record<string, [string, string]> = {\n \"amount-below-minimum\": [\"Kwota jest niższa od minimalnej.\", \"The amount is below the minimum.\"],\n \"amount-above-maximum\": [\"Kwota przekracza maksymalną.\", \"The amount is above the maximum.\"],\n \"amount-below-gateway-minimum\": [\"Kwota jest za niska dla dostępnych płatności.\", \"The amount is too low for available payment methods.\"],\n \"amount-invalid\": [\"Podaj prawidłową kwotę.\", \"Enter a valid amount.\"],\n \"email-verification-required\": [\"Potwierdź adres e-mail, aby odebrać pakiet.\", \"Verify your email to claim this pack.\"],\n \"free-claim-exists\": [\"Ten adres e-mail wykorzystał już limit darmowych odbiorów.\", \"This email has reached the free claim limit.\"],\n \"free-order-not-supported\": [\"Nie można zrealizować tego darmowego zamówienia.\", \"This free order cannot be completed.\"],\n \"captcha-required\": [\"Potwierdź, że nie jesteś robotem.\", \"Complete the security check.\"],\n \"captcha-invalid\": [\"Weryfikacja wygasła. Spróbuj ponownie.\", \"The security check expired. Try again.\"],\n \"rate-limited\": [\"Zbyt wiele prób. Spróbuj ponownie za chwilę.\", \"Too many attempts. Try again shortly.\"],\n \"incorrect-code\": [\"Nieprawidłowy kod. Sprawdź wiadomość i spróbuj ponownie.\", \"The code is incorrect. Check your email and try again.\"],\n \"too-many-attempts\": [\"Poproś o nowy kod za kilka minut.\", \"Request a new code in a few minutes.\"],\n unavailable: [\"Weryfikacja jest chwilowo niedostępna.\", \"Verification is temporarily unavailable.\"],\n };\n return messages[code]?.[en ? 1 : 0] ?? fallback;\n}\n","import { getLocale } from \"../components/checkout/locale\";\nimport { resolveTexts, type LocaleTables } from \"../lib/texts\";\n\n/**\n * Every string of the chat (panel \"Wiadomości\", `ChatThread`, `Channel`).\n * Pass a partial object through the `texts` prop to override any of them,\n * whatever the locale. Placeholders like `{count}` stay in overrides.\n *\n * Counts use three forms (`One`, `Few`, `Many`) for Polish; English uses\n * `One` and `Many`.\n */\nexport type ChatTexts = {\n inboxHeading: string;\n inboxIntro: string;\n inboxEmpty: string;\n inboxError: string;\n loading: string;\n retry: string;\n back: string;\n selectConversation: string;\n kindChat: string;\n kindChannel: string;\n startSection: string;\n startChat: string;\n openChannel: string;\n channelNotReady: string;\n chatFallbackTitle: string;\n channelFallbackTitle: string;\n /** Placeholder `{count}`. */\n repliesLeftOne: string;\n repliesLeftFew: string;\n repliesLeftMany: string;\n noRepliesLeft: string;\n /** Placeholder `{date}`. */\n expiresOn: string;\n /** Placeholder `{date}`. */\n expiredOn: string;\n /** Placeholder `{hours}`. */\n responseHours: string;\n /** Placeholder `{count}`. */\n unread: string;\n /** Placeholder `{count}`. */\n members: string;\n mutedBadge: string;\n noMessagesYet: string;\n emptyThread: string;\n emptyChannel: string;\n privacyNote: string;\n loadOlder: string;\n you: string;\n creator: string;\n systemLabel: string;\n pinned: string;\n deletedPlaceholder: string;\n attachment: string;\n composerLabel: string;\n composerPlaceholder: string;\n send: string;\n composerExpired: string;\n composerCreatorOnly: string;\n composerMuted: string;\n composerReadOnly: string;\n lockRepliesTitle: string;\n lockRepliesBody: string;\n lockExpiredTitle: string;\n lockExpiredBody: string;\n lockThreadTitle: string;\n lockThreadBody: string;\n lockChannelTitle: string;\n lockChannelBody: string;\n buyPack: string;\n buyAccess: string;\n buying: string;\n buyError: string;\n pending: string;\n notConfirmed: string;\n rejectedLink: string;\n rejectedBannedWord: string;\n rejectedMedia: string;\n /** Placeholder `{seconds}`. */\n rejectedCooldown: string;\n rejectedBlocked: string;\n rejectedMuted: string;\n rejectedReadOnly: string;\n rejectedPackEmpty: string;\n rejectedForbidden: string;\n rejectedGeneric: string;\n failedNetwork: string;\n editMessage: string;\n retrySend: string;\n dismiss: string;\n messageActions: string;\n deleteMessage: string;\n deleteConfirm: string;\n reportMessage: string;\n conversationActions: string;\n muteThread: string;\n unmuteThread: string;\n muteError: string;\n reportConversation: string;\n reportTitle: string;\n reportConversationTitle: string;\n reportReason: string;\n reasonSpam: string;\n reasonAbuse: string;\n reasonIllegal: string;\n reasonOther: string;\n reportNote: string;\n reportSend: string;\n reportCancel: string;\n reportSent: string;\n reportError: string;\n liveLabel: string;\n pollingLabel: string;\n errorNoToken: string;\n errorUnauthorized: string;\n errorPlan: string;\n errorNotFound: string;\n errorUnavailable: string;\n errorGeneric: string;\n};\n\nexport const CHAT_TEXT_TABLES: LocaleTables<keyof ChatTexts> = {\n pl: {\n inboxHeading: \"Wiadomości\",\n inboxIntro: \"Rozmowy z twórcami i kanały, do których należysz.\",\n inboxEmpty:\n \"Nie masz jeszcze rozmów. Po zakupie pakietu konsultacji lub dostępu do społeczności rozmowa pojawi się tutaj.\",\n inboxError: \"Nie udało się wczytać wiadomości.\",\n loading: \"Wczytywanie…\",\n retry: \"Spróbuj ponownie\",\n back: \"Wróć\",\n selectConversation: \"Wybierz rozmowę z listy.\",\n kindChat: \"Czat\",\n kindChannel: \"Kanał\",\n startSection: \"Do rozpoczęcia\",\n startChat: \"Rozpocznij rozmowę\",\n openChannel: \"Otwórz kanał\",\n channelNotReady: \"Twórca jeszcze nie otworzył tego kanału.\",\n chatFallbackTitle: \"Czat z twórcą\",\n channelFallbackTitle: \"Kanał\",\n repliesLeftOne: \"Została {count} odpowiedź\",\n repliesLeftFew: \"Zostały {count} odpowiedzi\",\n repliesLeftMany: \"Zostało {count} odpowiedzi\",\n noRepliesLeft: \"Brak odpowiedzi w pakiecie\",\n expiresOn: \"Ważny do {date}\",\n expiredOn: \"Wygasł {date}\",\n responseHours: \"Odpowiedź w ciągu {hours} h\",\n unread: \"Nieprzeczytane: {count}\",\n members: \"Członkowie: {count}\",\n mutedBadge: \"Wyciszony\",\n noMessagesYet: \"Brak wiadomości\",\n emptyThread:\n \"Napisz pierwszą wiadomość. Twórca odpowie w ramach Twojego pakietu.\",\n emptyChannel: \"W kanale nie ma jeszcze wiadomości.\",\n privacyNote:\n \"Wiadomości czyta druga strona rozmowy. Bitsnap przechowuje je przez 90 dni.\",\n loadOlder: \"Wcześniejsze wiadomości\",\n you: \"Ty\",\n creator: \"Twórca\",\n systemLabel: \"Wiadomość automatyczna\",\n pinned: \"Przypięta\",\n deletedPlaceholder: \"Wiadomość została usunięta\",\n attachment: \"Załącznik\",\n composerLabel: \"Wiadomość\",\n composerPlaceholder: \"Napisz wiadomość…\",\n send: \"Wyślij\",\n composerExpired: \"Pakiet wygasł, więc nie możesz już pisać w tej rozmowie.\",\n composerCreatorOnly: \"W tym kanale pisze tylko twórca.\",\n composerMuted: \"Masz wyciszone pisanie w tym kanale.\",\n composerReadOnly: \"Ten dostęp pozwala tylko czytać.\",\n lockRepliesTitle: \"Odpowiedzi w pakiecie się skończyły\",\n lockRepliesBody:\n \"Możesz dalej pisać, ale twórca odpowie dopiero po zakupie kolejnego pakietu.\",\n lockExpiredTitle: \"Pakiet wygasł\",\n lockExpiredBody:\n \"Rozmowa zostaje do wglądu. Kup nowy pakiet, aby ją kontynuować.\",\n lockThreadTitle: \"Nie masz pakietu odpowiedzi\",\n lockThreadBody: \"Kup pakiet, aby napisać do twórcy.\",\n lockChannelTitle: \"Kanał dla członków\",\n lockChannelBody: \"Kup dostęp, aby czytać i pisać w tym kanale.\",\n buyPack: \"Kup pakiet\",\n buyAccess: \"Kup dostęp\",\n buying: \"Dodawanie do koszyka…\",\n buyError: \"Nie udało się dodać do koszyka. Spróbuj ponownie.\",\n pending: \"Wysyłanie…\",\n notConfirmed: \"Czekamy na potwierdzenie wysłania\",\n rejectedLink: \"Linki są w tym kanale zablokowane. Usuń link i wyślij ponownie.\",\n rejectedBannedWord:\n \"Wiadomość zawiera słowo zablokowane w tym kanale. Popraw ją i wyślij ponownie.\",\n rejectedMedia: \"Załączniki są w tym kanale zablokowane.\",\n rejectedCooldown: \"Piszesz za szybko. Spróbuj ponownie za {seconds} s.\",\n rejectedBlocked: \"Twórca zablokował Ci możliwość pisania w tym sklepie.\",\n rejectedMuted: \"Masz wyciszone pisanie w tym kanale.\",\n rejectedReadOnly: \"Ten dostęp pozwala tylko czytać.\",\n rejectedPackEmpty: \"Pakiet nie ma już odpowiedzi.\",\n rejectedForbidden: \"Nie możesz pisać w tej rozmowie.\",\n rejectedGeneric: \"Nie udało się wysłać wiadomości.\",\n failedNetwork: \"Brak połączenia. Wiadomość nie została wysłana.\",\n editMessage: \"Popraw\",\n retrySend: \"Wyślij ponownie\",\n dismiss: \"Odrzuć\",\n messageActions: \"Opcje wiadomości\",\n deleteMessage: \"Usuń wiadomość\",\n deleteConfirm: \"Na pewno usunąć?\",\n reportMessage: \"Zgłoś wiadomość\",\n conversationActions: \"Opcje rozmowy\",\n muteThread: \"Wycisz powiadomienia\",\n unmuteThread: \"Włącz powiadomienia\",\n muteError: \"Nie udało się zmienić powiadomień.\",\n reportConversation: \"Zgłoś rozmowę\",\n reportTitle: \"Zgłoś wiadomość\",\n reportConversationTitle: \"Zgłoś rozmowę\",\n reportReason: \"Powód zgłoszenia\",\n reasonSpam: \"Spam\",\n reasonAbuse: \"Nękanie lub obraźliwe treści\",\n reasonIllegal: \"Treści niezgodne z prawem\",\n reasonOther: \"Inny powód\",\n reportNote: \"Opis (opcjonalnie)\",\n reportSend: \"Wyślij zgłoszenie\",\n reportCancel: \"Anuluj\",\n reportSent: \"Dziękujemy. Zgłoszenie trafiło do moderacji.\",\n reportError: \"Nie udało się wysłać zgłoszenia. Spróbuj ponownie.\",\n liveLabel: \"Nowe wiadomości pojawiają się na bieżąco\",\n pollingLabel: \"Nowe wiadomości odświeżają się co kilka sekund\",\n errorNoToken: \"Zaloguj się w panelu klienta, aby zobaczyć rozmowę.\",\n errorUnauthorized: \"Sesja wygasła. Zaloguj się ponownie.\",\n errorPlan: \"Czat nie jest dostępny w tym sklepie.\",\n errorNotFound: \"Nie znaleziono tej rozmowy.\",\n errorUnavailable: \"Czat jest chwilowo niedostępny. Spróbuj za chwilę.\",\n errorGeneric: \"Nie udało się wczytać rozmowy.\",\n },\n en: {\n inboxHeading: \"Messages\",\n inboxIntro: \"Your conversations with creators and the channels you belong to.\",\n inboxEmpty:\n \"No conversations yet. When you buy a consultation pack or community access, it shows up here.\",\n inboxError: \"Your messages could not be loaded.\",\n loading: \"Loading…\",\n retry: \"Try again\",\n back: \"Back\",\n selectConversation: \"Pick a conversation from the list.\",\n kindChat: \"Chat\",\n kindChannel: \"Channel\",\n startSection: \"Ready to start\",\n startChat: \"Start the conversation\",\n openChannel: \"Open the channel\",\n channelNotReady: \"The creator has not opened this channel yet.\",\n chatFallbackTitle: \"Chat with the creator\",\n channelFallbackTitle: \"Channel\",\n repliesLeftOne: \"{count} reply left\",\n repliesLeftFew: \"{count} replies left\",\n repliesLeftMany: \"{count} replies left\",\n noRepliesLeft: \"No replies left in your pack\",\n expiresOn: \"Valid until {date}\",\n expiredOn: \"Expired on {date}\",\n responseHours: \"Replies within {hours} h\",\n unread: \"Unread: {count}\",\n members: \"Members: {count}\",\n mutedBadge: \"Muted\",\n noMessagesYet: \"No messages yet\",\n emptyThread: \"Write the first message. The creator replies within your pack.\",\n emptyChannel: \"There are no messages in this channel yet.\",\n privacyNote:\n \"The other side of the conversation reads your messages. Bitsnap keeps them for 90 days.\",\n loadOlder: \"Earlier messages\",\n you: \"You\",\n creator: \"Creator\",\n systemLabel: \"Automatic message\",\n pinned: \"Pinned\",\n deletedPlaceholder: \"This message was deleted\",\n attachment: \"Attachment\",\n composerLabel: \"Message\",\n composerPlaceholder: \"Write a message…\",\n send: \"Send\",\n composerExpired: \"Your pack has expired, so you can no longer write here.\",\n composerCreatorOnly: \"Only the creator posts in this channel.\",\n composerMuted: \"You are muted in this channel.\",\n composerReadOnly: \"This access is read only.\",\n lockRepliesTitle: \"Your pack has no replies left\",\n lockRepliesBody:\n \"You can keep writing, but the creator can reply only after you buy another pack.\",\n lockExpiredTitle: \"Your pack has expired\",\n lockExpiredBody:\n \"You can still read the conversation. Buy a new pack to continue it.\",\n lockThreadTitle: \"You have no reply pack\",\n lockThreadBody: \"Buy a pack to write to the creator.\",\n lockChannelTitle: \"Members-only channel\",\n lockChannelBody: \"Buy access to read and post in this channel.\",\n buyPack: \"Buy a pack\",\n buyAccess: \"Buy access\",\n buying: \"Adding to cart…\",\n buyError: \"Could not add it to the cart. Try again.\",\n pending: \"Sending…\",\n notConfirmed: \"Waiting for the send to be confirmed\",\n rejectedLink: \"Links are blocked in this channel. Remove the link and send again.\",\n rejectedBannedWord:\n \"Your message contains a word blocked in this channel. Edit it and send again.\",\n rejectedMedia: \"Attachments are blocked in this channel.\",\n rejectedCooldown: \"You are posting too fast. Try again in {seconds} s.\",\n rejectedBlocked: \"The creator has blocked you from posting in this shop.\",\n rejectedMuted: \"You are muted in this channel.\",\n rejectedReadOnly: \"This access is read only.\",\n rejectedPackEmpty: \"The pack has no replies left.\",\n rejectedForbidden: \"You cannot post in this conversation.\",\n rejectedGeneric: \"Your message could not be sent.\",\n failedNetwork: \"No connection. Your message was not sent.\",\n editMessage: \"Edit\",\n retrySend: \"Send again\",\n dismiss: \"Discard\",\n messageActions: \"Message options\",\n deleteMessage: \"Delete message\",\n deleteConfirm: \"Delete for sure?\",\n reportMessage: \"Report message\",\n conversationActions: \"Conversation options\",\n muteThread: \"Mute notifications\",\n unmuteThread: \"Unmute notifications\",\n muteError: \"Notifications could not be changed.\",\n reportConversation: \"Report conversation\",\n reportTitle: \"Report message\",\n reportConversationTitle: \"Report conversation\",\n reportReason: \"Reason\",\n reasonSpam: \"Spam\",\n reasonAbuse: \"Harassment or abusive content\",\n reasonIllegal: \"Illegal content\",\n reasonOther: \"Something else\",\n reportNote: \"Details (optional)\",\n reportSend: \"Send report\",\n reportCancel: \"Cancel\",\n reportSent: \"Thank you. Your report went to moderation.\",\n reportError: \"Your report could not be sent. Try again.\",\n liveLabel: \"New messages appear live\",\n pollingLabel: \"New messages refresh every few seconds\",\n errorNoToken: \"Log in to the customer panel to see this conversation.\",\n errorUnauthorized: \"Your session has expired. Log in again.\",\n errorPlan: \"Chat is not available in this shop.\",\n errorNotFound: \"This conversation was not found.\",\n errorUnavailable: \"Chat is unavailable right now. Try again in a moment.\",\n errorGeneric: \"The conversation could not be loaded.\",\n },\n};\n\n/** Chat strings for a locale (default: the library locale), with overrides. */\nexport function getChatTexts(\n overrides?: Partial<ChatTexts>,\n locale: string = getLocale(),\n): ChatTexts {\n return resolveTexts(CHAT_TEXT_TABLES, locale, overrides);\n}\n\n/** Fills `{name}` placeholders. */\nexport function fill(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\n/** \"Zostały 3 odpowiedzi\" with the plural form of the locale. */\nexport function repliesLeftText(\n texts: ChatTexts,\n count: number,\n locale: string = getLocale(),\n): string {\n if (count <= 0) return texts.noRepliesLeft;\n let category: Intl.LDMLPluralRule = \"other\";\n try {\n category = new Intl.PluralRules(locale).select(count);\n } catch {\n // Unknown locale: \"other\".\n }\n const template =\n category === \"one\"\n ? texts.repliesLeftOne\n : category === \"few\"\n ? texts.repliesLeftFew\n : texts.repliesLeftMany;\n return fill(template, { count });\n}\n\n/** A short local date, `12.12.2026` / `12/12/2026`. */\nexport function formatChatDate(iso: string, locale: string = getLocale()): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(locale, {\n day: \"numeric\",\n month: \"numeric\",\n year: \"numeric\",\n }).format(date);\n}\n\n/** The time for today's messages, the date otherwise. */\nexport function formatMessageTime(\n iso: string,\n locale: string = getLocale(),\n now: Date = new Date(),\n): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n const sameDay = date.toDateString() === now.toDateString();\n return new Intl.DateTimeFormat(\n locale,\n sameDay\n ? { hour: \"2-digit\", minute: \"2-digit\" }\n : { day: \"numeric\", month: \"short\", hour: \"2-digit\", minute: \"2-digit\" },\n ).format(date);\n}\n\n/** The text of a refused send, by the error code or live reject reason. */\nexport function rejectionText(\n texts: ChatTexts,\n reason: string,\n retryAfter?: number,\n): string {\n switch (reason) {\n case \"moderation_link\":\n return texts.rejectedLink;\n case \"moderation_banned_word\":\n return texts.rejectedBannedWord;\n case \"moderation_media\":\n return texts.rejectedMedia;\n case \"cooldown\":\n return fill(texts.rejectedCooldown, { seconds: retryAfter ?? 30 });\n case \"blocked\":\n return texts.rejectedBlocked;\n case \"muted\":\n return texts.rejectedMuted;\n case \"read_only\":\n return texts.rejectedReadOnly;\n case \"pack_empty\":\n case \"replies_exhausted\":\n return texts.rejectedPackEmpty;\n case \"forbidden\":\n case \"locked\":\n return texts.rejectedForbidden;\n case \"network\":\n case \"unavailable\":\n return texts.failedNetwork;\n default:\n return texts.rejectedGeneric;\n }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { ChatError, type ChatClient } from \"./client\";\nimport { startLive, type NatsModule } from \"./realtime\";\nimport type {\n ChatChannelSummary,\n ChatLiveMode,\n ChatMessage,\n ChatRealtimeEvent,\n ChatReportReason,\n ChatThreadSummary,\n} from \"./types\";\n\n/** What to open: a pack thread (by product or id) or a channel. */\nexport type ConversationTarget =\n | { kind: \"thread\"; threadId?: string; productId?: string }\n | { kind: \"channel\"; channelId: string; productId?: string };\n\n/** The client-side state of a message the service has not stored. */\nexport type LocalMessageState = {\n state: \"pending\" | \"unconfirmed\" | \"rejected\" | \"failed\";\n /** Error code or live reject reason. */\n reason?: string;\n retryAfter?: number;\n /** An earlier send of this message may have been queued: retries keep its id. */\n keepId?: boolean;\n};\n\nexport type ConversationMessage = ChatMessage & { local?: LocalMessageState };\n\n/**\n * Why the buyer sees the lock: `replies` (no creator replies left, writing\n * still works), `expired` (every pack expired), `no-pack` (no thread and no\n * pack for the product), `no-access` (a paid channel without a grant).\n */\nexport type ConversationLock = {\n reason: \"replies\" | \"expired\" | \"no-pack\" | \"no-access\";\n productId?: string;\n priceCents?: number;\n amount?: number;\n priceType?: \"one_time\" | \"pay_what_you_want\" | \"free\";\n minimumCents?: number;\n maximumCents?: number;\n freeClaimLimit?: number;\n welcome?: string;\n};\n\nexport type ConversationState = {\n status: \"loading\" | \"ready\" | \"locked\" | \"error\";\n error?: ChatError;\n threadId?: string;\n /** The caller's e-mail, lowercased. */\n me?: string;\n thread?: ChatThreadSummary;\n channel?: ChatChannelSummary;\n lock?: ConversationLock;\n /** Oldest first. */\n messages: ConversationMessage[];\n hasMore: boolean;\n loadingOlder: boolean;\n live: ChatLiveMode;\n};\n\nexport type ConversationActions = {\n send(body: string): Promise<void>;\n /** Sends a failed or refused message again. */\n retry(id: string): Promise<void>;\n /** Removes a message that was not stored. */\n discard(id: string): void;\n deleteMessage(id: string): Promise<void>;\n report(input: {\n messageId?: string;\n reason: ChatReportReason;\n note?: string;\n }): Promise<void>;\n setMuted(muted: boolean): Promise<void>;\n loadOlder(): Promise<void>;\n reload(): void;\n};\n\nexport type UseConversationOptions = {\n /**\n * `undefined` when there is no credential (the state is an error), `null`\n * while the credential is still being looked up (the state stays loading).\n */\n client: ChatClient | null | undefined;\n target: ConversationTarget;\n pollIntervalMs?: number;\n realtimeUrl?: string;\n loadNats?: () => Promise<NatsModule>;\n /** A pending send counts as unconfirmed after this long. Default 45 s. */\n confirmTimeoutMs?: number;\n};\n\nconst PAGE = 50;\n\nconst initial: ConversationState = {\n status: \"loading\",\n messages: [],\n hasMore: false,\n loadingOlder: false,\n live: \"off\",\n};\n\n/** A UUID v4; the service uses it as the message id, so a retry is stored once. */\nexport function newMessageId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n const bytes = new Uint8Array(16);\n if (c && typeof c.getRandomValues === \"function\") c.getRandomValues(bytes);\n else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Merges messages by id, oldest first. A stored message replaces a pending\n * copy of itself (the live `message.created` or a history page after a 202);\n * messages that were refused locally stay as they are.\n */\nexport function mergeMessages(\n current: ConversationMessage[],\n incoming: ChatMessage[],\n): ConversationMessage[] {\n const byId = new Map(current.map((m) => [m.id, m] as const));\n for (const message of incoming) {\n const existing = byId.get(message.id);\n if (message.status === \"pending\") {\n // A pending answer never overwrites a stored copy.\n if (existing && !existing.local) continue;\n byId.set(message.id, { ...message, local: existing?.local ?? { state: \"pending\" } });\n continue;\n }\n if (existing?.local && existing.local.state !== \"pending\" && existing.local.state !== \"unconfirmed\") {\n continue;\n }\n byId.set(message.id, { ...message, local: undefined });\n }\n return [...byId.values()].sort((a, b) => {\n const at = Date.parse(a.createdAt) || 0;\n const bt = Date.parse(b.createdAt) || 0;\n return at - bt;\n });\n}\n\n/** The lock of a pack thread that the buyer can read. */\nexport function threadLock(thread: ChatThreadSummary): ConversationLock | undefined {\n if (thread.kind !== \"pack\") return undefined;\n if (thread.composerLocked) return { reason: \"expired\", productId: thread.productId };\n if (thread.repliesLeft <= 0) return { reason: \"replies\", productId: thread.productId };\n return undefined;\n}\n\nfunction lockedFrom(error: ChatError, reason: ConversationLock[\"reason\"], productId?: string): ConversationLock {\n const price = Number(error.data.price_cents);\n return {\n reason,\n productId: typeof error.data.product_id === \"string\" && error.data.product_id\n ? error.data.product_id\n : productId,\n priceCents: Number.isFinite(price) && price > 0 ? price : undefined,\n priceType: error.data.price_type === \"free\" || error.data.price_type === \"pay_what_you_want\" ? error.data.price_type : \"one_time\",\n minimumCents: Number.isFinite(Number(error.data.price_min_cents)) ? Number(error.data.price_min_cents) : undefined,\n maximumCents: Number.isFinite(Number(error.data.price_max_cents)) ? Number(error.data.price_max_cents) : undefined,\n freeClaimLimit: Number.isFinite(Number(error.data.free_claim_limit)) ? Number(error.data.free_claim_limit) : undefined,\n welcome: typeof error.data.welcome === \"string\" ? error.data.welcome : undefined,\n };\n}\n\nfunction asChatError(error: unknown): ChatError {\n return error instanceof ChatError ? error : new ChatError(\"internal_error\", 0);\n}\n\n/**\n * State and actions of one conversation: resolves the thread (opening a pack\n * thread when the buyer holds a pack and has none yet), loads the newest\n * page, listens live (or polls) and sends optimistically.\n */\nexport function useConversation(\n options: UseConversationOptions,\n): ConversationState & ConversationActions {\n const { client, target } = options;\n const [state, setState] = useState<ConversationState>(initial);\n const [generation, setGeneration] = useState(0);\n const stateRef = useRef(state);\n stateRef.current = state;\n const timers = useRef(new Set<ReturnType<typeof setTimeout>>());\n const refreshTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const lastRead = useRef<string>(undefined);\n\n const targetKey =\n target.kind === \"thread\"\n ? `t:${target.threadId ?? \"\"}:${target.productId ?? \"\"}`\n : `c:${target.channelId}:${target.productId ?? \"\"}`;\n\n // --- load -------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n lastRead.current = undefined;\n setState(initial);\n if (client === null) return;\n if (!client) {\n setState({ ...initial, status: \"error\", error: new ChatError(\"no_token\", 401) });\n return;\n }\n (async () => {\n try {\n const me = await client.me();\n const email = me.email.toLowerCase();\n let thread: ChatThreadSummary | undefined;\n let channel: ChatChannelSummary | undefined;\n let threadId: string;\n if (target.kind === \"thread\") {\n thread = target.threadId\n ? me.threads.find((t) => t.id === target.threadId)\n : me.threads.find((t) => t.productId === target.productId);\n if (!thread && target.productId) {\n try {\n thread = await client.openThread(target.productId);\n } catch (error) {\n const e = asChatError(error);\n if (e.code === \"locked\" || e.status === 404) {\n if (!cancelled)\n setState({\n ...initial,\n status: \"locked\",\n me: email,\n lock: lockedFrom(e, \"no-pack\", target.productId),\n });\n return;\n }\n throw e;\n }\n }\n if (!thread) throw new ChatError(\"not_found\", 404);\n threadId = thread.id;\n } else {\n channel = me.channels.find((c) => c.id === target.channelId);\n threadId = target.channelId;\n }\n\n let page;\n try {\n page = await client.messages(threadId, { limit: PAGE });\n } catch (error) {\n const e = asChatError(error);\n if (e.code === \"locked\") {\n if (!cancelled)\n setState({\n ...initial,\n status: \"locked\",\n me: email,\n thread,\n channel,\n threadId,\n lock: lockedFrom(\n e,\n target.kind === \"channel\" ? \"no-access\" : \"no-pack\",\n target.productId ?? channel?.productId ?? thread?.productId,\n ),\n });\n return;\n }\n throw e;\n }\n if (cancelled) return;\n setState({\n ...initial,\n status: \"ready\",\n me: email,\n thread,\n channel,\n threadId,\n lock: thread ? threadLock(thread) : undefined,\n messages: mergeMessages([], page.messages),\n hasMore: page.hasMore,\n });\n } catch (error) {\n if (!cancelled) setState({ ...initial, status: \"error\", error: asChatError(error) });\n }\n })();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, targetKey, generation]);\n\n // --- thread state refresh (replies left after a creator reply) --------\n const refreshThread = useCallback(() => {\n if (!client) return;\n if (refreshTimer.current) clearTimeout(refreshTimer.current);\n refreshTimer.current = setTimeout(async () => {\n try {\n const me = await client.me();\n setState((s) => {\n if (s.status !== \"ready\") return s;\n const thread = s.thread && me.threads.find((t) => t.id === s.thread!.id);\n const channel = s.channel && me.channels.find((c) => c.id === s.channel!.id);\n return {\n ...s,\n thread: thread ?? s.thread,\n channel: channel ?? s.channel,\n lock: thread ? threadLock(thread) : s.lock,\n };\n });\n } catch {\n // Keep the last state.\n }\n }, 1500);\n }, [client]);\n\n // --- live -------------------------------------------------------------\n const poll = useCallback(async () => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n const page = await client.messages(threadId, { limit: PAGE });\n setState((s) =>\n s.threadId === threadId ? { ...s, messages: mergeMessages(s.messages, page.messages) } : s,\n );\n }, [client]);\n\n const onEvent = useCallback(\n (event: ChatRealtimeEvent) => {\n setState((s) => {\n switch (event.type) {\n case \"message.created\":\n return { ...s, messages: mergeMessages(s.messages, [event.message]) };\n case \"message.rejected\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id\n ? { ...m, local: { state: \"rejected\", reason: event.reason || \"rejected\" } }\n : m,\n ),\n };\n case \"message.deleted\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id ? { ...m, deleted: true, body: \"\", attachmentKey: undefined } : m,\n ),\n };\n case \"message.pinned\":\n case \"message.unpinned\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id ? { ...m, pinned: event.type === \"message.pinned\" } : m,\n ),\n };\n }\n });\n if (\n event.type === \"message.created\" &&\n !event.message.system &&\n event.message.authorEmail.toLowerCase() !== stateRef.current.me\n ) {\n refreshThread();\n }\n },\n [refreshThread],\n );\n\n const ready = state.status === \"ready\";\n const liveThreadId = ready ? state.threadId : undefined;\n useEffect(() => {\n if (!client || !liveThreadId) return;\n const stop = startLive({\n client,\n kind: target.kind,\n id: liveThreadId,\n onEvent,\n onMode: (live) => setState((s) => ({ ...s, live })),\n poll,\n pollIntervalMs: options.pollIntervalMs,\n realtimeUrl: options.realtimeUrl,\n loadNats: options.loadNats,\n });\n return () => {\n stop();\n setState((s) => ({ ...s, live: \"off\" }));\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, liveThreadId, onEvent, poll]);\n\n // --- read receipt of the newest message from the other side -----------\n useEffect(() => {\n if (!client || !state.threadId || state.status !== \"ready\") return;\n const last = [...state.messages]\n .reverse()\n .find((m) => !m.local && m.authorEmail.toLowerCase() !== state.me);\n if (!last || lastRead.current === last.id) return;\n lastRead.current = last.id;\n void client.markRead(state.threadId, last.id).catch(() => undefined);\n }, [client, state.threadId, state.status, state.messages, state.me]);\n\n useEffect(\n () => () => {\n for (const timer of timers.current) clearTimeout(timer);\n if (refreshTimer.current) clearTimeout(refreshTimer.current);\n },\n [],\n );\n\n // --- actions ----------------------------------------------------------\n const updateMessage = (id: string, patch: (m: ConversationMessage) => ConversationMessage) =>\n setState((s) => ({ ...s, messages: s.messages.map((m) => (m.id === id ? patch(m) : m)) }));\n\n const send = useCallback(\n async (body: string, reuseId?: string) => {\n const text = body.trim();\n const { threadId, me } = stateRef.current;\n if (!client || !threadId || !text) return;\n const id = reuseId ?? newMessageId();\n const optimistic: ConversationMessage = {\n id,\n threadId,\n authorEmail: me ?? \"\",\n body: text,\n createdAt: new Date().toISOString(),\n pinned: false,\n deleted: false,\n system: false,\n status: \"pending\",\n local: { state: \"pending\" },\n };\n setState((s) => ({ ...s, messages: [...s.messages, optimistic] }));\n try {\n const result = await client.send(threadId, { body: text, clientId: id });\n setState((s) => ({\n ...s,\n messages: mergeMessages(s.messages, [{ ...result.message, id }]),\n thread: s.thread ? { ...s.thread, ...result.thread, id: s.thread.id } : s.thread,\n lock: s.thread ? threadLock({ ...s.thread, ...result.thread, id: s.thread.id }) : s.lock,\n }));\n const timer = setTimeout(() => {\n timers.current.delete(timer);\n updateMessage(id, (m) =>\n m.local?.state === \"pending\" ? { ...m, local: { state: \"unconfirmed\" } } : m,\n );\n }, options.confirmTimeoutMs ?? 45_000);\n timers.current.add(timer);\n } catch (error) {\n const e = asChatError(error);\n const failed = e.code === \"network\" || e.code === \"unavailable\" || e.status >= 500;\n updateMessage(id, (m) => ({\n ...m,\n local: {\n state: failed ? \"failed\" : \"rejected\",\n reason: e.code,\n retryAfter: e.retryAfter,\n keepId: failed || reuseId != null,\n },\n }));\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client, options.confirmTimeoutMs],\n );\n\n const discard = useCallback((id: string) => {\n setState((s) => ({ ...s, messages: s.messages.filter((m) => m.id !== id) }));\n }, []);\n\n const retry = useCallback(\n async (id: string) => {\n const message = stateRef.current.messages.find((m) => m.id === id);\n if (!message) return;\n // A send whose outcome is unknown (network error, 5xx, no\n // confirmation) may have been queued: it goes again under the same\n // id, so the service stores and bills it once, also after a later\n // attempt was refused. Only a send that was refused every time gets a\n // new id.\n const uncertain = message.local?.state !== \"rejected\" || message.local.keepId === true;\n discard(id);\n await send(message.body, uncertain ? id : undefined);\n },\n [discard, send],\n );\n\n const deleteMessage = useCallback(\n async (id: string) => {\n const { threadId } = stateRef.current;\n const before = stateRef.current.messages.find((m) => m.id === id);\n if (!client || !threadId || !before) return;\n updateMessage(id, (m) => ({ ...m, deleted: true, body: \"\", attachmentKey: undefined }));\n try {\n await client.deleteMessage(threadId, id);\n } catch (error) {\n updateMessage(id, () => before);\n throw error;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client],\n );\n\n const report = useCallback(\n async (input: { messageId?: string; reason: ChatReportReason; note?: string }) => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n await client.report({ threadId, ...input });\n },\n [client],\n );\n\n const setMuted = useCallback(\n async (muted: boolean) => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n await client.mute(threadId, muted);\n setState((s) => (s.thread ? { ...s, thread: { ...s.thread, muted } } : s));\n },\n [client],\n );\n\n const loadOlder = useCallback(async () => {\n const { threadId, messages, hasMore, loadingOlder } = stateRef.current;\n const oldest = messages.find((m) => !m.local);\n if (!client || !threadId || !hasMore || loadingOlder || !oldest) return;\n setState((s) => ({ ...s, loadingOlder: true }));\n try {\n const page = await client.messages(threadId, { before: oldest.id, limit: PAGE });\n setState((s) => ({\n ...s,\n loadingOlder: false,\n hasMore: page.hasMore,\n messages: mergeMessages(s.messages, page.messages),\n }));\n } catch {\n setState((s) => ({ ...s, loadingOlder: false }));\n }\n }, [client]);\n\n const reload = useCallback(() => setGeneration((g) => g + 1), []);\n\n return { ...state, send, retry, discard, deleteMessage, report, setMuted, loadOlder, reload };\n}\n","import { parseRealtimeEvent, type ChatClient } from \"./client\";\nimport {\n DEFAULT_CHAT_REALTIME_URL,\n type ChatLiveMode,\n type ChatRealtimeEvent,\n} from \"./types\";\n\n/** The part of `@nats-io/nats-core` the chat uses. */\nexport type NatsModule = {\n wsconnect(options: {\n servers: string[];\n authenticator: unknown;\n name?: string;\n reconnect?: boolean;\n maxReconnectAttempts?: number;\n }): Promise<NatsConnectionLike>;\n jwtAuthenticator(jwt: () => string): unknown;\n};\n\nexport type NatsConnectionLike = {\n subscribe(\n subject: string,\n options: { callback: (error: Error | null, msg: { data: Uint8Array }) => void },\n ): { unsubscribe(): void };\n closed(): Promise<void | Error>;\n close(): Promise<void>;\n};\n\n/**\n * The NATS client is loaded on the first open conversation, so pages and\n * shops without chat never download it.\n */\nexport const loadNatsCore = (): Promise<NatsModule> =>\n import(\"@nats-io/nats-core\") as unknown as Promise<NatsModule>;\n\nexport type LiveOptions = {\n client: ChatClient;\n /** `thread` for a pack thread, `channel` for a channel. */\n kind: \"thread\" | \"channel\";\n /** Thread id (a channel thread has the channel's id). */\n id: string;\n onEvent: (event: ChatRealtimeEvent) => void;\n onMode: (mode: ChatLiveMode) => void;\n /** Loads the newest page: the fallback, and the catch-up after a connect. */\n poll: () => Promise<void>;\n /** Polling interval while the socket is down. Default 8 s. */\n pollIntervalMs?: number;\n /** Delay before the socket is tried again after a failure. Default 60 s. */\n retryMs?: number;\n /** Overrides the WebSocket servers of the realtime token. */\n realtimeUrl?: string;\n /** Replaces the NATS loader (tests). */\n loadNats?: () => Promise<NatsModule>;\n};\n\n/**\n * Listens to one conversation: a realtime token from the chat service, then\n * a subscribe-only NATS WebSocket on the conversation's subject. When any\n * step fails (no WebSocket, token refused, subject not granted, socket\n * closed) it polls the newest page instead and tries the socket again later.\n * Returns the stop function.\n */\nexport function startLive(options: LiveOptions): () => void {\n const pollMs = options.pollIntervalMs ?? 8000;\n const retryMs = options.retryMs ?? 60_000;\n const load = options.loadNats ?? loadNatsCore;\n const suffix = `.${options.kind}.${options.id}`;\n\n let stopped = false;\n let connection: NatsConnectionLike | undefined;\n let jwt = \"\";\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let refreshTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stopPolling = () => {\n if (pollTimer != null) clearInterval(pollTimer);\n pollTimer = undefined;\n };\n\n const fallback = () => {\n if (stopped) return;\n options.onMode(\"polling\");\n if (pollTimer == null) {\n pollTimer = setInterval(() => {\n void options.poll().catch(() => undefined);\n }, pollMs);\n }\n if (retryTimer == null) {\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n void connect();\n }, retryMs);\n }\n };\n\n const scheduleRefresh = (expiresAt: string) => {\n if (refreshTimer != null) clearTimeout(refreshTimer);\n const at = Date.parse(expiresAt);\n if (!Number.isFinite(at)) return;\n // A new JWT a minute before the old one ends; the connection uses it on\n // its next (re)connect, which the server forces at the expiry.\n const delay = Math.max(5_000, at - Date.now() - 60_000);\n refreshTimer = setTimeout(async () => {\n if (stopped) return;\n try {\n const next = await options.client.realtimeToken({ fresh: true });\n jwt = next.jwt;\n scheduleRefresh(next.expiresAt);\n } catch {\n // The server closes the socket at the expiry: polling takes over.\n }\n }, delay);\n };\n\n async function connect() {\n if (stopped) return;\n if (typeof WebSocket === \"undefined\") {\n fallback();\n return;\n }\n options.onMode(\"connecting\");\n try {\n let token = await options.client.realtimeToken();\n let subject = token.subjects.find((s) => s.endsWith(suffix));\n if (!subject) {\n // A kept token may predate this conversation.\n token = await options.client.realtimeToken({ fresh: true });\n subject = token.subjects.find((s) => s.endsWith(suffix));\n }\n if (!subject || !token.jwt) throw new Error(\"subject not granted\");\n jwt = token.jwt;\n const nats = await load();\n if (stopped) return;\n const servers = options.realtimeUrl\n ? [options.realtimeUrl]\n : token.servers.length > 0\n ? token.servers\n : [DEFAULT_CHAT_REALTIME_URL];\n const nc = await nats.wsconnect({\n servers,\n authenticator: nats.jwtAuthenticator(() => jwt),\n name: \"bitsnap-react/chat\",\n reconnect: true,\n maxReconnectAttempts: 5,\n });\n if (stopped) {\n void nc.close().catch(() => undefined);\n return;\n }\n connection = nc;\n const decoder = new TextDecoder();\n nc.subscribe(subject, {\n callback: (error, msg) => {\n if (error) return;\n try {\n const event = parseRealtimeEvent(JSON.parse(decoder.decode(msg.data)));\n if (event) options.onEvent(event);\n } catch {\n // Not a chat event.\n }\n },\n });\n void nc.closed().then(() => {\n if (connection === nc) {\n connection = undefined;\n fallback();\n }\n });\n scheduleRefresh(token.expiresAt);\n stopPolling();\n options.onMode(\"live\");\n // Messages stored between the history load and the subscribe.\n void options.poll().catch(() => undefined);\n } catch {\n fallback();\n }\n }\n\n void connect();\n\n return () => {\n stopped = true;\n stopPolling();\n if (retryTimer != null) clearTimeout(retryTimer);\n if (refreshTimer != null) clearTimeout(refreshTimer);\n const nc = connection;\n connection = undefined;\n if (nc) void nc.close().catch(() => undefined);\n };\n}\n","import { Hash, MessageCircle } from \"lucide-react\";\nimport { lazy, Suspense, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { getLocale } from \"../components/checkout/locale\";\nimport {\n UserProductType,\n type UserProduct,\n} from \"../gen/proto/public/v1/public_api_pb\";\nimport { cn } from \"../lib/utils\";\nimport { addPackToCart } from \"./buy\";\nimport { ChatError, createChatClient, type ChatClient } from \"./client\";\nimport {\n fill,\n formatChatDate,\n formatMessageTime,\n getChatTexts,\n repliesLeftText,\n type ChatTexts,\n} from \"./texts\";\nimport type { ChatMe } from \"./types\";\nimport { Channel, ChatThread, type ChatBuyRequest } from \"./widgets\";\n\nconst BitsnapCheckout = lazy(() => import(\"../components/checkout/BitsnapCart\"));\n\n/** Which conversation the panel shows (kept in the URL). */\nexport type PanelMessagesSelection = {\n /** Chat project of the conversation. */\n chatProject?: string;\n thread?: string;\n channel?: string;\n};\n\nexport type PanelMessagesProps = {\n accessToken: string;\n /** Panel products: CHAT and COMMUNITY items name the conversations. */\n products: UserProduct[];\n /** `chat_api_url` of the products answer. */\n chatApiUrl?: string;\n /** Project of the panel, used when no chat product names one. */\n projectID?: string;\n host?: string;\n selection: PanelMessagesSelection;\n onSelect: (selection: PanelMessagesSelection) => void;\n locale?: string;\n texts?: Partial<ChatTexts>;\n};\n\ntype ProjectLoad =\n | { status: \"loading\" }\n | { status: \"ready\"; me: ChatMe }\n | { status: \"error\"; error: ChatError };\n\nconst isChatProduct = (product: UserProduct) =>\n product.productType === UserProductType.CHAT ||\n product.productType === UserProductType.COMMUNITY;\n\n/**\n * The projects whose chat the buyer may have: those of the CHAT and\n * COMMUNITY products, else the panel's project.\n *\n * `UserProduct.projectId` is the public (hashed) id the cart uses; the chat\n * service wants the numeric one (`X-Bitsnap-Project`), which Instapay sends as\n * `chatProjectId` on CHAT and COMMUNITY products (see `chatProjectIds`).\n */\nexport function chatProjects(products: UserProduct[], fallback?: string): string[] {\n const ids = [...new Set(products.filter(isChatProduct).map((p) => p.projectId).filter(Boolean))];\n if (ids.length > 0) return ids;\n return fallback ? [fallback] : [];\n}\n\n/** Public project id → numeric chat project id, from the chat products. */\nexport function chatProjectIds(products: UserProduct[]): Map<string, string> {\n const map = new Map<string, string>();\n for (const p of products) {\n if (isChatProduct(p) && p.projectId && p.chatProjectId) map.set(p.projectId, p.chatProjectId);\n }\n return map;\n}\n\n/** The \"Wiadomości\" screen of the customer panel. */\nexport default function PanelMessages({\n accessToken,\n products,\n chatApiUrl,\n projectID,\n host,\n selection,\n onSelect,\n locale = getLocale(),\n texts: overrides,\n}: PanelMessagesProps) {\n const texts = useMemo(() => getChatTexts(overrides, locale), [overrides, locale]);\n const projects = useMemo(() => chatProjects(products, projectID), [products, projectID]);\n const chatIds = useMemo(() => chatProjectIds(products), [products]);\n const clients = useMemo(() => {\n const map = new Map<string, ChatClient>();\n for (const id of projects)\n map.set(id, createChatClient({ projectId: chatIds.get(id) ?? id, token: accessToken, baseUrl: chatApiUrl }));\n return map;\n }, [projects, chatIds, accessToken, chatApiUrl]);\n\n const [loads, setLoads] = useState<Record<string, ProjectLoad>>({});\n const [generation, setGeneration] = useState(0);\n const selectedKey = `${selection.chatProject ?? \"\"}:${selection.thread ?? \"\"}:${selection.channel ?? \"\"}`;\n\n // Reloaded when the buyer leaves a conversation, so unread counts and\n // replies left are fresh.\n useEffect(() => {\n let cancelled = false;\n for (const [id, client] of clients) {\n setLoads((all) => (all[id]?.status === \"ready\" ? all : { ...all, [id]: { status: \"loading\" } }));\n client.me().then(\n (me) => !cancelled && setLoads((all) => ({ ...all, [id]: { status: \"ready\", me } })),\n (error) =>\n !cancelled &&\n setLoads((all) => ({\n ...all,\n [id]: {\n status: \"error\",\n error: error instanceof ChatError ? error : new ChatError(\"internal_error\", 0),\n },\n })),\n );\n }\n return () => {\n cancelled = true;\n };\n }, [clients, generation, selectedKey]);\n\n const productName = useMemo(() => {\n const map = new Map<string, UserProduct>();\n for (const product of products) map.set(product.productId, product);\n return map;\n }, [products]);\n\n const [checkoutProject, setCheckoutProject] = useState<string>();\n const buyFor = (project: string) => async (request: ChatBuyRequest) => {\n setCheckoutProject(project);\n await addPackToCart({\n projectID: project,\n productId: request.productId,\n name: productName.get(request.productId)?.productName,\n priceCents: request.priceCents,\n host,\n });\n };\n\n const selectedClient = selection.chatProject ? clients.get(selection.chatProject) : undefined;\n const hasSelection = Boolean(selectedClient && (selection.thread || selection.channel));\n const back = () => onSelect({});\n\n let conversation = null;\n if (hasSelection && selectedClient && selection.chatProject) {\n const project = selection.chatProject;\n const load = loads[project];\n const me = load?.status === \"ready\" ? load.me : undefined;\n if (selection.thread) {\n const thread = me?.threads.find((t) => t.id === selection.thread);\n conversation = (\n <ChatThread\n key={`t:${project}:${selection.thread}`}\n projectID={project}\n client={selectedClient}\n threadId={selection.thread}\n title={(thread?.productId && productName.get(thread.productId)?.productName) || undefined}\n locale={locale}\n texts={overrides}\n onBack={back}\n onBuy={buyFor(project)}\n />\n );\n } else if (selection.channel) {\n const entitlement = me?.entitlements.channels.find((c) => c.channelId === selection.channel);\n conversation = (\n <Channel\n key={`c:${project}:${selection.channel}`}\n projectID={project}\n client={selectedClient}\n channelId={selection.channel}\n productId={entitlement?.productId}\n locale={locale}\n texts={overrides}\n onBack={back}\n onBuy={buyFor(project)}\n />\n );\n }\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\">\n <div className={cn(hasSelection && \"hidden md:block\")}>\n <h3 className=\"text-2xl\">{texts.inboxHeading}</h3>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400\">{texts.inboxIntro}</p>\n </div>\n <div className=\"mt-6 grid min-h-0 flex-1 gap-4 md:grid-cols-[minmax(16rem,22rem)_1fr]\">\n <div className={cn(\"min-w-0\", hasSelection && \"hidden md:block\")}>\n <Inbox\n projects={projects}\n loads={loads}\n clients={clients}\n products={products}\n texts={texts}\n locale={locale}\n selection={selection}\n onSelect={onSelect}\n onRetry={() => setGeneration((g) => g + 1)}\n />\n </div>\n <div className={cn(\"min-h-[32rem] min-w-0 md:h-[calc(100dvh-10rem)]\", !hasSelection && \"hidden md:flex\")}>\n {conversation ?? (\n <div className=\"flex h-full w-full items-center justify-center rounded-xl border border-dashed border-neutral-300 p-6 text-sm text-neutral-600 dark:border-neutral-700 dark:text-neutral-400\">\n {texts.selectConversation}\n </div>\n )}\n </div>\n </div>\n {checkoutProject && (\n <Suspense fallback={null}>\n <BitsnapCheckout projectID={checkoutProject} locale={locale} />\n </Suspense>\n )}\n </div>\n );\n}\n\ntype InboxProps = {\n projects: string[];\n loads: Record<string, ProjectLoad>;\n clients: Map<string, ChatClient>;\n products: UserProduct[];\n texts: ChatTexts;\n locale: string;\n selection: PanelMessagesSelection;\n onSelect: (selection: PanelMessagesSelection) => void;\n onRetry: () => void;\n};\n\nconst rowClass =\n \"flex min-h-11 w-full min-w-0 items-start gap-3 rounded-xl p-3 text-left transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500\";\n\nfunction Inbox({ projects, loads, clients, products, texts, locale, selection, onSelect, onRetry }: InboxProps) {\n const [opening, setOpening] = useState<string>();\n const [openError, setOpenError] = useState<string>();\n const byProduct = new Map(products.map((p) => [p.productId, p] as const));\n const multiProject = projects.length > 1;\n const states = projects.map((id) => loads[id] ?? ({ status: \"loading\" } as const));\n\n if (states.length > 0 && states.every((s) => s.status === \"loading\")) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label={texts.loading} className=\"flex flex-col gap-2\">\n {[0, 1, 2].map((i) => (\n <div key={i} aria-hidden=\"true\" className=\"h-20 rounded-xl bg-white motion-safe:animate-pulse dark:bg-neutral-800\" />\n ))}\n </div>\n );\n }\n\n if (states.length === 0 || states.every((s) => s.status === \"error\")) {\n return (\n <div role=\"alert\" className=\"flex flex-col items-start gap-3\" data-testid=\"chat-inbox-error\">\n <p className=\"text-sm text-neutral-700 dark:text-neutral-300\">{texts.inboxError}</p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex min-h-11 items-center rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-current dark:bg-white dark:text-neutral-900\"\n >\n {texts.retry}\n </button>\n </div>\n );\n }\n\n const rows: ReactNode[] = [];\n const starts: ReactNode[] = [];\n for (const project of projects) {\n const load = loads[project];\n if (load?.status !== \"ready\") continue;\n const { me } = load;\n const projectName = multiProject\n ? products.find((p) => p.projectId === project)?.projectName\n : undefined;\n\n for (const thread of me.threads) {\n const product = thread.productId ? byProduct.get(thread.productId) : undefined;\n const selected = selection.chatProject === project && selection.thread === thread.id;\n const meta = [repliesLeftText(texts, thread.repliesLeft, locale)];\n if (thread.expiresAt)\n meta.push(\n fill(thread.composerLocked ? texts.expiredOn : texts.expiresOn, {\n date: formatChatDate(thread.expiresAt, locale),\n }),\n );\n if (thread.responseHours > 0) meta.push(fill(texts.responseHours, { hours: thread.responseHours }));\n const last = thread.lastMessage;\n rows.push(\n <li key={`t:${project}:${thread.id}`}>\n <button\n type=\"button\"\n data-thread-id={thread.id}\n aria-current={selected ? \"true\" : undefined}\n onClick={() => onSelect({ chatProject: project, thread: thread.id })}\n className={cn(rowClass, selected ? \"bg-neutral-200 dark:bg-neutral-800\" : \"bg-white hover:bg-neutral-50 dark:bg-neutral-900 dark:hover:bg-neutral-800\")}\n >\n <MessageCircle className=\"mt-0.5 size-5 shrink-0 text-neutral-500 dark:text-neutral-400\" aria-hidden=\"true\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"flex items-baseline gap-2\">\n <span className={cn(\"truncate\", thread.unread > 0 ? \"font-semibold\" : \"font-medium\")}>\n {product?.productName || texts.chatFallbackTitle}\n </span>\n {last && (\n <time dateTime={last.createdAt} className=\"ml-auto shrink-0 text-xs text-neutral-500 dark:text-neutral-400\">\n {formatMessageTime(last.createdAt, locale)}\n </time>\n )}\n </span>\n {projectName && <span className=\"truncate text-xs text-neutral-500 dark:text-neutral-400\">{projectName}</span>}\n <span className=\"flex items-center gap-2\">\n <span className=\"line-clamp-1 flex-1 text-sm text-neutral-600 dark:text-neutral-300\">\n {last ? (last.deleted ? texts.deletedPlaceholder : last.body) : texts.noMessagesYet}\n </span>\n {thread.unread > 0 && (\n <span\n aria-label={fill(texts.unread, { count: thread.unread })}\n className=\"inline-flex min-w-5 shrink-0 items-center justify-center rounded-full bg-neutral-900 px-1.5 text-xs font-semibold text-white tabular-nums dark:bg-white dark:text-neutral-900\"\n >\n {thread.unread}\n </span>\n )}\n </span>\n <span className={cn(\"text-xs\", thread.repliesLeft <= 0 || thread.composerLocked ? \"text-amber-800 dark:text-amber-300\" : \"text-neutral-500 dark:text-neutral-400\")}>\n {meta.join(\" · \")}\n {thread.muted && ` · ${texts.mutedBadge}`}\n </span>\n </span>\n </button>\n </li>,\n );\n }\n\n for (const channel of me.channels) {\n const selected = selection.chatProject === project && selection.channel === channel.id;\n rows.push(\n <li key={`c:${project}:${channel.id}`}>\n <button\n type=\"button\"\n data-channel-id={channel.id}\n aria-current={selected ? \"true\" : undefined}\n onClick={() => onSelect({ chatProject: project, channel: channel.id })}\n className={cn(rowClass, selected ? \"bg-neutral-200 dark:bg-neutral-800\" : \"bg-white hover:bg-neutral-50 dark:bg-neutral-900 dark:hover:bg-neutral-800\")}\n >\n <Hash className=\"mt-0.5 size-5 shrink-0 text-neutral-500 dark:text-neutral-400\" aria-hidden=\"true\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{channel.name || texts.channelFallbackTitle}</span>\n {projectName && <span className=\"truncate text-xs text-neutral-500 dark:text-neutral-400\">{projectName}</span>}\n <span className=\"line-clamp-1 text-sm text-neutral-600 dark:text-neutral-300\">\n {channel.topic || texts.kindChannel}\n </span>\n {channel.memberCount > 0 && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fill(texts.members, { count: channel.memberCount })}\n </span>\n )}\n </span>\n </button>\n </li>,\n );\n }\n\n for (const pack of me.entitlements.packs) {\n const key = `${project}:${pack.productId}`;\n const name = byProduct.get(pack.productId)?.productName || pack.name || texts.chatFallbackTitle;\n starts.push(\n <li key={`p:${key}`} className=\"flex flex-col gap-2 rounded-xl bg-white p-3 dark:bg-neutral-900 sm:flex-row sm:items-center\">\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{name}</span>\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {[\n repliesLeftText(texts, pack.repliesLeftHint, locale),\n pack.expiresAt ? fill(texts.expiresOn, { date: formatChatDate(pack.expiresAt, locale) }) : \"\",\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </span>\n <button\n type=\"button\"\n disabled={opening === key}\n aria-busy={opening === key || undefined}\n onClick={async () => {\n const client = clients.get(project);\n if (!client) return;\n setOpening(key);\n setOpenError(undefined);\n try {\n const thread = await client.openThread(pack.productId);\n onSelect({ chatProject: project, thread: thread.id });\n } catch {\n setOpenError(key);\n } finally {\n setOpening(undefined);\n }\n }}\n className=\"inline-flex min-h-11 shrink-0 items-center justify-center rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200\"\n >\n {opening === key ? texts.loading : texts.startChat}\n </button>\n {openError === key && (\n <p role=\"alert\" className=\"text-xs text-red-700 dark:text-red-400 sm:basis-full\">\n {texts.errorGeneric}\n </p>\n )}\n </li>,\n );\n }\n\n const listed = new Set(me.channels.map((c) => c.id));\n for (const entitlement of me.entitlements.channels) {\n if (entitlement.channelId && listed.has(entitlement.channelId)) continue;\n const name = byProduct.get(entitlement.productId)?.productName || texts.channelFallbackTitle;\n starts.push(\n <li key={`e:${project}:${entitlement.productId}`} className=\"flex flex-col gap-2 rounded-xl bg-white p-3 dark:bg-neutral-900 sm:flex-row sm:items-center\">\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{name}</span>\n {!entitlement.channelId && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">{texts.channelNotReady}</span>\n )}\n </span>\n {entitlement.channelId && (\n <button\n type=\"button\"\n onClick={() => onSelect({ chatProject: project, channel: entitlement.channelId })}\n className=\"inline-flex min-h-11 shrink-0 items-center justify-center rounded-md border border-neutral-300 px-4 text-sm font-medium hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n >\n {texts.openChannel}\n </button>\n )}\n </li>,\n );\n }\n }\n\n if (rows.length === 0 && starts.length === 0) {\n return (\n <p data-testid=\"chat-inbox-empty\" className=\"text-sm text-neutral-600 dark:text-neutral-400\">\n {texts.inboxEmpty}\n </p>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-6\" data-testid=\"chat-inbox\">\n {rows.length > 0 && <ul className=\"flex flex-col gap-2\">{rows}</ul>}\n {starts.length > 0 && (\n <section className=\"flex flex-col gap-2\">\n <h4 className=\"text-sm font-semibold text-neutral-700 dark:text-neutral-300\">{texts.startSection}</h4>\n <ul className=\"flex flex-col gap-2\">{starts}</ul>\n </section>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useMemo","useRef","useState","addPackToCart","input","addProductToCart","DEFAULT_CHAT_API_URL","DEFAULT_CHAT_REALTIME_URL","ChatError","code","status","data","retry","createChatClient","options","baseUrl","DEFAULT_CHAT_API_URL","doFetch","args","call","method","path","body","response","json","text","isRecord","id","cachedToken","mapMe","productId","mapThread","threadId","opts","query","qs","suffix","page","arr","mapMessage","input","res","mapThreadState","messageId","muted","str","s","parseRealtimeEvent","raw","eventId","value","num","n","optStr","time","m","mapPack","p","mapChannel","c","ent","AlertCircle","ArrowLeft","Clock","Lock","MoreHorizontal","Paperclip","Pin","SendHorizontal","useEffect","useId","useLayoutEffect","useRef","useState","CHAT_TEXT_TABLES","getChatTexts","overrides","locale","getLocale","resolveTexts","fill","template","values","match","key","repliesLeftText","texts","count","category","formatChatDate","iso","date","formatMessageTime","now","sameDay","rejectionText","reason","retryAfter","Fragment","jsx","jsxs","focusRing","quietButton","cn","primaryButton","ConversationView","host","c","texts","locale","kind","title","onBack","onBuy","className","menuOpen","setMenuOpen","useState","reporting","setReporting","notice","setNotice","draft","setDraft","reportSent","setReportSent","useEffect","timer","heading","meta","metaLine","ArrowLeft","LiveDot","item","open","MoreHorizontal","LoadingBody","ErrorBody","LockCard","MessageList","messageId","message","ReportForm","reason","note","Composer","items","repliesLeftText","date","formatChatDate","fill","live","label","width","index","errorText","code","canRetry","lock","prominent","buying","setBuying","failed","setFailed","amountText","setAmountText","normalized","amount","isPwyw","min","max","validAmount","isFreeClaim","email","setEmail","setCode","codeSent","setCodeSent","turnstileToken","setTurnstileToken","challengeAttempt","setChallengeAttempt","cooldown","setCooldown","codeInput","useRef","restart","n","english","body","action","canBuy","Lock","event","TurnstileChallenge","error","chatBuyError","authorLabel","mine","onReport","onEdit","scroller","stick","count","lastId","useLayoutEffect","el","MessageRow","onDelete","onRetry","onDiscard","actions","setActions","confirmDelete","setConfirmDelete","local","refused","waiting","canAct","Paperclip","Pin","Clock","formatMessageTime","AlertCircle","rejectionText","composerBlock","id","useId","area","blocked","submit","text","onKeyDown","SendHorizontal","REASONS","onCancel","onSubmit","setReason","setNote","sending","setSending","noteId","value","fallback","en","useCallback","useEffect","useRef","useState","loadNatsCore","startLive","options","pollMs","retryMs","load","suffix","stopped","connection","jwt","pollTimer","retryTimer","refreshTimer","stopPolling","fallback","connect","scheduleRefresh","expiresAt","at","delay","next","token","subject","s","nats","servers","DEFAULT_CHAT_REALTIME_URL","nc","decoder","error","msg","event","parseRealtimeEvent","PAGE","initial","newMessageId","c","bytes","i","hex","b","mergeMessages","current","incoming","byId","m","message","existing","a","at","bt","threadLock","thread","lockedFrom","error","reason","productId","price","asChatError","ChatError","useConversation","options","client","target","state","setState","useState","generation","setGeneration","stateRef","useRef","timers","refreshTimer","lastRead","targetKey","useEffect","cancelled","me","email","channel","threadId","t","e","page","refreshThread","useCallback","s","poll","onEvent","event","liveThreadId","stop","startLive","live","last","timer","updateMessage","id","patch","send","body","reuseId","text","optimistic","result","failed","discard","retry","uncertain","deleteMessage","before","report","input","setMuted","muted","loadOlder","messages","hasMore","loadingOlder","oldest","reload","g","jsx","useWidgetClient","props","stored","setStored","useState","host","getPanelConfig","useEffect","getPanelSession","token","projectId","useMemo","createChatClient","useBuy","claim","useRef","lock","productId","request","commerceBase","isFreeClaim","response","result","key","verify","verified","bought","addPackToCart","ChatThread","client","locale","getLocale","texts","getChatTexts","conversation","useConversation","buy","ConversationView","Channel","Hash","MessageCircle","lazy","Suspense","useEffect","useMemo","useState","jsx","jsxs","BitsnapCheckout","lazy","isChatProduct","product","chatProjects","products","fallback","ids","p","chatProjectIds","map","PanelMessages","accessToken","chatApiUrl","projectID","host","selection","onSelect","locale","getLocale","overrides","texts","useMemo","getChatTexts","projects","chatIds","clients","id","createChatClient","loads","setLoads","useState","generation","setGeneration","selectedKey","useEffect","cancelled","client","all","me","error","ChatError","productName","checkoutProject","setCheckoutProject","buyFor","project","request","addPackToCart","selectedClient","hasSelection","back","conversation","load","thread","t","ChatThread","entitlement","c","Channel","cn","Inbox","g","Suspense","rowClass","onRetry","opening","setOpening","openError","setOpenError","byProduct","multiProject","states","s","i","rows","starts","projectName","selected","meta","repliesLeftText","fill","formatChatDate","last","MessageCircle","formatMessageTime","channel","Hash","pack","key","name","listed"]}
|
|
1
|
+
{"version":3,"sources":["../src/chat/widgets.tsx","../src/chat/buy.ts","../src/chat/types.ts","../src/chat/client.ts","../src/chat/Conversation.tsx","../src/chat/texts.ts","../src/chat/useConversation.ts","../src/chat/realtime.ts","../src/chat/PanelMessages.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { getLocale } from \"../components/checkout/locale\";\nimport { getPanelConfig } from \"../panel/config\";\nimport { getPanelSession } from \"../panel/session-storage\";\nimport { addPackToCart } from \"./buy\";\nimport { createChatClient, type ChatClient } from \"./client\";\nimport { ConversationView } from \"./Conversation\";\nimport type { NatsModule } from \"./realtime\";\nimport { getChatTexts, type ChatTexts } from \"./texts\";\nimport { useConversation, type ConversationLock } from \"./useConversation\";\n\n/** What the lock's buy action gets. */\nexport type ChatBuyRequest = {\n /** The product (or variant) to sell: `buyProductId`, else the locked product. */\n productId: string;\n /** `replies`, `expired`, `no-pack` or `no-access`. */\n reason: ConversationLock[\"reason\"];\n /** Known for a locked channel (from the chat service). */\n priceCents?: number;\n amount?: number;\n email?: string;\n code?: string;\n turnstileToken?: string;\n};\n\n/** Props shared by `ChatThread` and `Channel`. */\nexport type ChatWidgetBaseProps = {\n /**\n * Public Bitsnap project id of the shop. The cart uses it, the panel\n * session is looked up with it, and it is the `X-Bitsnap-Project` of the\n * chat service unless `chatProjectID` is given.\n */\n projectID: string;\n /** Project id for the chat service, when it differs from `projectID`. */\n chatProjectID?: string;\n /**\n * Bearer for the chat service: a `bcht_` buyer token from your server\n * (`POST /api/v1/chat/buyer-token`). Defaults to the customer panel session\n * of this browser (after `PanelLogin`).\n */\n token?: string;\n /** Chat service base URL. Defaults to `https://chat.bitsnap.pl`. */\n apiUrl?: string;\n /** Bitsnap app host serving private chat attachment APIs (defaults to the page origin). */\n attachmentsUrl?: string;\n /** NATS WebSocket URL overriding the one of the realtime token. */\n realtimeUrl?: string;\n /** Bitsnap host of the panel session (`setHost`), when not the default. */\n host?: string;\n /** Polling interval in ms while the live connection is down. Default 8000. */\n pollIntervalMs?: number;\n /** Language of the default texts; defaults to the library locale. */\n locale?: string;\n /** Text overrides, whatever the locale. */\n texts?: Partial<ChatTexts>;\n /** Heading of the conversation. */\n title?: string;\n /** Shows a back button in the header. */\n onBack?: () => void;\n /**\n * The lock's \"Kup pakiet\" / \"Kup dostęp\". Default: add the product to the\n * page cart and open it (mount `BitsnapCheckout` on the page). Throw to\n * show the error line.\n */\n onBuy?: (request: ChatBuyRequest) => Promise<void> | void;\n /** The product or variant the buy action sells (e.g. the pack variant). */\n buyProductId?: string;\n className?: string;\n /** A ready client (tests, custom transport). Replaces token and URLs. */\n client?: ChatClient;\n /** @internal Replaces the NATS loader (tests). */\n loadNats?: () => Promise<NatsModule>;\n};\n\nexport type ChatThreadProps = ChatWidgetBaseProps & {\n /** The service product of the pack: opens its thread, creating it if needed. */\n productId?: string;\n /** An existing thread. Give this or `productId`. */\n threadId?: string;\n};\n\nexport type ChannelProps = ChatWidgetBaseProps & {\n /** The channel (its id is also the id of its conversation). */\n channelId: string;\n /** The community product sold by the lock; defaults to the channel's own. */\n productId?: string;\n};\n\n/**\n * The chat client of a widget. The panel session lives in localStorage, so\n * it is read after mounting (`null` until then: the widget stays loading and\n * the server HTML has no session-dependent content).\n */\nfunction useWidgetClient(props: ChatWidgetBaseProps): ChatClient | null | undefined {\n const [stored, setStored] = useState<string | undefined | null>(null);\n const host = props.host ?? getPanelConfig().host;\n useEffect(() => {\n if (props.client || props.token) return;\n setStored(getPanelSession(props.projectID, host));\n }, [props.client, props.token, props.projectID, host]);\n const token = props.token ?? stored;\n const projectId = props.chatProjectID ?? props.projectID;\n return useMemo(() => {\n if (props.client) return props.client;\n if (token === null) return null;\n if (!token) return undefined;\n return createChatClient({ projectId, token, baseUrl: props.apiUrl, attachmentsUrl: props.attachmentsUrl });\n }, [props.client, token, projectId, props.apiUrl, props.attachmentsUrl]);\n}\n\nfunction useBuy(props: ChatWidgetBaseProps) {\n const claim = useRef<{ key: string; token: string; expires: number } | null>(null);\n return async (lock: ConversationLock & { email?: string; code?: string; turnstileToken?: string }) => {\n const productId = props.buyProductId ?? lock.productId;\n if (!productId) return;\n const request: ChatBuyRequest = { productId, reason: lock.reason, priceCents: lock.priceCents, ...(lock.amount != null ? { amount: lock.amount } : {}), ...(lock.email ? { email: lock.email } : {}), ...(lock.code ? { code: lock.code } : {}), ...(lock.turnstileToken != null ? { turnstileToken: lock.turnstileToken } : {}) };\n if (props.onBuy) {\n await props.onBuy(request);\n return;\n }\n const commerceBase = (props.host ?? getPanelConfig().host ?? \"https://bitsnap.pl\").replace(/\\/+$/, \"\");\n const isFreeClaim = lock.freeClaimLimit != null && (lock.priceType === \"free\" || (lock.priceType === \"pay_what_you_want\" && lock.minimumCents === 0 && request.amount === 0));\n if (isFreeClaim && request.email && request.turnstileToken != null) {\n claim.current = null;\n const response = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/free-claim/request-code`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email: request.email, turnstileToken: request.turnstileToken }),\n });\n const result = await response.json() as { code?: string };\n if (!response.ok) throw new Error(result.code ?? \"internal\");\n return;\n }\n if (isFreeClaim && request.email && request.code) {\n const key = JSON.stringify([commerceBase, props.projectID, productId, request.email, request.code]);\n if (!claim.current || claim.current.key !== key || claim.current.expires <= Date.now()) {\n claim.current = null;\n const verify = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/free-claim/verify-code`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ email: request.email, code: request.code }),\n });\n const verified = await verify.json() as { claimToken?: string; code?: string; expiresIn?: number };\n if (!verify.ok || !verified.claimToken) throw new Error(verified.code ?? \"email-verification-required\");\n claim.current = { key, token: verified.claimToken, expires: Date.now() + (verified.expiresIn ?? 600) * 1000 };\n }\n const bought = await fetch(`${commerceBase}/api/integrations/${encodeURIComponent(props.projectID)}/public-commerce/buy`, {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ items: [{ id: productId, quantity: 1, amount: 0 }], details: { email: request.email }, claimToken: claim.current.token }),\n });\n const result = await bought.json() as { success?: boolean; url?: string; code?: string };\n if (!bought.ok || !result.url) throw new Error(result.code ?? \"free-order-not-supported\");\n window.location.assign(result.url);\n return;\n }\n await addPackToCart({\n projectID: props.projectID,\n productId,\n name: props.title,\n priceCents: lock.priceCents,\n amount: lock.amount,\n host: props.host,\n });\n };\n}\n\n/**\n * A buyer's pack thread for one service product: history, live updates,\n * sending with pending and refused states, report and mute, and the lock\n * with \"Kup pakiet\" when the replies are used up or the pack expired.\n * Load `bitsnap-react/dist/index.css` once.\n */\nexport function ChatThread(props: ChatThreadProps) {\n const client = useWidgetClient(props);\n const locale = props.locale ?? getLocale();\n const texts = useMemo(() => getChatTexts(props.texts, locale), [props.texts, locale]);\n const conversation = useConversation({\n client,\n target: { kind: \"thread\", threadId: props.threadId, productId: props.productId },\n pollIntervalMs: props.pollIntervalMs,\n realtimeUrl: props.realtimeUrl,\n loadNats: props.loadNats,\n });\n const buy = useBuy(props);\n return (\n <div className=\"bitsnap-react h-full\">\n <ConversationView\n host={props.host}\n conversation={conversation}\n texts={texts}\n locale={locale}\n kind=\"thread\"\n title={props.title}\n onBack={props.onBack}\n onBuy={buy}\n className={props.className}\n />\n </div>\n );\n}\n\n/**\n * A community channel: messages, live updates, posting when the channel\n * allows it (with the channel's moderation refusals shown on the message),\n * report, and the lock with \"Kup dostęp\" for a buyer without a grant.\n * Load `bitsnap-react/dist/index.css` once.\n */\nexport function Channel(props: ChannelProps) {\n const client = useWidgetClient(props);\n const locale = props.locale ?? getLocale();\n const texts = useMemo(() => getChatTexts(props.texts, locale), [props.texts, locale]);\n const conversation = useConversation({\n client,\n target: { kind: \"channel\", channelId: props.channelId, productId: props.productId },\n pollIntervalMs: props.pollIntervalMs,\n realtimeUrl: props.realtimeUrl,\n loadNats: props.loadNats,\n });\n const buy = useBuy(props);\n return (\n <div className=\"bitsnap-react h-full\">\n <ConversationView\n host={props.host}\n conversation={conversation}\n texts={texts}\n locale={locale}\n kind=\"channel\"\n title={props.title}\n onBack={props.onBack}\n onBuy={buy}\n className={props.className}\n />\n </div>\n );\n}\n","import { addProductToCart } from \"../components/products/add-to-cart\";\n\nexport type BuyPackInput = {\n /** Public project id of the shop (the cart's project). */\n projectID: string;\n /** The product (or variant) to add: the pack's service or community product. */\n productId: string;\n name?: string;\n priceCents?: number;\n amount?: number;\n /** Backend origin of the cart, when not the default. */\n host?: string;\n};\n\n/**\n * Default \"Kup pakiet\" / \"Kup dostęp\" action: adds the product to the page\n * cart (`Bitsnap.addProductToCart`) and opens the cart drawer. The page needs\n * a mounted `BitsnapCheckout`. When the page cart belongs to another project\n * or host nothing changes and the promise rejects; pass `onBuy` to the widget\n * to use your own checkout instead.\n */\nexport async function addPackToCart(input: BuyPackInput): Promise<void> {\n await addProductToCart(\n {\n id: input.productId,\n name: input.name ?? \"\",\n price: input.priceCents ?? 0,\n currency: \"\",\n amount: input.amount,\n },\n { projectID: input.projectID, host: input.host, showCart: true },\n );\n}\n","/**\n * Buyer side of the Bitsnap chat service (`bitsnap-messaging`): the shapes\n * the library works with. The wire is snake_case JSON; `client.ts` maps it to\n * these camelCase types. Times are ISO 8601 strings, `null` when absent.\n */\n\n/** Default base URL of the chat service. */\nexport const DEFAULT_CHAT_API_URL = \"https://chat.bitsnap.pl\";\n/** Bitsnap API that authenticates callers before signing private attachment URLs. */\nexport const DEFAULT_CHAT_ATTACHMENTS_URL = \"https://bitsnap.pl\";\n\n/** Default NATS WebSocket URL, used when the realtime token lists no server. */\nexport const DEFAULT_CHAT_REALTIME_URL =\n \"wss://bitsnap-nats-queue-production.up.railway.app\";\n\n/** One paid order that gives a pack thread creator replies. */\nexport type ChatPack = {\n orderId: string;\n productId?: string;\n replies: number;\n used: number;\n left: number;\n expiresAt: string | null;\n expired: boolean;\n};\n\n/** A stored message, or the pending answer of a send. */\nexport type ChatMessage = {\n id: string;\n threadId: string;\n authorEmail: string;\n body: string;\n attachmentKey?: string;\n attachmentKind?: string;\n attachmentName?: string;\n attachmentSize?: number;\n attachmentContentType?: string;\n createdAt: string;\n pinned: boolean;\n /** Soft deleted: the body is empty. */\n deleted: boolean;\n /** A message of the service, such as the creator's away notice. */\n system: boolean;\n /** `\"pending\"` in the answer of a send: queued, not stored yet. */\n status?: \"pending\";\n};\n\nexport type ChatAttachment = {\n key: string;\n kind: \"image\" | \"file\";\n filename: string;\n size: number;\n contentType: string;\n};\n\n/** The state of a conversation for the caller (`ThreadView` on the wire). */\nexport type ChatThreadSummary = {\n id: string;\n kind: \"pack\" | \"channel\";\n channelId?: string;\n /** For a buyer, the creator's e-mail (pack) or the channel name. */\n title: string;\n lastMessage?: ChatMessage;\n unread: number;\n repliesTotal: number;\n repliesUsed: number;\n repliesLeft: number;\n expiresAt: string | null;\n /** The caller cannot write: every pack has expired. */\n composerLocked: boolean;\n /** The creator blocked this buyer from posting. */\n blocked?: boolean;\n productId?: string;\n /** The creator's promise in hours (\"odpowiedź w ciągu 48 h\"), 0 for none. */\n responseHours: number;\n packs: ChatPack[];\n muted: boolean;\n};\n\n/** A channel the caller may read. */\nexport type ChatChannelSummary = {\n id: string;\n name: string;\n topic: string;\n welcome: string;\n whoCanPost: \"creator\" | \"members\";\n productId: string;\n /** `creator`, `member` or `muted`. */\n role: string;\n memberCount: number;\n createdAt: string;\n blockLinks: boolean;\n blockMedia: boolean;\n cooldownSeconds: number;\n};\n\n/** A pack the buyer holds and has not opened a thread for yet. */\nexport type ChatPackEntitlement = {\n productId: string;\n name: string;\n repliesLeftHint: number;\n expiresAt: string | null;\n};\n\n/** A community product the buyer holds; `channelId` once a channel is bound. */\nexport type ChatChannelEntitlement = {\n productId: string;\n channelId?: string;\n};\n\n/** `GET /v1/me`: the buyer's chat in one project. */\nexport type ChatMe = {\n email: string;\n threads: ChatThreadSummary[];\n entitlements: {\n packs: ChatPackEntitlement[];\n channels: ChatChannelEntitlement[];\n };\n channels: ChatChannelSummary[];\n};\n\n/** One page of history, newest first as the service answers it. */\nexport type ChatMessagePage = {\n messages: ChatMessage[];\n hasMore: boolean;\n};\n\n/** The thread part of a send answer. */\nexport type ChatThreadState = Pick<\n ChatThreadSummary,\n | \"id\"\n | \"kind\"\n | \"repliesTotal\"\n | \"repliesUsed\"\n | \"repliesLeft\"\n | \"expiresAt\"\n | \"composerLocked\"\n | \"responseHours\"\n>;\n\n/** `POST /v1/threads/{id}/messages` answers 202 with the queued message. */\nexport type ChatSendResult = {\n message: ChatMessage;\n thread: ChatThreadState;\n};\n\nexport type ChatReportReason = \"spam\" | \"abuse\" | \"illegal\" | \"other\";\n\n/** `POST /v1/realtime/token`. */\nexport type ChatRealtimeToken = {\n jwt: string;\n expiresAt: string;\n servers: string[];\n subjects: string[];\n truncated: boolean;\n};\n\n/** Live events on the thread or channel subject. */\nexport type ChatRealtimeEvent =\n | { type: \"message.created\"; message: ChatMessage }\n | { type: \"message.rejected\"; id: string; reason: string }\n | { type: \"message.deleted\"; id: string }\n | { type: \"message.pinned\"; id: string }\n | { type: \"message.unpinned\"; id: string };\n\n/** How a conversation hears about new messages. */\nexport type ChatLiveMode = \"connecting\" | \"live\" | \"polling\" | \"off\";\n","import {\n DEFAULT_CHAT_API_URL,\n DEFAULT_CHAT_ATTACHMENTS_URL,\n type ChatChannelSummary,\n type ChatAttachment,\n type ChatMe,\n type ChatMessage,\n type ChatMessagePage,\n type ChatPack,\n type ChatRealtimeEvent,\n type ChatRealtimeToken,\n type ChatReportReason,\n type ChatSendResult,\n type ChatThreadState,\n type ChatThreadSummary,\n} from \"./types\";\n\n/**\n * An error answer of the chat service (`{\"error\":\"<code>\"}`), or `network`\n * (status 0) when the request did not get an answer.\n *\n * Codes the buyer UI handles: `unauthorized` (401), `plan_required`,\n * `locked`, `muted`, `blocked`, `forbidden`, `read_only` (403), `not_found`\n * (404), `moderation_link`, `moderation_banned_word`, `moderation_media`\n * (422), `cooldown` (429, with `retryAfter`), `unavailable` (503),\n * `invalid_project` (400).\n */\nexport class ChatError extends Error {\n readonly code: string;\n readonly status: number;\n /** `cooldown`: seconds to wait before the next post. */\n readonly retryAfter?: number;\n /** Extra fields of the answer (`locked`: product, price type and bounds). */\n readonly data: Record<string, unknown>;\n\n constructor(\n code: string,\n status: number,\n data: Record<string, unknown> = {},\n ) {\n super(`chat: ${code} (${status})`);\n this.name = \"ChatError\";\n this.code = code;\n this.status = status;\n this.data = data;\n const retry = Number(data.retry_after);\n if (Number.isFinite(retry) && retry > 0) this.retryAfter = retry;\n }\n}\n\nexport type ChatClientOptions = {\n /** Project id sent as `X-Bitsnap-Project`. */\n projectId: string;\n /** Bearer credential: the panel session token or a `bcht_` buyer token. */\n token: string;\n /** Base URL of the chat service. Defaults to `https://chat.bitsnap.pl`. */\n baseUrl?: string;\n /** Bitsnap app host for private attachment upload and read endpoints. */\n attachmentsUrl?: string;\n /** Replaces `fetch` (tests, server rendering). */\n fetch?: typeof fetch;\n};\n\n/** The buyer calls of the chat service HTTP API. */\nexport type ChatClient = {\n readonly baseUrl: string;\n readonly projectId: string;\n me(): Promise<ChatMe>;\n /** Opens or returns the pack thread of a service product. */\n openThread(productId: string): Promise<ChatThreadSummary>;\n /** Newest first; `before` is a message id. */\n messages(\n threadId: string,\n options?: { before?: string; limit?: number },\n ): Promise<ChatMessagePage>;\n /** Queues a message. The service answers 202 with `status: \"pending\"`. */\n send(\n threadId: string,\n input: { body: string; clientId?: string; attachment?: ChatAttachment },\n ): Promise<ChatSendResult>;\n uploadAttachment(threadId: string, file: File, progress: (value: number) => void): Promise<ChatAttachment>;\n attachmentURL(threadId: string, message: ChatMessage): Promise<string>;\n deleteMessage(threadId: string, messageId: string): Promise<void>;\n mute(threadId: string, muted: boolean): Promise<void>;\n report(input: {\n threadId: string;\n messageId?: string;\n reason: ChatReportReason;\n note?: string;\n }): Promise<void>;\n markRead(threadId: string, messageId: string): Promise<void>;\n /**\n * A NATS token for every conversation the caller may read. The answer is\n * kept until a minute and a half before it expires (the service mints one\n * per caller every 10 s); `fresh` asks for a new one.\n */\n realtimeToken(options?: { fresh?: boolean }): Promise<ChatRealtimeToken>;\n};\n\nexport function createChatClient(options: ChatClientOptions): ChatClient {\n const baseUrl = (options.baseUrl || DEFAULT_CHAT_API_URL).replace(/\\/+$/, \"\");\n const localOrigin = typeof window !== \"undefined\" && [\"localhost\", \"127.0.0.1\"].includes(window.location.hostname) ? window.location.origin : undefined;\n const attachmentBase = (options.attachmentsUrl ?? localOrigin ?? DEFAULT_CHAT_ATTACHMENTS_URL).replace(/\\/+$/, \"\");\n const doFetch = options.fetch ?? ((...args) => fetch(...args));\n\n async function call(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<{ status: number; json: unknown }> {\n let response: Response;\n try {\n response = await doFetch(baseUrl + path, {\n method,\n headers: {\n Authorization: `Bearer ${options.token}`,\n \"X-Bitsnap-Project\": options.projectId,\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n } catch {\n throw new ChatError(\"network\", 0);\n }\n let json: unknown = null;\n try {\n const text = await response.text();\n json = text ? JSON.parse(text) : null;\n } catch {\n json = null;\n }\n if (!response.ok) {\n const data = isRecord(json) ? json : {};\n const code = typeof data.error === \"string\" ? data.error : \"internal_error\";\n throw new ChatError(code, response.status, data);\n }\n return { status: response.status, json };\n }\n\n const id = encodeURIComponent;\n let cachedToken: ChatRealtimeToken | undefined;\n\n return {\n baseUrl,\n projectId: options.projectId,\n async me() {\n const { json } = await call(\"GET\", \"/v1/me\");\n return mapMe(json);\n },\n async openThread(productId) {\n const { json } = await call(\"POST\", \"/v1/threads\", {\n product_id: productId,\n });\n return mapThread(json);\n },\n async messages(threadId, opts = {}) {\n const query = new URLSearchParams();\n if (opts.before) query.set(\"before\", opts.before);\n if (opts.limit) query.set(\"limit\", String(opts.limit));\n const qs = query.toString();\n const suffix = qs ? `?${qs}` : \"\";\n const { json } = await call(\n \"GET\",\n `/v1/threads/${id(threadId)}/messages${suffix}`,\n );\n const page = isRecord(json) ? json : {};\n return {\n messages: arr(page.messages).map(mapMessage),\n hasMore: page.has_more === true,\n };\n },\n async send(threadId, input) {\n const { json } = await call(\n \"POST\",\n `/v1/threads/${id(threadId)}/messages`,\n {\n body: input.body,\n ...(input.clientId ? { client_id: input.clientId } : {}),\n ...(input.attachment ? {\n attachment_key: input.attachment.key,\n attachment_kind: input.attachment.kind,\n attachment_name: input.attachment.filename,\n attachment_size: input.attachment.size,\n attachment_content_type: input.attachment.contentType,\n } : {}),\n },\n );\n const res = isRecord(json) ? json : {};\n return {\n message: mapMessage(res.message),\n thread: mapThreadState(res.thread),\n };\n },\n async uploadAttachment(threadId, file, progress) {\n const requestAttachment = async <T,>(path: string, body: unknown): Promise<T> => {\n const response = await doFetch(`${attachmentBase}/api/chat/attachments/${path}`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${options.token}`, \"X-Bitsnap-Project\": options.projectId, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n let data: Record<string, unknown> = {};\n try { data = await response.json() as Record<string, unknown>; } catch { /* parsed as request error below */ }\n if (!response.ok) throw new ChatError(typeof data.error === \"string\" ? data.error : \"request_failed\", response.status, data);\n return data as T;\n };\n const ticket = await requestAttachment<{ key: string; uploadURL: string; uploadTicket: string }>(\"upload\", {\n project_id: options.projectId, thread_id: threadId, filename: file.name, content_type: file.type, size: file.size,\n });\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open(\"PUT\", ticket.uploadURL);\n xhr.setRequestHeader(\"Content-Type\", file.type);\n xhr.upload.onprogress = (event) => { if (event.lengthComputable) progress(Math.round(event.loaded / event.total * 100)); };\n xhr.onload = () => xhr.status >= 200 && xhr.status < 300 ? resolve() : reject(new ChatError(\"upload_failed\", xhr.status));\n xhr.onerror = () => reject(new ChatError(\"network\", 0));\n xhr.send(file);\n });\n const done = await requestAttachment<{ key: string; kind: \"image\" | \"file\"; filename: string; size: number; content_type: string }>(\"complete\", {\n project_id: options.projectId, thread_id: threadId, key: ticket.key, upload_ticket: ticket.uploadTicket, filename: file.name, content_type: file.type, size: file.size,\n });\n return { key: done.key, kind: done.kind, filename: done.filename, size: done.size, contentType: done.content_type };\n },\n async attachmentURL(threadId, message) {\n if (!message.attachmentKey) throw new ChatError(\"invalid_attachment\", 400);\n const query = new URLSearchParams({ project_id: options.projectId, thread_id: threadId, key: message.attachmentKey, filename: message.attachmentName ?? \"attachment\", size: String(message.attachmentSize ?? 0) });\n const response = await doFetch(`${attachmentBase}/api/chat/attachments/read?${query}`, { headers: { Authorization: `Bearer ${options.token}`, \"X-Bitsnap-Project\": options.projectId } });\n let data: Record<string, unknown> = {};\n try { data = await response.json() as Record<string, unknown>; } catch { /* parsed as request error below */ }\n if (!response.ok || typeof data.url !== \"string\") throw new ChatError(typeof data.error === \"string\" ? data.error : \"request_failed\", response.status, data);\n return data.url;\n },\n async deleteMessage(threadId, messageId) {\n await call(\n \"DELETE\",\n `/v1/threads/${id(threadId)}/messages/${id(messageId)}`,\n );\n },\n async mute(threadId, muted) {\n await call(\"POST\", `/v1/threads/${id(threadId)}/mute`, { muted });\n },\n async report(input) {\n await call(\"POST\", \"/v1/reports\", {\n thread_id: input.threadId,\n ...(input.messageId ? { message_id: input.messageId } : {}),\n reason: input.reason,\n ...(input.note ? { note: input.note } : {}),\n });\n },\n async markRead(threadId, messageId) {\n await call(\"POST\", `/v1/threads/${id(threadId)}/receipts`, {\n state: \"read\",\n message_id: messageId,\n });\n },\n async realtimeToken(opts = {}) {\n if (\n !opts.fresh &&\n cachedToken &&\n Date.parse(cachedToken.expiresAt) - Date.now() > 90_000\n ) {\n return cachedToken;\n }\n const { json } = await call(\"POST\", \"/v1/realtime/token\");\n const res = isRecord(json) ? json : {};\n return (cachedToken = {\n jwt: str(res.jwt),\n expiresAt: str(res.expires_at),\n servers: arr(res.servers).filter((s): s is string => typeof s === \"string\"),\n subjects: arr(res.subjects).filter((s): s is string => typeof s === \"string\"),\n truncated: res.truncated === true,\n });\n },\n };\n}\n\n/** Parses one live event of the thread or channel subject. */\nexport function parseRealtimeEvent(raw: unknown): ChatRealtimeEvent | null {\n if (!isRecord(raw) || typeof raw.type !== \"string\") return null;\n const eventId = str(raw.id);\n switch (raw.type) {\n case \"message.created\":\n return { type: \"message.created\", message: mapMessage(raw) };\n case \"message.rejected\":\n return { type: \"message.rejected\", id: eventId, reason: str(raw.reason) };\n case \"message.deleted\":\n case \"message.pinned\":\n case \"message.unpinned\":\n return { type: raw.type, id: eventId };\n default:\n return null;\n }\n}\n\n// --- wire mapping -------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value != null && !Array.isArray(value);\n}\n\nfunction arr(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nfunction str(value: unknown): string {\n return typeof value === \"string\" ? value : value == null ? \"\" : String(value);\n}\n\nfunction num(value: unknown): number {\n const n = Number(value);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction optStr(value: unknown): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\nfunction time(value: unknown): string | null {\n return typeof value === \"string\" && value !== \"\" ? value : null;\n}\n\nexport function mapMessage(raw: unknown): ChatMessage {\n const m = isRecord(raw) ? raw : {};\n return {\n id: str(m.id),\n threadId: str(m.thread_id),\n authorEmail: str(m.author_email),\n body: str(m.body),\n attachmentKey: optStr(m.attachment_key),\n attachmentKind: optStr(m.attachment_kind),\n attachmentName: optStr(m.attachment_name),\n attachmentSize: num(m.attachment_size),\n attachmentContentType: optStr(m.attachment_content_type),\n createdAt: str(m.created_at),\n pinned: m.pinned === true,\n deleted: m.deleted === true,\n system: m.system === true,\n ...(m.status === \"pending\" ? { status: \"pending\" as const } : {}),\n };\n}\n\nfunction mapPack(raw: unknown): ChatPack {\n const p = isRecord(raw) ? raw : {};\n return {\n orderId: str(p.order_id),\n productId: optStr(p.product_id),\n replies: num(p.replies),\n used: num(p.used),\n left: num(p.left),\n expiresAt: time(p.expires_at),\n expired: p.expired === true,\n };\n}\n\nfunction mapThreadState(raw: unknown): ChatThreadState {\n const t = isRecord(raw) ? raw : {};\n return {\n id: str(t.id),\n kind: t.kind === \"channel\" ? \"channel\" : \"pack\",\n repliesTotal: num(t.replies_total),\n repliesUsed: num(t.replies_used),\n repliesLeft: num(t.replies_left),\n expiresAt: time(t.expires_at),\n composerLocked: t.composer_locked === true,\n responseHours: num(t.response_hours),\n };\n}\n\nexport function mapThread(raw: unknown): ChatThreadSummary {\n const t = isRecord(raw) ? raw : {};\n return {\n ...mapThreadState(t),\n channelId: optStr(t.channel_id),\n title: str(t.title),\n lastMessage: isRecord(t.last_message) ? mapMessage(t.last_message) : undefined,\n unread: num(t.unread),\n productId: optStr(t.product_id),\n blocked: t.blocked === true,\n packs: arr(t.packs).map(mapPack),\n muted: t.muted === true,\n };\n}\n\nfunction mapChannel(raw: unknown): ChatChannelSummary {\n const c = isRecord(raw) ? raw : {};\n return {\n id: str(c.id),\n name: str(c.name),\n topic: str(c.topic),\n welcome: str(c.welcome),\n whoCanPost: c.who_can_post === \"members\" ? \"members\" : \"creator\",\n productId: str(c.product_id),\n role: str(c.role),\n memberCount: num(c.member_count),\n createdAt: str(c.created_at),\n blockLinks: c.block_links === true,\n blockMedia: c.block_media === true,\n cooldownSeconds: num(c.cooldown_seconds),\n };\n}\n\nfunction mapMe(raw: unknown): ChatMe {\n const m = isRecord(raw) ? raw : {};\n const ent = isRecord(m.entitlements) ? m.entitlements : {};\n return {\n email: str(m.email),\n threads: arr(m.threads).map(mapThread),\n entitlements: {\n packs: arr(ent.packs).map((raw) => {\n const p = isRecord(raw) ? raw : {};\n return {\n productId: str(p.product_id),\n name: str(p.name),\n repliesLeftHint: num(p.replies_left_hint),\n expiresAt: time(p.expires_at),\n };\n }),\n channels: arr(ent.channels).map((raw) => {\n const c = isRecord(raw) ? raw : {};\n return { productId: str(c.product_id), channelId: optStr(c.channel_id) };\n }),\n },\n channels: arr(m.channels).map(mapChannel),\n };\n}\n","import {\n AlertCircle,\n ArrowLeft,\n Clock,\n Lock,\n MoreHorizontal,\n Paperclip,\n Pin,\n Plus,\n SendHorizontal,\n X,\n} from \"lucide-react\";\nimport {\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { TurnstileChallenge } from \"../captcha/TurnstileChallenge\";\nimport {\n fill,\n formatChatDate,\n formatMessageTime,\n rejectionText,\n repliesLeftText,\n type ChatTexts,\n} from \"./texts\";\nimport type { ChatMessage, ChatReportReason } from \"./types\";\nimport type {\n ConversationActions,\n ConversationLock,\n ConversationMessage,\n ConversationState,\n} from \"./useConversation\";\n\nexport type ConversationViewProps = {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n locale: string;\n kind: \"thread\" | \"channel\";\n /** Heading; defaults to the thread title or the channel name. */\n title?: string;\n onBack?: () => void;\n /** The lock's buy action. Rejects to show the error. */\n onBuy?: (lock: ConversationLock & { email?: string; code?: string; turnstileToken?: string }) => Promise<void> | void;\n className?: string;\n host?: string;\n};\n\nconst focusRing =\n \"focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500\";\n\nfunction prefersReducedMotion(): boolean {\n return typeof window !== \"undefined\" && typeof window.matchMedia === \"function\" && window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\nfunction formatBytes(bytes = 0): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction scrollToBottom(element: HTMLElement, smooth: boolean) {\n if (typeof element.scrollTo === \"function\") element.scrollTo({ top: element.scrollHeight, behavior: smooth ? \"smooth\" : \"auto\" });\n else element.scrollTop = element.scrollHeight;\n}\n\nconst quietButton = cn(\n \"inline-flex min-h-11 items-center gap-2 rounded-md px-3 text-sm text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-800\",\n focusRing,\n);\n\nconst primaryButton = cn(\n \"inline-flex min-h-11 items-center justify-center gap-2 rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200\",\n focusRing,\n);\n\n/** The shared body of `ChatThread`, `Channel` and the panel screen. */\nexport function ConversationView({\n host,\n conversation: c,\n texts,\n locale,\n kind,\n title,\n onBack,\n onBuy,\n className,\n}: ConversationViewProps) {\n const [menuOpen, setMenuOpen] = useState(false);\n const [reporting, setReporting] = useState<{ messageId?: string } | null>(null);\n const [notice, setNotice] = useState<string>();\n const [draft, setDraft] = useState(\"\");\n const [reportSent, setReportSent] = useState(false);\n useEffect(() => {\n if (!reportSent) return;\n const timer = setTimeout(() => setReportSent(false), 6000);\n return () => clearTimeout(timer);\n }, [reportSent]);\n const heading =\n title ||\n c.channel?.name ||\n (kind === \"channel\" ? texts.channelFallbackTitle : texts.chatFallbackTitle);\n\n const meta = metaLine(c, texts, locale);\n\n return (\n <section\n data-bitsnap-chat={kind}\n aria-label={heading}\n className={cn(\n \"flex h-full min-h-[28rem] w-full min-w-0 flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white text-neutral-900 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-50\",\n className,\n )}\n >\n <header className=\"flex items-start gap-2 border-b border-neutral-200 px-3 py-2 dark:border-neutral-800\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={texts.back}\n className={cn(quietButton, \"w-11 justify-center px-0\")}\n >\n <ArrowLeft className=\"size-5\" aria-hidden=\"true\" />\n </button>\n )}\n <div className=\"min-w-0 flex-1 py-1.5\">\n <h2 className=\"flex items-center gap-2 text-base font-semibold\">\n <span className=\"truncate\">{heading}</span>\n <LiveDot live={c.live} texts={texts} />\n </h2>\n {meta.length > 0 && (\n <p className=\"mt-0.5 flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-neutral-600 dark:text-neutral-400\">\n {meta.map((item) => (\n <span key={item}>{item}</span>\n ))}\n </p>\n )}\n </div>\n {c.status === \"ready\" && (\n <button\n type=\"button\"\n aria-label={texts.conversationActions}\n aria-expanded={menuOpen}\n onClick={() => setMenuOpen((open) => !open)}\n className={cn(quietButton, \"w-11 justify-center px-0\")}\n >\n <MoreHorizontal className=\"size-5\" aria-hidden=\"true\" />\n </button>\n )}\n </header>\n {menuOpen && c.status === \"ready\" && (\n <div className=\"flex flex-wrap gap-1 border-b border-neutral-200 bg-neutral-50 px-3 py-1 dark:border-neutral-800 dark:bg-neutral-950\">\n {kind === \"thread\" && c.thread && (\n <button\n type=\"button\"\n className={quietButton}\n onClick={async () => {\n setMenuOpen(false);\n try {\n await c.setMuted(!c.thread!.muted);\n setNotice(undefined);\n } catch {\n setNotice(texts.muteError);\n }\n }}\n >\n {c.thread.muted ? texts.unmuteThread : texts.muteThread}\n </button>\n )}\n <button\n type=\"button\"\n className={quietButton}\n onClick={() => {\n setMenuOpen(false);\n setReporting({});\n }}\n >\n {texts.reportConversation}\n </button>\n </div>\n )}\n\n {c.status === \"loading\" && <LoadingBody texts={texts} />}\n {c.status === \"error\" && <ErrorBody conversation={c} texts={texts} />}\n {c.status === \"locked\" && c.lock && (\n <div className=\"flex flex-1 flex-col justify-center overflow-y-auto p-4\">\n <LockCard host={host} lock={c.lock} kind={kind} texts={texts} locale={locale} onBuy={onBuy} prominent />\n </div>\n )}\n {c.status === \"ready\" && (\n <>\n <MessageList\n conversation={c}\n texts={texts}\n locale={locale}\n kind={kind}\n onReport={(messageId) => setReporting({ messageId })}\n onEdit={(message) => {\n setDraft(message.body);\n c.discard(message.id);\n }}\n />\n {notice && (\n <p role=\"status\" className=\"px-4 pb-2 text-xs text-red-700 dark:text-red-400\">\n {notice}\n </p>\n )}\n {c.lock && (\n <div className=\"border-t border-neutral-200 p-3 dark:border-neutral-800\">\n <LockCard host={host} lock={c.lock} kind={kind} texts={texts} locale={locale} onBuy={onBuy} />\n </div>\n )}\n {reporting ? (\n <ReportForm\n texts={texts}\n messageId={reporting.messageId}\n onCancel={() => setReporting(null)}\n onSubmit={async (reason, note) => {\n await c.report({ messageId: reporting.messageId, reason, note });\n setReporting(null);\n setNotice(undefined);\n setReportSent(true);\n }}\n />\n ) : (\n <Composer\n conversation={c}\n texts={texts}\n kind={kind}\n draft={draft}\n setDraft={setDraft}\n />\n )}\n </>\n )}\n {reportSent && (\n <p role=\"status\" className=\"border-t border-neutral-200 px-4 py-2 text-xs text-neutral-700 dark:border-neutral-800 dark:text-neutral-300\">\n {texts.reportSent}\n </p>\n )}\n </section>\n );\n}\n\nfunction metaLine(c: ConversationState, texts: ChatTexts, locale: string): string[] {\n const items: string[] = [];\n if (c.thread && c.thread.kind === \"pack\") {\n items.push(repliesLeftText(texts, c.thread.repliesLeft, locale));\n if (c.thread.expiresAt) {\n const date = formatChatDate(c.thread.expiresAt, locale);\n items.push(\n fill(c.thread.composerLocked ? texts.expiredOn : texts.expiresOn, { date }),\n );\n }\n if (c.thread.responseHours > 0)\n items.push(fill(texts.responseHours, { hours: c.thread.responseHours }));\n if (c.thread.muted) items.push(texts.mutedBadge);\n }\n if (c.channel) {\n if (c.channel.topic) items.push(c.channel.topic);\n if (c.channel.memberCount > 0)\n items.push(fill(texts.members, { count: c.channel.memberCount }));\n }\n return items;\n}\n\nfunction LiveDot({ live, texts }: { live: ConversationState[\"live\"]; texts: ChatTexts }) {\n if (live !== \"live\" && live !== \"polling\") return null;\n const label = live === \"live\" ? texts.liveLabel : texts.pollingLabel;\n return (\n <span\n title={label}\n data-live={live}\n className={cn(\n \"inline-block size-2 shrink-0 rounded-full\",\n live === \"live\" ? \"bg-emerald-500\" : \"bg-neutral-400 dark:bg-neutral-500\",\n )}\n >\n <span className=\"sr-only\">{label}</span>\n </span>\n );\n}\n\nfunction LoadingBody({ texts }: { texts: ChatTexts }) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label={texts.loading} className=\"flex flex-1 flex-col gap-3 p-4\">\n {[60, 40, 70].map((width, index) => (\n <div\n key={index}\n aria-hidden=\"true\"\n style={{ width: `${width}%` }}\n className={cn(\n \"h-10 rounded-2xl bg-neutral-100 motion-safe:animate-pulse dark:bg-neutral-800\",\n index === 1 && \"self-end\",\n )}\n />\n ))}\n </div>\n );\n}\n\nfunction errorText(code: string, texts: ChatTexts): string {\n switch (code) {\n case \"no_token\":\n return texts.errorNoToken;\n case \"unauthorized\":\n return texts.errorUnauthorized;\n case \"plan_required\":\n return texts.errorPlan;\n case \"not_found\":\n return texts.errorNotFound;\n case \"unavailable\":\n case \"network\":\n return texts.errorUnavailable;\n default:\n return texts.errorGeneric;\n }\n}\n\nfunction ErrorBody({\n conversation: c,\n texts,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n}) {\n const code = c.error?.code ?? \"\";\n const canRetry = code !== \"no_token\" && code !== \"plan_required\" && code !== \"unauthorized\";\n return (\n <div role=\"alert\" className=\"flex flex-1 flex-col items-start justify-center gap-3 p-4\">\n <p className=\"text-sm text-neutral-700 dark:text-neutral-300\">{errorText(code, texts)}</p>\n {canRetry && (\n <button type=\"button\" className={primaryButton} onClick={c.reload}>\n {texts.retry}\n </button>\n )}\n </div>\n );\n}\n\nfunction LockCard({\n host,\n lock,\n kind,\n texts,\n locale,\n onBuy,\n prominent,\n}: {\n host?: string;\n lock: ConversationLock;\n kind: \"thread\" | \"channel\";\n texts: ChatTexts;\n locale: string;\n onBuy?: ConversationViewProps[\"onBuy\"];\n prominent?: boolean;\n}) {\n const [buying, setBuying] = useState(false);\n const [failed, setFailed] = useState(\"\");\n const [amountText, setAmountText] = useState(String((lock.priceCents ?? 0) / 100));\n const normalized = amountText.trim().replace(\",\", \".\");\n const amount = /^\\d+(\\.\\d{0,2})?$/.test(normalized) ? Math.round(Number(normalized) * 100) : NaN;\n const isPwyw = lock.priceType === \"pay_what_you_want\";\n const min = lock.minimumCents ?? 0;\n const max = lock.maximumCents ?? Number.MAX_SAFE_INTEGER;\n const validAmount = Number.isInteger(amount) && amount >= min && amount <= max && (amount > 0 || lock.freeClaimLimit != null);\n const isFreeClaim = lock.freeClaimLimit != null && (lock.priceType === \"free\" || (isPwyw && min === 0 && amount === 0));\n const [email, setEmail] = useState(\"\");\n const [code, setCode] = useState(\"\");\n const [codeSent, setCodeSent] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState<string | null>(null);\n const [challengeAttempt, setChallengeAttempt] = useState(0);\n const [cooldown, setCooldown] = useState(0);\n const codeInput = useRef<HTMLInputElement>(null);\n useEffect(() => { if (codeSent) codeInput.current?.focus(); }, [codeSent]);\n useEffect(() => {\n if (cooldown <= 0) return;\n const timer = window.setTimeout(() => setCooldown(cooldown - 1), 1000);\n return () => window.clearTimeout(timer);\n }, [cooldown]);\n const restart = () => { setCodeSent(false); setCode(\"\"); setTurnstileToken(null); setChallengeAttempt((n) => n + 1); setFailed(\"\"); };\n const english = locale.toLowerCase().startsWith(\"en\");\n const [title, body] =\n lock.reason === \"replies\"\n ? [texts.lockRepliesTitle, texts.lockRepliesBody]\n : lock.reason === \"expired\"\n ? [texts.lockExpiredTitle, texts.lockExpiredBody]\n : kind === \"channel\"\n ? [texts.lockChannelTitle, texts.lockChannelBody]\n : [texts.lockThreadTitle, texts.lockThreadBody];\n const action = kind === \"channel\" ? texts.buyAccess : texts.buyPack;\n const canBuy = onBuy != null && lock.productId != null;\n\n return (\n <div\n data-testid=\"chat-lock\"\n data-lock={lock.reason}\n className={cn(\n \"flex gap-3 rounded-xl bg-neutral-100 p-3 dark:bg-neutral-800\",\n prominent ? \"flex-col items-start p-5\" : \"flex-col sm:flex-row sm:items-center\",\n )}\n >\n <Lock\n className={cn(\"shrink-0 text-neutral-500 dark:text-neutral-400\", prominent ? \"size-6\" : \"hidden size-5 sm:block\")}\n aria-hidden=\"true\"\n />\n <div className=\"min-w-0 flex-1\">\n <p className={cn(\"font-semibold\", prominent ? \"text-lg\" : \"text-sm\")}>{title}</p>\n <p className=\"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300\">{body}</p>\n {isFreeClaim && (\n <div className=\"mt-3 grid max-w-sm gap-2\">\n <label className=\"grid gap-1 text-sm\">\n <span>{english ? \"Email\" : \"E-mail\"}</span>\n <input type=\"email\" autoComplete=\"email\" value={email} onChange={(event) => { setEmail(event.currentTarget.value); restart(); }} disabled={buying} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" />\n </label>\n {codeSent ? (\n <label className=\"grid gap-1 text-sm\"><span>{english ? \"Email code\" : \"Kod z e-maila\"}</span><input ref={codeInput} inputMode=\"numeric\" autoComplete=\"one-time-code\" value={code} onChange={(event) => setCode(event.currentTarget.value)} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" /></label>\n ) : email.includes(\"@\") ? <TurnstileChallenge host={host} key={challengeAttempt} action=\"free_chat_claim\" locale={locale} onToken={setTurnstileToken} onFailure={() => setTurnstileToken(null)} /> : null}\n {codeSent && <button type=\"button\" disabled={buying || cooldown > 0} className=\"min-h-11 text-sm underline disabled:opacity-50\" onClick={restart}>{english ? \"Request a new code\" : \"Poproś o nowy kod\"}{cooldown > 0 ? ` (${cooldown}s)` : \"\"}</button>}\n </div>\n )}\n {isPwyw && (\n <label className=\"mt-3 grid max-w-xs gap-1 text-sm\">\n <span>{english ? `Amount (${(min / 100).toFixed(2)}–${Number.isFinite(max) ? (max / 100).toFixed(2) : \"∞\"} PLN)` : `Kwota (${(min / 100).toFixed(2)}–${Number.isFinite(max) ? (max / 100).toFixed(2) : \"∞\"} zł)`}</span>\n <input aria-label={english ? \"Amount\" : \"Kwota\"} inputMode=\"decimal\" type=\"text\" min={min / 100} max={Number.isFinite(max) ? max / 100 : undefined} step=\"0.01\" value={amountText} onChange={(event) => setAmountText(event.currentTarget.value)} onBlur={() => { if (Number.isFinite(amount)) setAmountText((amount / 100).toFixed(2)); }} className=\"h-11 rounded-md border border-neutral-300 bg-white px-3 text-neutral-900 dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50\" />\n {!validAmount && <span role=\"alert\" className=\"text-xs text-red-700 dark:text-red-400\">{english ? (amount === 0 && lock.freeClaimLimit == null ? \"Free claims are unavailable. Enter a positive amount.\" : \"Enter an amount within the range.\") : (amount === 0 && lock.freeClaimLimit == null ? \"Darmowy odbiór jest niedostępny. Wpisz kwotę większą od zera.\" : \"Kwota musi mieścić się w podanym zakresie.\")}</span>}\n </label>\n )}\n {lock.welcome && (\n <p className=\"mt-3 whitespace-pre-wrap text-sm\">{lock.welcome}</p>\n )}\n {failed && (\n <p role=\"alert\" className=\"mt-2 text-sm text-red-700 dark:text-red-400\">\n {failed}\n </p>\n )}\n </div>\n {canBuy && (\n <button\n type=\"button\"\n className={cn(primaryButton, \"shrink-0\")}\n disabled={buying || (isPwyw && !validAmount) || (isFreeClaim && (!email.includes(\"@\") || (!codeSent && turnstileToken === null) || (codeSent && !code)))}\n aria-busy={buying || undefined}\n onClick={async () => {\n setBuying(true);\n setFailed(\"\");\n try {\n if (isFreeClaim) {\n await onBuy!({ ...lock, ...(isPwyw ? { amount } : {}), email, ...(codeSent ? { code } : { turnstileToken }) });\n if (!codeSent) { setCodeSent(true); setCooldown(60); }\n } else {\n await onBuy!({ ...lock, ...(isPwyw ? { amount } : {}) });\n }\n } catch (error) {\n setFailed(chatBuyError(error, locale, texts.buyError));\n if (!codeSent) { setTurnstileToken(null); setChallengeAttempt((n) => n + 1); }\n } finally {\n setBuying(false);\n }\n }}\n >\n {buying ? texts.buying : isPwyw ? (english ? \"Choose what you pay\" : \"Kup pakiet, płacąc, ile chcesz\") : isFreeClaim ? (codeSent ? (english ? \"Claim for free\" : \"Odbierz za darmo\") : (english ? \"Send code\" : \"Wyślij kod\")) : action}\n </button>\n )}\n </div>\n );\n}\n\nfunction authorLabel(message: ConversationMessage, mine: boolean, kind: \"thread\" | \"channel\", texts: ChatTexts) {\n if (mine) return texts.you;\n if (kind === \"thread\") return texts.creator;\n // Channels have no display names yet: the part of the e-mail before \"@\".\n return message.authorEmail.split(\"@\")[0] || texts.creator;\n}\n\nfunction MessageList({\n conversation: c,\n texts,\n locale,\n kind,\n onReport,\n onEdit,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n locale: string;\n kind: \"thread\" | \"channel\";\n onReport: (messageId: string) => void;\n onEdit: (message: ConversationMessage) => void;\n}) {\n const scroller = useRef<HTMLDivElement>(null);\n const stick = useRef(true);\n const paintedHistory = useRef(false);\n const knownIds = useRef<Set<string> | null>(null);\n const knownOldestAt = useRef<string | null>(null);\n const [newIds, setNewIds] = useState<Set<string>>(() => new Set());\n const [newMessages, setNewMessages] = useState(false);\n const count = c.messages.length;\n const lastId = c.messages[count - 1]?.id;\n\n useEffect(() => {\n if (c.status !== \"ready\") return;\n const ids = new Set(c.messages.map((message) => message.id));\n const previous = knownIds.current;\n knownIds.current = ids;\n const oldest = c.messages.reduce<string | null>((at, message) => at == null || message.createdAt < at ? message.createdAt : at, null);\n const priorOldest = knownOldestAt.current;\n knownOldestAt.current = oldest;\n if (!previous) return; // Initial history is already on screen.\n const added = c.messages.filter((message) => !previous.has(message.id) && (priorOldest == null || message.createdAt >= priorOldest));\n if (!added.length || c.loadingOlder) return;\n setNewIds((current) => new Set([...current, ...added.map((message) => message.id)]));\n if (stick.current) setNewMessages(false);\n else setNewMessages(true);\n }, [c.messages, c.loadingOlder]);\n\n useLayoutEffect(() => {\n const el = scroller.current;\n if (!el) return;\n if (stick.current) {\n const reduced = prefersReducedMotion();\n scrollToBottom(el, !reduced && paintedHistory.current);\n }\n paintedHistory.current = true;\n }, [count, lastId]);\n\n return (\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n {newMessages && (\n <button\n type=\"button\"\n onClick={() => {\n const el = scroller.current;\n if (el) scrollToBottom(el, !prefersReducedMotion());\n stick.current = true;\n setNewMessages(false);\n }}\n className=\"absolute bottom-3 left-1/2 z-10 -translate-x-1/2 rounded-full bg-neutral-900 px-3 py-1.5 text-xs font-medium text-white shadow-md dark:bg-neutral-100 dark:text-neutral-900\"\n >\n {texts.newMessages ?? \"Nowe wiadomości ↓\"}\n </button>\n )}\n <div\n ref={scroller}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n onLoadCapture={() => {\n const el = scroller.current;\n if (el && stick.current) scrollToBottom(el, false);\n }}\n onScroll={(event) => {\n const el = event.currentTarget;\n stick.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;\n }}\n className=\"flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto overscroll-contain px-3 py-4 sm:px-4\"\n >\n {c.hasMore && (\n <button\n type=\"button\"\n onClick={() => {\n stick.current = false;\n void c.loadOlder();\n }}\n disabled={c.loadingOlder}\n className={cn(quietButton, \"self-center text-xs\")}\n >\n {c.loadingOlder ? texts.loading : texts.loadOlder}\n </button>\n )}\n {count === 0 && (\n <div className=\"m-auto max-w-sm text-center\" data-testid=\"chat-empty\">\n <p className=\"text-sm font-medium\">{texts.noMessagesYet}</p>\n <p className=\"mt-1 text-sm text-neutral-600 dark:text-neutral-400\">\n {kind === \"channel\" ? texts.emptyChannel : texts.emptyThread}\n </p>\n {c.channel?.welcome && (\n <p className=\"mt-3 whitespace-pre-wrap text-sm\">{c.channel.welcome}</p>\n )}\n </div>\n )}\n {c.messages.map((message) => (\n <MessageRow\n key={message.id}\n message={message}\n mine={message.authorEmail.toLowerCase() === c.me}\n kind={kind}\n texts={texts}\n locale={locale}\n getAttachmentURL={c.getAttachmentURL}\n animateEntry={newIds.has(message.id)}\n onDelete={() => c.deleteMessage(message.id)}\n onReport={() => onReport(message.id)}\n onRetry={() => void c.retry(message.id)}\n onDiscard={() => c.discard(message.id)}\n onEdit={() => onEdit(message)}\n />\n ))}\n </div>\n </div>\n );\n}\n\nfunction MessageRow({\n message,\n mine,\n kind,\n texts,\n locale,\n getAttachmentURL,\n animateEntry = false,\n onDelete,\n onReport,\n onRetry,\n onDiscard,\n onEdit,\n}: {\n message: ConversationMessage;\n mine: boolean;\n kind: \"thread\" | \"channel\";\n texts: ChatTexts;\n locale: string;\n getAttachmentURL: (message: ChatMessage) => Promise<string>;\n animateEntry?: boolean;\n onDelete: () => Promise<void>;\n onReport: () => void;\n onRetry: () => void;\n onDiscard: () => void;\n onEdit: () => void;\n}) {\n const [actions, setActions] = useState(false);\n const [confirmDelete, setConfirmDelete] = useState(false);\n const [attachmentUrl, setAttachmentUrl] = useState<string>();\n const [attachmentError, setAttachmentError] = useState(false);\n const [attachmentVersion, setAttachmentVersion] = useState(0);\n const attachmentExpiresAt = useRef(0);\n const retryAttachment = () => { setAttachmentUrl(undefined); setAttachmentError(false); setAttachmentVersion((value) => value + 1); };\n const openAttachment = async (event: React.MouseEvent<HTMLAnchorElement>) => {\n if (attachmentUrl && Date.now() < attachmentExpiresAt.current) return;\n event.preventDefault();\n // Open synchronously so a refreshed URL is not blocked as a popup.\n const tab = window.open(\"about:blank\", \"_blank\");\n if (tab) tab.opener = null;\n try {\n const url = await getAttachmentURL(message);\n attachmentExpiresAt.current = Date.now() + 50_000;\n setAttachmentUrl(url);\n if (tab) tab.location.href = url;\n } catch { tab?.close(); setAttachmentError(true); }\n };\n const local = message.local;\n\n useEffect(() => {\n if (!message.attachmentKey || message.deleted || message.local) return;\n let cancelled = false;\n setAttachmentError(false);\n void getAttachmentURL(message).then((url) => {\n if (!cancelled) { attachmentExpiresAt.current = Date.now() + 50_000; setAttachmentUrl(url); }\n }).catch(() => { if (!cancelled) setAttachmentError(true); });\n return () => { cancelled = true; };\n }, [message.attachmentKey, message.deleted, !!message.local, getAttachmentURL, attachmentVersion]);\n\n if (message.system) {\n return (\n <div data-message-id={message.id} className=\"mx-auto max-w-md text-center text-xs text-neutral-600 dark:text-neutral-400\">\n <span className=\"font-medium\">{texts.systemLabel}</span>\n {message.body && <p className=\"mt-0.5 whitespace-pre-wrap\">{message.body}</p>}\n </div>\n );\n }\n\n const refused = local?.state === \"rejected\" || local?.state === \"failed\";\n const waiting = local?.state === \"pending\" || local?.state === \"unconfirmed\";\n const canAct = !local && !message.deleted;\n\n return (\n <div\n data-message-id={message.id}\n data-state={local?.state ?? (message.deleted ? \"deleted\" : \"stored\")}\n className={cn(\"group flex max-w-[85%] flex-col gap-1 sm:max-w-[75%]\", animateEntry && \"motion-safe:animate-[bitsnap-chat-message-enter_190ms_ease-out]\", mine ? \"items-end self-end\" : \"items-start self-start\")}\n >\n <div className={cn(\"flex items-end gap-1\", mine && \"flex-row-reverse\")}>\n <div\n className={cn(\n \"min-w-0 rounded-2xl px-3.5 py-2 text-sm leading-relaxed transition-opacity duration-200 ease-out motion-reduce:transition-none\",\n message.deleted\n ? \"border border-dashed border-neutral-300 bg-transparent italic text-neutral-500 dark:border-neutral-700 dark:text-neutral-400\"\n : mine\n ? \"bg-neutral-900 text-white dark:bg-neutral-100 dark:text-neutral-900\"\n : \"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50\",\n waiting && \"opacity-70\",\n refused && \"border border-red-600 dark:border-red-400\",\n )}\n >\n {message.deleted ? (\n texts.deletedPlaceholder\n ) : (\n <>\n <p className=\"whitespace-pre-wrap break-words\">{message.body}</p>\n {!message.deleted && message.attachmentName && (\n message.attachmentKind === \"image\" ? (\n attachmentError ? <button type=\"button\" onClick={retryAttachment} className=\"mt-2 text-xs underline\">{texts.attachmentLoadFailed}</button>\n : attachmentUrl ? <a href={attachmentUrl} onClick={openAttachment} target=\"_blank\" rel=\"noreferrer\"><img src={attachmentUrl} onError={() => { if (Date.now() >= attachmentExpiresAt.current) retryAttachment(); else { setAttachmentUrl(undefined); setAttachmentError(true); } }} alt={message.attachmentName} className=\"mt-2 max-h-48 rounded-lg object-cover\" /></a>\n : <div role=\"status\" aria-label={texts.loading} className=\"mt-2 h-32 w-48 animate-pulse rounded-lg bg-current/10 motion-reduce:animate-none\" />\n ) : (\n <a href={attachmentUrl ?? \"#\"} onClick={openAttachment} download={message.attachmentName} className=\"mt-2 flex max-w-56 items-center gap-2 rounded-lg border border-neutral-300/60 px-2 py-1.5 text-xs dark:border-neutral-700\" >\n <Paperclip className=\"size-3.5 shrink-0\" aria-hidden=\"true\" /><span className=\"min-w-0 flex-1 truncate\">{message.attachmentName}</span><span>{formatBytes(message.attachmentSize)}</span>\n </a>\n )\n )}\n {attachmentError && message.attachmentKind !== \"image\" && <button type=\"button\" onClick={retryAttachment} className=\"mt-1 text-xs underline\">{texts.attachmentLoadFailed}</button>}\n {message.attachmentName && local?.state === \"pending\" && local.uploadProgress != null && <p className=\"mt-1 text-[11px] opacity-75\">{texts.uploading} {local.uploadProgress}%</p>}\n </>\n )}\n </div>\n {canAct && (\n <button\n type=\"button\"\n aria-label={texts.messageActions}\n aria-expanded={actions}\n onClick={() => {\n setActions((open) => !open);\n setConfirmDelete(false);\n }}\n className={cn(\n \"inline-flex size-11 shrink-0 items-center justify-center rounded-md text-neutral-500 opacity-100 hover:bg-neutral-100 sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100 dark:text-neutral-400 dark:hover:bg-neutral-800\",\n actions && \"sm:opacity-100\",\n focusRing,\n )}\n >\n <MoreHorizontal className=\"size-4\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n <p className=\"flex items-center gap-1.5 px-1 text-[11px] text-neutral-500 dark:text-neutral-400\">\n {message.pinned && (\n <span className=\"inline-flex items-center gap-0.5\">\n <Pin className=\"size-3\" aria-hidden=\"true\" />\n {texts.pinned}\n </span>\n )}\n <span>{authorLabel(message, mine, kind, texts)}</span>\n <span aria-hidden=\"true\">·</span>\n {waiting ? (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"size-3\" aria-hidden=\"true\" />\n {local?.state === \"pending\" ? texts.pending : texts.notConfirmed}\n </span>\n ) : (\n <time dateTime={message.createdAt}>{formatMessageTime(message.createdAt, locale)}</time>\n )}\n </p>\n {refused && (\n <div role=\"alert\" className={cn(\"flex flex-col gap-1\", mine ? \"items-end\" : \"items-start\")}>\n <p className=\"flex items-start gap-1 text-xs text-red-700 dark:text-red-400\">\n <AlertCircle className=\"mt-px size-3.5 shrink-0\" aria-hidden=\"true\" />\n {rejectionText(texts, local?.reason ?? \"\", local?.retryAfter)}\n </p>\n <div className=\"flex flex-wrap gap-1\">\n {local?.state === \"failed\" || local?.reason === \"cooldown\" ? (\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onRetry}>\n {texts.retrySend}\n </button>\n ) : (\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onEdit}>\n {texts.editMessage}\n </button>\n )}\n <button type=\"button\" className={cn(quietButton, \"text-xs\")} onClick={onDiscard}>\n {texts.dismiss}\n </button>\n </div>\n </div>\n )}\n {actions && canAct && (\n <div className=\"flex flex-wrap gap-1\">\n {mine ? (\n <button\n type=\"button\"\n className={cn(quietButton, \"text-xs text-red-700 dark:text-red-400\")}\n onClick={async () => {\n if (!confirmDelete) {\n setConfirmDelete(true);\n return;\n }\n setActions(false);\n await onDelete().catch(() => undefined);\n }}\n >\n {confirmDelete ? texts.deleteConfirm : texts.deleteMessage}\n </button>\n ) : (\n <button\n type=\"button\"\n className={cn(quietButton, \"text-xs\")}\n onClick={() => {\n setActions(false);\n onReport();\n }}\n >\n {texts.reportMessage}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction composerBlock(c: ConversationState, kind: \"thread\" | \"channel\", texts: ChatTexts): string | undefined {\n if (kind === \"thread\" && c.thread?.blocked) return texts.blockedNotice;\n if (kind === \"thread\" && c.thread?.composerLocked) return texts.composerExpired;\n if (kind === \"channel\" && c.channel) {\n if (c.channel.role === \"muted\") return texts.composerMuted;\n if (c.channel.whoCanPost === \"creator\" && c.channel.role !== \"creator\")\n return texts.composerCreatorOnly;\n }\n return undefined;\n}\n\nfunction Composer({\n conversation: c,\n texts,\n kind,\n draft,\n setDraft,\n}: {\n conversation: ConversationState & ConversationActions;\n texts: ChatTexts;\n kind: \"thread\" | \"channel\";\n draft: string;\n setDraft: (value: string) => void;\n}) {\n const id = useId();\n const area = useRef<HTMLTextAreaElement>(null);\n const fileInput = useRef<HTMLInputElement>(null);\n const previewRemoveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const [selectedFile, setSelectedFile] = useState<File>();\n const [previewLeaving, setPreviewLeaving] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const [accept, setAccept] = useState(\".pdf,.zip,.txt,.docx,.xlsx\");\n const blocked = composerBlock(c, kind, texts);\n\n useEffect(() => () => {\n if (previewRemoveTimer.current) clearTimeout(previewRemoveTimer.current);\n }, []);\n\n const chooseFile = (file: File) => {\n if (previewRemoveTimer.current) clearTimeout(previewRemoveTimer.current);\n previewRemoveTimer.current = null;\n setPreviewLeaving(false);\n setSelectedFile(file);\n };\n\n const removeFile = () => {\n if (!selectedFile || previewLeaving) return;\n setPreviewLeaving(true);\n previewRemoveTimer.current = setTimeout(() => {\n setSelectedFile(undefined);\n setPreviewLeaving(false);\n previewRemoveTimer.current = null;\n }, 140);\n };\n\n useLayoutEffect(() => {\n const el = area.current;\n if (!el) return;\n el.style.height = \"auto\";\n el.style.height = `${Math.min(el.scrollHeight, 160)}px`;\n }, [draft]);\n\n if (blocked) {\n return (\n <p data-testid=\"chat-composer-blocked\" className=\"border-t border-neutral-200 px-4 py-3 text-sm text-neutral-600 dark:border-neutral-800 dark:text-neutral-400\">\n {blocked}\n </p>\n );\n }\n\n const submit = (event?: FormEvent) => {\n event?.preventDefault();\n const text = draft.trim();\n if (!text && !selectedFile) return;\n setDraft(\"\");\n setSelectedFile(undefined);\n void c.send(text, selectedFile);\n requestAnimationFrame(() => area.current?.focus());\n };\n\n const onKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n if (event.key === \"Enter\" && !event.shiftKey && !event.nativeEvent.isComposing) {\n event.preventDefault();\n submit();\n }\n };\n\n return (\n <form onSubmit={submit} className=\"border-t border-neutral-200 px-3 pb-3 pt-2 dark:border-neutral-800\">\n {c.messages.length === 0 && (\n <p className=\"pb-2 text-xs text-neutral-500 dark:text-neutral-400\">{texts.privacyNote}</p>\n )}\n {selectedFile && (\n <div className={cn(\"mb-2 flex max-w-sm items-center gap-2 rounded-lg border border-neutral-300 bg-neutral-50 px-3 py-2 text-xs text-neutral-800 dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-100\", previewLeaving ? \"chat-preview-exit\" : \"chat-preview-enter\")}>\n <span className=\"min-w-0 flex-1 truncate\">{selectedFile.name}</span>\n <span className=\"text-neutral-500 dark:text-neutral-400\">{formatBytes(selectedFile.size)}</span>\n <button type=\"button\" aria-label={texts.removeAttachment} onClick={removeFile} disabled={previewLeaving} className=\"rounded p-1 hover:bg-neutral-200 disabled:cursor-default dark:hover:bg-neutral-800\"><X className=\"size-3.5\" /></button>\n </div>\n )}\n <div className=\"flex items-end gap-2\">\n <div className=\"relative\">\n <button type=\"button\" aria-label={texts.addAttachment} aria-expanded={menuOpen} onClick={() => setMenuOpen((open) => !open)} className={cn(quietButton, \"h-11 w-11 justify-center px-0 transition-transform duration-150 motion-safe:active:scale-95 motion-reduce:transition-none\")}>\n <Plus className=\"size-5\" aria-hidden=\"true\" />\n </button>\n {menuOpen && <div role=\"menu\" className=\"absolute bottom-full left-0 z-20 mb-2 min-w-36 rounded-lg border border-neutral-200 bg-white p-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-900\">\n <button type=\"button\" role=\"menuitem\" className=\"block w-full rounded px-3 py-2 text-left text-sm hover:bg-neutral-100 dark:hover:bg-neutral-800\" onClick={() => { setAccept(\"image/jpeg,image/png,image/webp,image/gif\"); if (fileInput.current) { fileInput.current.accept = \"image/jpeg,image/png,image/webp,image/gif\"; fileInput.current.click(); } setMenuOpen(false); }}>{texts.photo}</button>\n <button type=\"button\" role=\"menuitem\" className=\"block w-full rounded px-3 py-2 text-left text-sm hover:bg-neutral-100 dark:hover:bg-neutral-800\" onClick={() => { setAccept(\".pdf,.zip,.txt,.docx,.xlsx\"); if (fileInput.current) { fileInput.current.accept = \".pdf,.zip,.txt,.docx,.xlsx\"; fileInput.current.click(); } setMenuOpen(false); }}>{texts.file}</button>\n </div>}\n <input ref={fileInput} hidden type=\"file\" accept={accept} onChange={(event) => { const file = event.target.files?.[0]; if (file) chooseFile(file); event.currentTarget.value = \"\"; }} />\n </div>\n <label htmlFor={id} className=\"sr-only\">\n {texts.composerLabel}\n </label>\n <textarea\n id={id}\n ref={area}\n rows={1}\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={onKeyDown}\n placeholder={texts.composerPlaceholder}\n className=\"max-h-40 min-h-11 flex-1 resize-none rounded-xl border border-neutral-300 bg-white px-3 py-2.5 text-sm text-neutral-900 placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-950 dark:text-neutral-50 dark:placeholder:text-neutral-400\"\n />\n <button\n type=\"submit\"\n aria-label={texts.send}\n disabled={draft.trim().length === 0 && !selectedFile}\n className={cn(primaryButton, \"w-11 px-0 transition-[transform,opacity] duration-150 ease-out motion-safe:active:scale-95 disabled:scale-100 motion-reduce:transition-none\")}\n >\n <SendHorizontal className=\"size-5\" aria-hidden=\"true\" />\n </button>\n </div>\n </form>\n );\n}\n\nconst REASONS: ChatReportReason[] = [\"spam\", \"abuse\", \"illegal\", \"other\"];\n\nfunction ReportForm({\n texts,\n messageId,\n onCancel,\n onSubmit,\n}: {\n texts: ChatTexts;\n messageId?: string;\n onCancel: () => void;\n onSubmit: (reason: ChatReportReason, note: string) => Promise<void>;\n}) {\n const [reason, setReason] = useState<ChatReportReason>(\"spam\");\n const [note, setNote] = useState(\"\");\n const [sending, setSending] = useState(false);\n const [failed, setFailed] = useState(false);\n const noteId = useId();\n const label: Record<ChatReportReason, string> = {\n spam: texts.reasonSpam,\n abuse: texts.reasonAbuse,\n illegal: texts.reasonIllegal,\n other: texts.reasonOther,\n };\n return (\n <form\n data-testid=\"chat-report\"\n className=\"flex flex-col gap-3 border-t border-neutral-200 p-4 dark:border-neutral-800\"\n onSubmit={async (event) => {\n event.preventDefault();\n setSending(true);\n setFailed(false);\n try {\n await onSubmit(reason, note.trim());\n } catch {\n setFailed(true);\n } finally {\n setSending(false);\n }\n }}\n >\n <p className=\"text-sm font-semibold\">\n {messageId ? texts.reportTitle : texts.reportConversationTitle}\n </p>\n <fieldset className=\"flex flex-col gap-1\">\n <legend className=\"mb-1 text-xs text-neutral-600 dark:text-neutral-400\">{texts.reportReason}</legend>\n {REASONS.map((value) => (\n <label key={value} className=\"flex min-h-9 items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n name=\"chat-report-reason\"\n value={value}\n checked={reason === value}\n onChange={() => setReason(value)}\n className=\"size-4 accent-neutral-900 dark:accent-white\"\n />\n {label[value]}\n </label>\n ))}\n </fieldset>\n <div className=\"flex flex-col gap-1\">\n <label htmlFor={noteId} className=\"text-xs text-neutral-600 dark:text-neutral-400\">\n {texts.reportNote}\n </label>\n <textarea\n id={noteId}\n rows={2}\n maxLength={1000}\n value={note}\n onChange={(event) => setNote(event.target.value)}\n className=\"resize-none rounded-xl border border-neutral-300 bg-white px-3 py-2 text-sm focus-visible:border-neutral-500 focus-visible:outline-none dark:border-neutral-700 dark:bg-neutral-950\"\n />\n </div>\n {failed && (\n <p role=\"alert\" className=\"text-sm text-red-700 dark:text-red-400\">\n {texts.reportError}\n </p>\n )}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <button type=\"button\" className={quietButton} onClick={onCancel}>\n {texts.reportCancel}\n </button>\n <button type=\"submit\" className={primaryButton} disabled={sending} aria-busy={sending || undefined}>\n {texts.reportSend}\n </button>\n </div>\n </form>\n );\n}\n\nfunction chatBuyError(error: unknown, locale: string, fallback: string): string {\n const code = error instanceof Error ? error.message : \"\";\n const en = locale.toLowerCase().startsWith(\"en\");\n const messages: Record<string, [string, string]> = {\n \"amount-below-minimum\": [\"Kwota jest niższa od minimalnej.\", \"The amount is below the minimum.\"],\n \"amount-above-maximum\": [\"Kwota przekracza maksymalną.\", \"The amount is above the maximum.\"],\n \"amount-below-gateway-minimum\": [\"Kwota jest za niska dla dostępnych płatności.\", \"The amount is too low for available payment methods.\"],\n \"amount-invalid\": [\"Podaj prawidłową kwotę.\", \"Enter a valid amount.\"],\n \"email-verification-required\": [\"Potwierdź adres e-mail, aby odebrać pakiet.\", \"Verify your email to claim this pack.\"],\n \"free-claim-exists\": [\"Ten adres e-mail wykorzystał już limit darmowych odbiorów.\", \"This email has reached the free claim limit.\"],\n \"free-order-not-supported\": [\"Nie można zrealizować tego darmowego zamówienia.\", \"This free order cannot be completed.\"],\n \"captcha-required\": [\"Potwierdź, że nie jesteś robotem.\", \"Complete the security check.\"],\n \"captcha-invalid\": [\"Weryfikacja wygasła. Spróbuj ponownie.\", \"The security check expired. Try again.\"],\n \"rate-limited\": [\"Zbyt wiele prób. Spróbuj ponownie za chwilę.\", \"Too many attempts. Try again shortly.\"],\n \"incorrect-code\": [\"Nieprawidłowy kod. Sprawdź wiadomość i spróbuj ponownie.\", \"The code is incorrect. Check your email and try again.\"],\n \"too-many-attempts\": [\"Poproś o nowy kod za kilka minut.\", \"Request a new code in a few minutes.\"],\n unavailable: [\"Weryfikacja jest chwilowo niedostępna.\", \"Verification is temporarily unavailable.\"],\n };\n return messages[code]?.[en ? 1 : 0] ?? fallback;\n}\n","import { getLocale } from \"../components/checkout/locale\";\nimport { resolveTexts, type LocaleTables } from \"../lib/texts\";\n\n/**\n * Every string of the chat (panel \"Wiadomości\", `ChatThread`, `Channel`).\n * Pass a partial object through the `texts` prop to override any of them,\n * whatever the locale. Placeholders like `{count}` stay in overrides.\n *\n * Counts use three forms (`One`, `Few`, `Many`) for Polish; English uses\n * `One` and `Many`.\n */\nexport type ChatTexts = {\n inboxHeading: string;\n inboxIntro: string;\n inboxEmpty: string;\n inboxError: string;\n loading: string;\n retry: string;\n back: string;\n selectConversation: string;\n kindChat: string;\n kindChannel: string;\n startSection: string;\n startChat: string;\n openChannel: string;\n channelNotReady: string;\n chatFallbackTitle: string;\n channelFallbackTitle: string;\n /** Placeholder `{count}`. */\n repliesLeftOne: string;\n repliesLeftFew: string;\n repliesLeftMany: string;\n noRepliesLeft: string;\n /** Placeholder `{date}`. */\n expiresOn: string;\n /** Placeholder `{date}`. */\n expiredOn: string;\n /** Placeholder `{hours}`. */\n responseHours: string;\n /** Placeholder `{count}`. */\n unread: string;\n /** Placeholder `{count}`. */\n members: string;\n mutedBadge: string;\n noMessagesYet: string;\n emptyThread: string;\n emptyChannel: string;\n privacyNote: string;\n loadOlder: string;\n newMessages: string;\n blockedNotice: string;\n addAttachment: string;\n photo: string;\n file: string;\n removeAttachment: string;\n uploading: string;\n attachmentLoadFailed: string;\n you: string;\n creator: string;\n systemLabel: string;\n pinned: string;\n deletedPlaceholder: string;\n attachment: string;\n composerLabel: string;\n composerPlaceholder: string;\n send: string;\n composerExpired: string;\n composerCreatorOnly: string;\n composerMuted: string;\n composerReadOnly: string;\n lockRepliesTitle: string;\n lockRepliesBody: string;\n lockExpiredTitle: string;\n lockExpiredBody: string;\n lockThreadTitle: string;\n lockThreadBody: string;\n lockChannelTitle: string;\n lockChannelBody: string;\n buyPack: string;\n buyAccess: string;\n buying: string;\n buyError: string;\n pending: string;\n notConfirmed: string;\n rejectedLink: string;\n rejectedBannedWord: string;\n rejectedMedia: string;\n /** Placeholder `{seconds}`. */\n rejectedCooldown: string;\n rejectedBlocked: string;\n rejectedMuted: string;\n rejectedReadOnly: string;\n rejectedPackEmpty: string;\n rejectedForbidden: string;\n rejectedGeneric: string;\n failedNetwork: string;\n editMessage: string;\n retrySend: string;\n dismiss: string;\n messageActions: string;\n deleteMessage: string;\n deleteConfirm: string;\n reportMessage: string;\n conversationActions: string;\n muteThread: string;\n unmuteThread: string;\n muteError: string;\n reportConversation: string;\n reportTitle: string;\n reportConversationTitle: string;\n reportReason: string;\n reasonSpam: string;\n reasonAbuse: string;\n reasonIllegal: string;\n reasonOther: string;\n reportNote: string;\n reportSend: string;\n reportCancel: string;\n reportSent: string;\n reportError: string;\n liveLabel: string;\n pollingLabel: string;\n errorNoToken: string;\n errorUnauthorized: string;\n errorPlan: string;\n errorNotFound: string;\n errorUnavailable: string;\n errorGeneric: string;\n};\n\nexport const CHAT_TEXT_TABLES: LocaleTables<keyof ChatTexts> = {\n pl: {\n inboxHeading: \"Wiadomości\",\n inboxIntro: \"Rozmowy z twórcami i kanały, do których należysz.\",\n inboxEmpty:\n \"Nie masz jeszcze rozmów. Po zakupie pakietu konsultacji lub dostępu do społeczności rozmowa pojawi się tutaj.\",\n inboxError: \"Nie udało się wczytać wiadomości.\",\n loading: \"Wczytywanie…\",\n retry: \"Spróbuj ponownie\",\n back: \"Wróć\",\n selectConversation: \"Wybierz rozmowę z listy.\",\n kindChat: \"Czat\",\n kindChannel: \"Kanał\",\n startSection: \"Do rozpoczęcia\",\n startChat: \"Rozpocznij rozmowę\",\n openChannel: \"Otwórz kanał\",\n channelNotReady: \"Twórca jeszcze nie otworzył tego kanału.\",\n chatFallbackTitle: \"Czat z twórcą\",\n channelFallbackTitle: \"Kanał\",\n repliesLeftOne: \"Została {count} odpowiedź\",\n repliesLeftFew: \"Zostały {count} odpowiedzi\",\n repliesLeftMany: \"Zostało {count} odpowiedzi\",\n noRepliesLeft: \"Brak odpowiedzi w pakiecie\",\n expiresOn: \"Ważny do {date}\",\n expiredOn: \"Wygasł {date}\",\n responseHours: \"Odpowiedź w ciągu {hours} h\",\n unread: \"Nieprzeczytane: {count}\",\n members: \"Członkowie: {count}\",\n mutedBadge: \"Wyciszony\",\n noMessagesYet: \"Brak wiadomości\",\n emptyThread:\n \"Napisz pierwszą wiadomość. Twórca odpowie w ramach Twojego pakietu.\",\n emptyChannel: \"W kanale nie ma jeszcze wiadomości.\",\n privacyNote:\n \"Wiadomości czyta druga strona rozmowy. Bitsnap przechowuje je przez 90 dni.\",\n loadOlder: \"Wcześniejsze wiadomości\",\n newMessages: \"Nowe wiadomości ↓\",\n blockedNotice: \"Twórca zablokował możliwość wysyłania wiadomości w tej rozmowie.\",\n addAttachment: \"Dodaj załącznik\",\n photo: \"Zdjęcie\",\n file: \"Plik\",\n removeAttachment: \"Usuń załącznik\",\n uploading: \"Wysyłanie pliku\",\n attachmentLoadFailed: \"Nie udało się wczytać — ponów\",\n you: \"Ty\",\n creator: \"Twórca\",\n systemLabel: \"Wiadomość automatyczna\",\n pinned: \"Przypięta\",\n deletedPlaceholder: \"Wiadomość została usunięta\",\n attachment: \"Załącznik\",\n composerLabel: \"Wiadomość\",\n composerPlaceholder: \"Napisz wiadomość…\",\n send: \"Wyślij\",\n composerExpired: \"Pakiet wygasł, więc nie możesz już pisać w tej rozmowie.\",\n composerCreatorOnly: \"W tym kanale pisze tylko twórca.\",\n composerMuted: \"Masz wyciszone pisanie w tym kanale.\",\n composerReadOnly: \"Ten dostęp pozwala tylko czytać.\",\n lockRepliesTitle: \"Odpowiedzi w pakiecie się skończyły\",\n lockRepliesBody:\n \"Możesz dalej pisać, ale twórca odpowie dopiero po zakupie kolejnego pakietu.\",\n lockExpiredTitle: \"Pakiet wygasł\",\n lockExpiredBody:\n \"Rozmowa zostaje do wglądu. Kup nowy pakiet, aby ją kontynuować.\",\n lockThreadTitle: \"Nie masz pakietu odpowiedzi\",\n lockThreadBody: \"Kup pakiet, aby napisać do twórcy.\",\n lockChannelTitle: \"Kanał dla członków\",\n lockChannelBody: \"Kup dostęp, aby czytać i pisać w tym kanale.\",\n buyPack: \"Kup pakiet\",\n buyAccess: \"Kup dostęp\",\n buying: \"Dodawanie do koszyka…\",\n buyError: \"Nie udało się dodać do koszyka. Spróbuj ponownie.\",\n pending: \"Wysyłanie…\",\n notConfirmed: \"Czekamy na potwierdzenie wysłania\",\n rejectedLink: \"Linki są w tym kanale zablokowane. Usuń link i wyślij ponownie.\",\n rejectedBannedWord:\n \"Wiadomość zawiera słowo zablokowane w tym kanale. Popraw ją i wyślij ponownie.\",\n rejectedMedia: \"Załączniki są w tym kanale zablokowane.\",\n rejectedCooldown: \"Piszesz za szybko. Spróbuj ponownie za {seconds} s.\",\n rejectedBlocked: \"Twórca zablokował Ci możliwość pisania w tym sklepie.\",\n rejectedMuted: \"Masz wyciszone pisanie w tym kanale.\",\n rejectedReadOnly: \"Ten dostęp pozwala tylko czytać.\",\n rejectedPackEmpty: \"Pakiet nie ma już odpowiedzi.\",\n rejectedForbidden: \"Nie możesz pisać w tej rozmowie.\",\n rejectedGeneric: \"Nie udało się wysłać wiadomości.\",\n failedNetwork: \"Brak połączenia. Wiadomość nie została wysłana.\",\n editMessage: \"Popraw\",\n retrySend: \"Wyślij ponownie\",\n dismiss: \"Odrzuć\",\n messageActions: \"Opcje wiadomości\",\n deleteMessage: \"Usuń wiadomość\",\n deleteConfirm: \"Na pewno usunąć?\",\n reportMessage: \"Zgłoś wiadomość\",\n conversationActions: \"Opcje rozmowy\",\n muteThread: \"Wycisz powiadomienia\",\n unmuteThread: \"Włącz powiadomienia\",\n muteError: \"Nie udało się zmienić powiadomień.\",\n reportConversation: \"Zgłoś rozmowę\",\n reportTitle: \"Zgłoś wiadomość\",\n reportConversationTitle: \"Zgłoś rozmowę\",\n reportReason: \"Powód zgłoszenia\",\n reasonSpam: \"Spam\",\n reasonAbuse: \"Nękanie lub obraźliwe treści\",\n reasonIllegal: \"Treści niezgodne z prawem\",\n reasonOther: \"Inny powód\",\n reportNote: \"Opis (opcjonalnie)\",\n reportSend: \"Wyślij zgłoszenie\",\n reportCancel: \"Anuluj\",\n reportSent: \"Dziękujemy. Zgłoszenie trafiło do moderacji.\",\n reportError: \"Nie udało się wysłać zgłoszenia. Spróbuj ponownie.\",\n liveLabel: \"Nowe wiadomości pojawiają się na bieżąco\",\n pollingLabel: \"Nowe wiadomości odświeżają się co kilka sekund\",\n errorNoToken: \"Zaloguj się w panelu klienta, aby zobaczyć rozmowę.\",\n errorUnauthorized: \"Sesja wygasła. Zaloguj się ponownie.\",\n errorPlan: \"Czat nie jest dostępny w tym sklepie.\",\n errorNotFound: \"Nie znaleziono tej rozmowy.\",\n errorUnavailable: \"Czat jest chwilowo niedostępny. Spróbuj za chwilę.\",\n errorGeneric: \"Nie udało się wczytać rozmowy.\",\n },\n en: {\n inboxHeading: \"Messages\",\n inboxIntro: \"Your conversations with creators and the channels you belong to.\",\n inboxEmpty:\n \"No conversations yet. When you buy a consultation pack or community access, it shows up here.\",\n inboxError: \"Your messages could not be loaded.\",\n loading: \"Loading…\",\n retry: \"Try again\",\n back: \"Back\",\n selectConversation: \"Pick a conversation from the list.\",\n kindChat: \"Chat\",\n kindChannel: \"Channel\",\n startSection: \"Ready to start\",\n startChat: \"Start the conversation\",\n openChannel: \"Open the channel\",\n channelNotReady: \"The creator has not opened this channel yet.\",\n chatFallbackTitle: \"Chat with the creator\",\n channelFallbackTitle: \"Channel\",\n repliesLeftOne: \"{count} reply left\",\n repliesLeftFew: \"{count} replies left\",\n repliesLeftMany: \"{count} replies left\",\n noRepliesLeft: \"No replies left in your pack\",\n expiresOn: \"Valid until {date}\",\n expiredOn: \"Expired on {date}\",\n responseHours: \"Replies within {hours} h\",\n unread: \"Unread: {count}\",\n members: \"Members: {count}\",\n mutedBadge: \"Muted\",\n noMessagesYet: \"No messages yet\",\n emptyThread: \"Write the first message. The creator replies within your pack.\",\n emptyChannel: \"There are no messages in this channel yet.\",\n privacyNote:\n \"The other side of the conversation reads your messages. Bitsnap keeps them for 90 days.\",\n loadOlder: \"Earlier messages\",\n newMessages: \"New messages ↓\",\n blockedNotice: \"The creator has blocked you from sending messages in this conversation.\",\n addAttachment: \"Add attachment\",\n photo: \"Photo\",\n file: \"File\",\n removeAttachment: \"Remove attachment\",\n uploading: \"Uploading file\",\n attachmentLoadFailed: \"Could not load — retry\",\n you: \"You\",\n creator: \"Creator\",\n systemLabel: \"Automatic message\",\n pinned: \"Pinned\",\n deletedPlaceholder: \"This message was deleted\",\n attachment: \"Attachment\",\n composerLabel: \"Message\",\n composerPlaceholder: \"Write a message…\",\n send: \"Send\",\n composerExpired: \"Your pack has expired, so you can no longer write here.\",\n composerCreatorOnly: \"Only the creator posts in this channel.\",\n composerMuted: \"You are muted in this channel.\",\n composerReadOnly: \"This access is read only.\",\n lockRepliesTitle: \"Your pack has no replies left\",\n lockRepliesBody:\n \"You can keep writing, but the creator can reply only after you buy another pack.\",\n lockExpiredTitle: \"Your pack has expired\",\n lockExpiredBody:\n \"You can still read the conversation. Buy a new pack to continue it.\",\n lockThreadTitle: \"You have no reply pack\",\n lockThreadBody: \"Buy a pack to write to the creator.\",\n lockChannelTitle: \"Members-only channel\",\n lockChannelBody: \"Buy access to read and post in this channel.\",\n buyPack: \"Buy a pack\",\n buyAccess: \"Buy access\",\n buying: \"Adding to cart…\",\n buyError: \"Could not add it to the cart. Try again.\",\n pending: \"Sending…\",\n notConfirmed: \"Waiting for the send to be confirmed\",\n rejectedLink: \"Links are blocked in this channel. Remove the link and send again.\",\n rejectedBannedWord:\n \"Your message contains a word blocked in this channel. Edit it and send again.\",\n rejectedMedia: \"Attachments are blocked in this channel.\",\n rejectedCooldown: \"You are posting too fast. Try again in {seconds} s.\",\n rejectedBlocked: \"The creator has blocked you from posting in this shop.\",\n rejectedMuted: \"You are muted in this channel.\",\n rejectedReadOnly: \"This access is read only.\",\n rejectedPackEmpty: \"The pack has no replies left.\",\n rejectedForbidden: \"You cannot post in this conversation.\",\n rejectedGeneric: \"Your message could not be sent.\",\n failedNetwork: \"No connection. Your message was not sent.\",\n editMessage: \"Edit\",\n retrySend: \"Send again\",\n dismiss: \"Discard\",\n messageActions: \"Message options\",\n deleteMessage: \"Delete message\",\n deleteConfirm: \"Delete for sure?\",\n reportMessage: \"Report message\",\n conversationActions: \"Conversation options\",\n muteThread: \"Mute notifications\",\n unmuteThread: \"Unmute notifications\",\n muteError: \"Notifications could not be changed.\",\n reportConversation: \"Report conversation\",\n reportTitle: \"Report message\",\n reportConversationTitle: \"Report conversation\",\n reportReason: \"Reason\",\n reasonSpam: \"Spam\",\n reasonAbuse: \"Harassment or abusive content\",\n reasonIllegal: \"Illegal content\",\n reasonOther: \"Something else\",\n reportNote: \"Details (optional)\",\n reportSend: \"Send report\",\n reportCancel: \"Cancel\",\n reportSent: \"Thank you. Your report went to moderation.\",\n reportError: \"Your report could not be sent. Try again.\",\n liveLabel: \"New messages appear live\",\n pollingLabel: \"New messages refresh every few seconds\",\n errorNoToken: \"Log in to the customer panel to see this conversation.\",\n errorUnauthorized: \"Your session has expired. Log in again.\",\n errorPlan: \"Chat is not available in this shop.\",\n errorNotFound: \"This conversation was not found.\",\n errorUnavailable: \"Chat is unavailable right now. Try again in a moment.\",\n errorGeneric: \"The conversation could not be loaded.\",\n },\n};\n\n/** Chat strings for a locale (default: the library locale), with overrides. */\nexport function getChatTexts(\n overrides?: Partial<ChatTexts>,\n locale: string = getLocale(),\n): ChatTexts {\n return resolveTexts(CHAT_TEXT_TABLES, locale, overrides);\n}\n\n/** Fills `{name}` placeholders. */\nexport function fill(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\n/** \"Zostały 3 odpowiedzi\" with the plural form of the locale. */\nexport function repliesLeftText(\n texts: ChatTexts,\n count: number,\n locale: string = getLocale(),\n): string {\n if (count <= 0) return texts.noRepliesLeft;\n let category: Intl.LDMLPluralRule = \"other\";\n try {\n category = new Intl.PluralRules(locale).select(count);\n } catch {\n // Unknown locale: \"other\".\n }\n const template =\n category === \"one\"\n ? texts.repliesLeftOne\n : category === \"few\"\n ? texts.repliesLeftFew\n : texts.repliesLeftMany;\n return fill(template, { count });\n}\n\n/** A short local date, `12.12.2026` / `12/12/2026`. */\nexport function formatChatDate(iso: string, locale: string = getLocale()): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n return new Intl.DateTimeFormat(locale, {\n day: \"numeric\",\n month: \"numeric\",\n year: \"numeric\",\n }).format(date);\n}\n\n/** The time for today's messages, the date otherwise. */\nexport function formatMessageTime(\n iso: string,\n locale: string = getLocale(),\n now: Date = new Date(),\n): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return \"\";\n const sameDay = date.toDateString() === now.toDateString();\n return new Intl.DateTimeFormat(\n locale,\n sameDay\n ? { hour: \"2-digit\", minute: \"2-digit\" }\n : { day: \"numeric\", month: \"short\", hour: \"2-digit\", minute: \"2-digit\" },\n ).format(date);\n}\n\n/** The text of a refused send, by the error code or live reject reason. */\nexport function rejectionText(\n texts: ChatTexts,\n reason: string,\n retryAfter?: number,\n): string {\n switch (reason) {\n case \"moderation_link\":\n return texts.rejectedLink;\n case \"moderation_banned_word\":\n return texts.rejectedBannedWord;\n case \"moderation_media\":\n return texts.rejectedMedia;\n case \"cooldown\":\n return fill(texts.rejectedCooldown, { seconds: retryAfter ?? 30 });\n case \"blocked\":\n return texts.rejectedBlocked;\n case \"muted\":\n return texts.rejectedMuted;\n case \"read_only\":\n return texts.rejectedReadOnly;\n case \"pack_empty\":\n case \"replies_exhausted\":\n return texts.rejectedPackEmpty;\n case \"forbidden\":\n case \"locked\":\n return texts.rejectedForbidden;\n case \"network\":\n case \"unavailable\":\n return texts.failedNetwork;\n default:\n return texts.rejectedGeneric;\n }\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { ChatError, type ChatClient } from \"./client\";\nimport { startLive, type NatsModule } from \"./realtime\";\nimport type {\n ChatChannelSummary,\n ChatLiveMode,\n ChatMessage,\n ChatRealtimeEvent,\n ChatReportReason,\n ChatThreadSummary,\n} from \"./types\";\n\n/** What to open: a pack thread (by product or id) or a channel. */\nexport type ConversationTarget =\n | { kind: \"thread\"; threadId?: string; productId?: string }\n | { kind: \"channel\"; channelId: string; productId?: string };\n\n/** The client-side state of a message the service has not stored. */\nexport type LocalMessageState = {\n state: \"pending\" | \"unconfirmed\" | \"rejected\" | \"failed\";\n /** Error code or live reject reason. */\n reason?: string;\n retryAfter?: number;\n /** An earlier send of this message may have been queued: retries keep its id. */\n keepId?: boolean;\n uploadProgress?: number;\n};\n\nexport type ConversationMessage = ChatMessage & { local?: LocalMessageState; retryFile?: File };\n\n/**\n * Why the buyer sees the lock: `replies` (no creator replies left, writing\n * still works), `expired` (every pack expired), `no-pack` (no thread and no\n * pack for the product), `no-access` (a paid channel without a grant).\n */\nexport type ConversationLock = {\n reason: \"replies\" | \"expired\" | \"no-pack\" | \"no-access\";\n productId?: string;\n priceCents?: number;\n amount?: number;\n priceType?: \"one_time\" | \"pay_what_you_want\" | \"free\";\n minimumCents?: number;\n maximumCents?: number;\n freeClaimLimit?: number;\n welcome?: string;\n};\n\nexport type ConversationState = {\n status: \"loading\" | \"ready\" | \"locked\" | \"error\";\n error?: ChatError;\n threadId?: string;\n /** The caller's e-mail, lowercased. */\n me?: string;\n thread?: ChatThreadSummary;\n channel?: ChatChannelSummary;\n lock?: ConversationLock;\n /** Oldest first. */\n messages: ConversationMessage[];\n hasMore: boolean;\n loadingOlder: boolean;\n live: ChatLiveMode;\n};\n\nexport type ConversationActions = {\n send(body: string, attachment?: File): Promise<void>;\n /** Sends a failed or refused message again. */\n retry(id: string): Promise<void>;\n getAttachmentURL(message: ChatMessage): Promise<string>;\n /** Removes a message that was not stored. */\n discard(id: string): void;\n deleteMessage(id: string): Promise<void>;\n report(input: {\n messageId?: string;\n reason: ChatReportReason;\n note?: string;\n }): Promise<void>;\n setMuted(muted: boolean): Promise<void>;\n loadOlder(): Promise<void>;\n reload(): void;\n};\n\nexport type UseConversationOptions = {\n /**\n * `undefined` when there is no credential (the state is an error), `null`\n * while the credential is still being looked up (the state stays loading).\n */\n client: ChatClient | null | undefined;\n target: ConversationTarget;\n pollIntervalMs?: number;\n realtimeUrl?: string;\n loadNats?: () => Promise<NatsModule>;\n /** A pending send counts as unconfirmed after this long. Default 45 s. */\n confirmTimeoutMs?: number;\n};\n\nconst PAGE = 50;\n\nconst initial: ConversationState = {\n status: \"loading\",\n messages: [],\n hasMore: false,\n loadingOlder: false,\n live: \"off\",\n};\n\n/** A UUID v4; the service uses it as the message id, so a retry is stored once. */\nexport function newMessageId(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n const bytes = new Uint8Array(16);\n if (c && typeof c.getRandomValues === \"function\") c.getRandomValues(bytes);\n else for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/**\n * Merges messages by id, oldest first. A stored message replaces a pending\n * copy of itself (the live `message.created` or a history page after a 202);\n * messages that were refused locally stay as they are.\n */\nexport function mergeMessages(\n current: ConversationMessage[],\n incoming: ChatMessage[],\n): ConversationMessage[] {\n const byId = new Map(current.map((m) => [m.id, m] as const));\n for (const message of incoming) {\n const existing = byId.get(message.id);\n if (message.status === \"pending\") {\n // A pending answer never overwrites a stored copy.\n if (existing && !existing.local) continue;\n byId.set(message.id, { ...message, retryFile: existing?.retryFile, local: existing?.local ?? { state: \"pending\" } });\n continue;\n }\n if (existing?.local && existing.local.state !== \"pending\" && existing.local.state !== \"unconfirmed\") {\n continue;\n }\n byId.set(message.id, { ...message, local: undefined });\n }\n return [...byId.values()].sort((a, b) => {\n const at = Date.parse(a.createdAt) || 0;\n const bt = Date.parse(b.createdAt) || 0;\n return at - bt;\n });\n}\n\n/** The lock of a pack thread that the buyer can read. */\nexport function threadLock(thread: ChatThreadSummary): ConversationLock | undefined {\n if (thread.kind !== \"pack\") return undefined;\n if (thread.composerLocked) return { reason: \"expired\", productId: thread.productId };\n if (thread.repliesLeft <= 0) return { reason: \"replies\", productId: thread.productId };\n return undefined;\n}\n\nfunction lockedFrom(error: ChatError, reason: ConversationLock[\"reason\"], productId?: string): ConversationLock {\n const price = Number(error.data.price_cents);\n return {\n reason,\n productId: typeof error.data.product_id === \"string\" && error.data.product_id\n ? error.data.product_id\n : productId,\n priceCents: Number.isFinite(price) && price > 0 ? price : undefined,\n priceType: error.data.price_type === \"free\" || error.data.price_type === \"pay_what_you_want\" ? error.data.price_type : \"one_time\",\n minimumCents: Number.isFinite(Number(error.data.price_min_cents)) ? Number(error.data.price_min_cents) : undefined,\n maximumCents: Number.isFinite(Number(error.data.price_max_cents)) ? Number(error.data.price_max_cents) : undefined,\n freeClaimLimit: Number.isFinite(Number(error.data.free_claim_limit)) ? Number(error.data.free_claim_limit) : undefined,\n welcome: typeof error.data.welcome === \"string\" ? error.data.welcome : undefined,\n };\n}\n\nfunction asChatError(error: unknown): ChatError {\n return error instanceof ChatError ? error : new ChatError(\"internal_error\", 0);\n}\n\n/**\n * State and actions of one conversation: resolves the thread (opening a pack\n * thread when the buyer holds a pack and has none yet), loads the newest\n * page, listens live (or polls) and sends optimistically.\n */\nexport function useConversation(\n options: UseConversationOptions,\n): ConversationState & ConversationActions {\n const { client, target } = options;\n const [state, setState] = useState<ConversationState>(initial);\n const [generation, setGeneration] = useState(0);\n const stateRef = useRef(state);\n stateRef.current = state;\n const timers = useRef(new Set<ReturnType<typeof setTimeout>>());\n const refreshTimer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const lastRead = useRef<string>(undefined);\n\n const targetKey =\n target.kind === \"thread\"\n ? `t:${target.threadId ?? \"\"}:${target.productId ?? \"\"}`\n : `c:${target.channelId}:${target.productId ?? \"\"}`;\n\n // --- load -------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n lastRead.current = undefined;\n setState(initial);\n if (client === null) return;\n if (!client) {\n setState({ ...initial, status: \"error\", error: new ChatError(\"no_token\", 401) });\n return;\n }\n (async () => {\n try {\n const me = await client.me();\n const email = me.email.toLowerCase();\n let thread: ChatThreadSummary | undefined;\n let channel: ChatChannelSummary | undefined;\n let threadId: string;\n if (target.kind === \"thread\") {\n thread = target.threadId\n ? me.threads.find((t) => t.id === target.threadId)\n : me.threads.find((t) => t.productId === target.productId);\n if (!thread && target.productId) {\n try {\n thread = await client.openThread(target.productId);\n } catch (error) {\n const e = asChatError(error);\n if (e.code === \"locked\" || e.status === 404) {\n if (!cancelled)\n setState({\n ...initial,\n status: \"locked\",\n me: email,\n lock: lockedFrom(e, \"no-pack\", target.productId),\n });\n return;\n }\n throw e;\n }\n }\n if (!thread) throw new ChatError(\"not_found\", 404);\n threadId = thread.id;\n } else {\n channel = me.channels.find((c) => c.id === target.channelId);\n threadId = target.channelId;\n }\n\n let page;\n try {\n page = await client.messages(threadId, { limit: PAGE });\n } catch (error) {\n const e = asChatError(error);\n if (e.code === \"locked\") {\n if (!cancelled)\n setState({\n ...initial,\n status: \"locked\",\n me: email,\n thread,\n channel,\n threadId,\n lock: lockedFrom(\n e,\n target.kind === \"channel\" ? \"no-access\" : \"no-pack\",\n target.productId ?? channel?.productId ?? thread?.productId,\n ),\n });\n return;\n }\n throw e;\n }\n if (cancelled) return;\n setState({\n ...initial,\n status: \"ready\",\n me: email,\n thread,\n channel,\n threadId,\n lock: thread ? threadLock(thread) : undefined,\n messages: mergeMessages([], page.messages),\n hasMore: page.hasMore,\n });\n } catch (error) {\n if (!cancelled) setState({ ...initial, status: \"error\", error: asChatError(error) });\n }\n })();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, targetKey, generation]);\n\n // --- thread state refresh (replies left after a creator reply) --------\n const refreshThread = useCallback(() => {\n if (!client) return;\n if (refreshTimer.current) clearTimeout(refreshTimer.current);\n refreshTimer.current = setTimeout(async () => {\n try {\n const me = await client.me();\n setState((s) => {\n if (s.status !== \"ready\") return s;\n const thread = s.thread && me.threads.find((t) => t.id === s.thread!.id);\n const channel = s.channel && me.channels.find((c) => c.id === s.channel!.id);\n return {\n ...s,\n thread: thread ?? s.thread,\n channel: channel ?? s.channel,\n lock: thread ? threadLock(thread) : s.lock,\n };\n });\n } catch {\n // Keep the last state.\n }\n }, 1500);\n }, [client]);\n\n // --- live -------------------------------------------------------------\n const poll = useCallback(async () => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n const page = await client.messages(threadId, { limit: PAGE });\n setState((s) =>\n s.threadId === threadId ? { ...s, messages: mergeMessages(s.messages, page.messages) } : s,\n );\n }, [client]);\n\n const onEvent = useCallback(\n (event: ChatRealtimeEvent) => {\n setState((s) => {\n switch (event.type) {\n case \"message.created\":\n return { ...s, messages: mergeMessages(s.messages, [event.message]) };\n case \"message.rejected\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id\n ? { ...m, local: { state: \"rejected\", reason: event.reason || \"rejected\" } }\n : m,\n ),\n };\n case \"message.deleted\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id ? { ...m, deleted: true, body: \"\", attachmentKey: undefined } : m,\n ),\n };\n case \"message.pinned\":\n case \"message.unpinned\":\n return {\n ...s,\n messages: s.messages.map((m) =>\n m.id === event.id ? { ...m, pinned: event.type === \"message.pinned\" } : m,\n ),\n };\n }\n });\n if (\n event.type === \"message.created\" &&\n !event.message.system &&\n event.message.authorEmail.toLowerCase() !== stateRef.current.me\n ) {\n refreshThread();\n }\n },\n [refreshThread],\n );\n\n const ready = state.status === \"ready\";\n const liveThreadId = ready ? state.threadId : undefined;\n useEffect(() => {\n if (!client || !liveThreadId) return;\n const stop = startLive({\n client,\n kind: target.kind,\n id: liveThreadId,\n onEvent,\n onMode: (live) => setState((s) => ({ ...s, live })),\n poll,\n pollIntervalMs: options.pollIntervalMs,\n realtimeUrl: options.realtimeUrl,\n loadNats: options.loadNats,\n });\n return () => {\n stop();\n setState((s) => ({ ...s, live: \"off\" }));\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client, liveThreadId, onEvent, poll]);\n\n // --- read receipt of the newest message from the other side -----------\n useEffect(() => {\n if (!client || !state.threadId || state.status !== \"ready\") return;\n const last = [...state.messages]\n .reverse()\n .find((m) => !m.local && m.authorEmail.toLowerCase() !== state.me);\n if (!last || lastRead.current === last.id) return;\n lastRead.current = last.id;\n void client.markRead(state.threadId, last.id).catch(() => undefined);\n }, [client, state.threadId, state.status, state.messages, state.me]);\n\n useEffect(\n () => () => {\n for (const timer of timers.current) clearTimeout(timer);\n if (refreshTimer.current) clearTimeout(refreshTimer.current);\n },\n [],\n );\n\n // --- actions ----------------------------------------------------------\n const updateMessage = (id: string, patch: (m: ConversationMessage) => ConversationMessage) =>\n setState((s) => ({ ...s, messages: s.messages.map((m) => (m.id === id ? patch(m) : m)) }));\n\n const send = useCallback(\n async (body: string, attachment?: File, reuseId?: string) => {\n const text = body.trim();\n const { threadId, me } = stateRef.current;\n if (!client || !threadId || (!text && !attachment)) return;\n const id = reuseId ?? newMessageId();\n const optimistic: ConversationMessage = {\n id,\n threadId,\n authorEmail: me ?? \"\",\n body: text,\n retryFile: attachment,\n ...(attachment ? { attachmentName: attachment.name, attachmentSize: attachment.size, attachmentContentType: attachment.type, attachmentKind: attachment.type.startsWith(\"image/\") ? \"image\" : \"file\" } : {}),\n createdAt: new Date().toISOString(),\n pinned: false,\n deleted: false,\n system: false,\n status: \"pending\",\n local: { state: \"pending\" },\n };\n setState((s) => ({ ...s, messages: [...s.messages, optimistic] }));\n try {\n const uploaded = attachment ? await client.uploadAttachment(threadId, attachment, (uploadProgress) => updateMessage(id, (message) => ({ ...message, local: { ...message.local, state: \"pending\", uploadProgress } }))) : undefined;\n if (uploaded) updateMessage(id, (message) => ({ ...message, attachmentKey: uploaded.key, attachmentKind: uploaded.kind, attachmentName: uploaded.filename, attachmentSize: uploaded.size, attachmentContentType: uploaded.contentType, local: { state: \"pending\" } }));\n const result = await client.send(threadId, { body: text, clientId: id, ...(uploaded ? { attachment: uploaded } : {}) });\n setState((s) => ({\n ...s,\n messages: mergeMessages(s.messages, [{ ...result.message, id }]),\n thread: s.thread ? { ...s.thread, ...result.thread, id: s.thread.id } : s.thread,\n lock: s.thread ? threadLock({ ...s.thread, ...result.thread, id: s.thread.id }) : s.lock,\n }));\n const timer = setTimeout(() => {\n timers.current.delete(timer);\n updateMessage(id, (m) =>\n m.local?.state === \"pending\" ? { ...m, local: { state: \"unconfirmed\" } } : m,\n );\n }, options.confirmTimeoutMs ?? 45_000);\n timers.current.add(timer);\n } catch (error) {\n const e = asChatError(error);\n const failed = e.code === \"network\" || e.code === \"unavailable\" || e.status >= 500;\n updateMessage(id, (m) => ({\n ...m,\n local: {\n state: failed ? \"failed\" : \"rejected\",\n reason: e.code,\n retryAfter: e.retryAfter,\n keepId: failed || reuseId != null,\n },\n }));\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client, options.confirmTimeoutMs],\n );\n\n const discard = useCallback((id: string) => {\n setState((s) => ({ ...s, messages: s.messages.filter((m) => m.id !== id) }));\n }, []);\n\n const retry = useCallback(\n async (id: string) => {\n const message = stateRef.current.messages.find((m) => m.id === id);\n if (!message) return;\n // A send whose outcome is unknown (network error, 5xx, no\n // confirmation) may have been queued: it goes again under the same\n // id, so the service stores and bills it once, also after a later\n // attempt was refused. Only a send that was refused every time gets a\n // new id.\n const uncertain = message.local?.state !== \"rejected\" || message.local.keepId === true;\n discard(id);\n await send(message.body, message.retryFile, uncertain ? id : undefined);\n },\n [discard, send],\n );\n\n const deleteMessage = useCallback(\n async (id: string) => {\n const { threadId } = stateRef.current;\n const before = stateRef.current.messages.find((m) => m.id === id);\n if (!client || !threadId || !before) return;\n updateMessage(id, (m) => ({ ...m, deleted: true, body: \"\", attachmentKey: undefined }));\n try {\n await client.deleteMessage(threadId, id);\n } catch (error) {\n updateMessage(id, () => before);\n throw error;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [client],\n );\n\n const getAttachmentURL = useCallback(async (message: ChatMessage) => {\n const threadId = stateRef.current.threadId;\n if (!client || !threadId) throw new ChatError(\"not_found\", 404);\n return client.attachmentURL(threadId, message);\n }, [client]);\n\n const report = useCallback(\n async (input: { messageId?: string; reason: ChatReportReason; note?: string }) => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n await client.report({ threadId, ...input });\n },\n [client],\n );\n\n const setMuted = useCallback(\n async (muted: boolean) => {\n const { threadId } = stateRef.current;\n if (!client || !threadId) return;\n await client.mute(threadId, muted);\n setState((s) => (s.thread ? { ...s, thread: { ...s.thread, muted } } : s));\n },\n [client],\n );\n\n const loadOlder = useCallback(async () => {\n const { threadId, messages, hasMore, loadingOlder } = stateRef.current;\n const oldest = messages.find((m) => !m.local);\n if (!client || !threadId || !hasMore || loadingOlder || !oldest) return;\n setState((s) => ({ ...s, loadingOlder: true }));\n try {\n const page = await client.messages(threadId, { before: oldest.id, limit: PAGE });\n setState((s) => ({\n ...s,\n loadingOlder: false,\n hasMore: page.hasMore,\n messages: mergeMessages(s.messages, page.messages),\n }));\n } catch {\n setState((s) => ({ ...s, loadingOlder: false }));\n }\n }, [client]);\n\n const reload = useCallback(() => setGeneration((g) => g + 1), []);\n\n return { ...state, send, retry, getAttachmentURL, discard, deleteMessage, report, setMuted, loadOlder, reload };\n}\n","import { parseRealtimeEvent, type ChatClient } from \"./client\";\nimport {\n DEFAULT_CHAT_REALTIME_URL,\n type ChatLiveMode,\n type ChatRealtimeEvent,\n} from \"./types\";\n\n/** The part of `@nats-io/nats-core` the chat uses. */\nexport type NatsModule = {\n wsconnect(options: {\n servers: string[];\n authenticator: unknown;\n name?: string;\n reconnect?: boolean;\n maxReconnectAttempts?: number;\n }): Promise<NatsConnectionLike>;\n jwtAuthenticator(jwt: () => string): unknown;\n};\n\nexport type NatsConnectionLike = {\n subscribe(\n subject: string,\n options: { callback: (error: Error | null, msg: { data: Uint8Array }) => void },\n ): { unsubscribe(): void };\n closed(): Promise<void | Error>;\n close(): Promise<void>;\n};\n\n/**\n * The NATS client is loaded on the first open conversation, so pages and\n * shops without chat never download it.\n */\nexport const loadNatsCore = (): Promise<NatsModule> =>\n import(\"@nats-io/nats-core\") as unknown as Promise<NatsModule>;\n\nexport type LiveOptions = {\n client: ChatClient;\n /** `thread` for a pack thread, `channel` for a channel. */\n kind: \"thread\" | \"channel\";\n /** Thread id (a channel thread has the channel's id). */\n id: string;\n onEvent: (event: ChatRealtimeEvent) => void;\n onMode: (mode: ChatLiveMode) => void;\n /** Loads the newest page: the fallback, and the catch-up after a connect. */\n poll: () => Promise<void>;\n /** Polling interval while the socket is down. Default 8 s. */\n pollIntervalMs?: number;\n /** Delay before the socket is tried again after a failure. Default 60 s. */\n retryMs?: number;\n /** Overrides the WebSocket servers of the realtime token. */\n realtimeUrl?: string;\n /** Replaces the NATS loader (tests). */\n loadNats?: () => Promise<NatsModule>;\n};\n\n/**\n * Listens to one conversation: a realtime token from the chat service, then\n * a subscribe-only NATS WebSocket on the conversation's subject. When any\n * step fails (no WebSocket, token refused, subject not granted, socket\n * closed) it polls the newest page instead and tries the socket again later.\n * Returns the stop function.\n */\nexport function startLive(options: LiveOptions): () => void {\n const pollMs = options.pollIntervalMs ?? 8000;\n const retryMs = options.retryMs ?? 60_000;\n const load = options.loadNats ?? loadNatsCore;\n const suffix = `.${options.kind}.${options.id}`;\n\n let stopped = false;\n let connection: NatsConnectionLike | undefined;\n let jwt = \"\";\n let pollTimer: ReturnType<typeof setInterval> | undefined;\n let retryTimer: ReturnType<typeof setTimeout> | undefined;\n let refreshTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stopPolling = () => {\n if (pollTimer != null) clearInterval(pollTimer);\n pollTimer = undefined;\n };\n\n const fallback = () => {\n if (stopped) return;\n options.onMode(\"polling\");\n if (pollTimer == null) {\n pollTimer = setInterval(() => {\n void options.poll().catch(() => undefined);\n }, pollMs);\n }\n if (retryTimer == null) {\n retryTimer = setTimeout(() => {\n retryTimer = undefined;\n void connect();\n }, retryMs);\n }\n };\n\n const scheduleRefresh = (expiresAt: string) => {\n if (refreshTimer != null) clearTimeout(refreshTimer);\n const at = Date.parse(expiresAt);\n if (!Number.isFinite(at)) return;\n // A new JWT a minute before the old one ends; the connection uses it on\n // its next (re)connect, which the server forces at the expiry.\n const delay = Math.max(5_000, at - Date.now() - 60_000);\n refreshTimer = setTimeout(async () => {\n if (stopped) return;\n try {\n const next = await options.client.realtimeToken({ fresh: true });\n jwt = next.jwt;\n scheduleRefresh(next.expiresAt);\n } catch {\n // The server closes the socket at the expiry: polling takes over.\n }\n }, delay);\n };\n\n async function connect() {\n if (stopped) return;\n if (typeof WebSocket === \"undefined\") {\n fallback();\n return;\n }\n options.onMode(\"connecting\");\n try {\n let token = await options.client.realtimeToken();\n let subject = token.subjects.find((s) => s.endsWith(suffix));\n if (!subject) {\n // A kept token may predate this conversation.\n token = await options.client.realtimeToken({ fresh: true });\n subject = token.subjects.find((s) => s.endsWith(suffix));\n }\n if (!subject || !token.jwt) throw new Error(\"subject not granted\");\n jwt = token.jwt;\n const nats = await load();\n if (stopped) return;\n const servers = options.realtimeUrl\n ? [options.realtimeUrl]\n : token.servers.length > 0\n ? token.servers\n : [DEFAULT_CHAT_REALTIME_URL];\n const nc = await nats.wsconnect({\n servers,\n authenticator: nats.jwtAuthenticator(() => jwt),\n name: \"bitsnap-react/chat\",\n reconnect: true,\n maxReconnectAttempts: 5,\n });\n if (stopped) {\n void nc.close().catch(() => undefined);\n return;\n }\n connection = nc;\n const decoder = new TextDecoder();\n nc.subscribe(subject, {\n callback: (error, msg) => {\n if (error) return;\n try {\n const event = parseRealtimeEvent(JSON.parse(decoder.decode(msg.data)));\n if (event) options.onEvent(event);\n } catch {\n // Not a chat event.\n }\n },\n });\n void nc.closed().then(() => {\n if (connection === nc) {\n connection = undefined;\n fallback();\n }\n });\n scheduleRefresh(token.expiresAt);\n stopPolling();\n options.onMode(\"live\");\n // Messages stored between the history load and the subscribe.\n void options.poll().catch(() => undefined);\n } catch {\n fallback();\n }\n }\n\n void connect();\n\n return () => {\n stopped = true;\n stopPolling();\n if (retryTimer != null) clearTimeout(retryTimer);\n if (refreshTimer != null) clearTimeout(refreshTimer);\n const nc = connection;\n connection = undefined;\n if (nc) void nc.close().catch(() => undefined);\n };\n}\n","import { Hash, MessageCircle } from \"lucide-react\";\nimport { lazy, Suspense, useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { getLocale } from \"../components/checkout/locale\";\nimport {\n UserProductType,\n type UserProduct,\n} from \"../gen/proto/public/v1/public_api_pb\";\nimport { cn } from \"../lib/utils\";\nimport { addPackToCart } from \"./buy\";\nimport { ChatError, createChatClient, type ChatClient } from \"./client\";\nimport {\n fill,\n formatChatDate,\n formatMessageTime,\n getChatTexts,\n repliesLeftText,\n type ChatTexts,\n} from \"./texts\";\nimport type { ChatMe } from \"./types\";\nimport { Channel, ChatThread, type ChatBuyRequest } from \"./widgets\";\n\nconst BitsnapCheckout = lazy(() => import(\"../components/checkout/BitsnapCart\"));\n\n/** Which conversation the panel shows (kept in the URL). */\nexport type PanelMessagesSelection = {\n /** Chat project of the conversation. */\n chatProject?: string;\n thread?: string;\n channel?: string;\n};\n\nexport type PanelMessagesProps = {\n accessToken: string;\n /** Panel products: CHAT and COMMUNITY items name the conversations. */\n products: UserProduct[];\n /** `chat_api_url` of the products answer. */\n chatApiUrl?: string;\n /** Project of the panel, used when no chat product names one. */\n projectID?: string;\n host?: string;\n selection: PanelMessagesSelection;\n onSelect: (selection: PanelMessagesSelection) => void;\n locale?: string;\n texts?: Partial<ChatTexts>;\n};\n\ntype ProjectLoad =\n | { status: \"loading\" }\n | { status: \"ready\"; me: ChatMe }\n | { status: \"error\"; error: ChatError };\n\nconst isChatProduct = (product: UserProduct) =>\n product.productType === UserProductType.CHAT ||\n product.productType === UserProductType.COMMUNITY;\n\n/**\n * The projects whose chat the buyer may have: those of the CHAT and\n * COMMUNITY products, else the panel's project.\n *\n * `UserProduct.projectId` is the public (hashed) id the cart uses; the chat\n * service wants the numeric one (`X-Bitsnap-Project`), which Instapay sends as\n * `chatProjectId` on CHAT and COMMUNITY products (see `chatProjectIds`).\n */\nexport function chatProjects(products: UserProduct[], fallback?: string): string[] {\n const ids = [...new Set(products.filter(isChatProduct).map((p) => p.projectId).filter(Boolean))];\n if (ids.length > 0) return ids;\n return fallback ? [fallback] : [];\n}\n\n/** Public project id → numeric chat project id, from the chat products. */\nexport function chatProjectIds(products: UserProduct[]): Map<string, string> {\n const map = new Map<string, string>();\n for (const p of products) {\n if (isChatProduct(p) && p.projectId && p.chatProjectId) map.set(p.projectId, p.chatProjectId);\n }\n return map;\n}\n\n/** The \"Wiadomości\" screen of the customer panel. */\nexport default function PanelMessages({\n accessToken,\n products,\n chatApiUrl,\n projectID,\n host,\n selection,\n onSelect,\n locale = getLocale(),\n texts: overrides,\n}: PanelMessagesProps) {\n const texts = useMemo(() => getChatTexts(overrides, locale), [overrides, locale]);\n const projects = useMemo(() => chatProjects(products, projectID), [products, projectID]);\n const chatIds = useMemo(() => chatProjectIds(products), [products]);\n const clients = useMemo(() => {\n const map = new Map<string, ChatClient>();\n for (const id of projects)\n map.set(id, createChatClient({ projectId: chatIds.get(id) ?? id, token: accessToken, baseUrl: chatApiUrl }));\n return map;\n }, [projects, chatIds, accessToken, chatApiUrl]);\n\n const [loads, setLoads] = useState<Record<string, ProjectLoad>>({});\n const [generation, setGeneration] = useState(0);\n const selectedKey = `${selection.chatProject ?? \"\"}:${selection.thread ?? \"\"}:${selection.channel ?? \"\"}`;\n\n // Reloaded when the buyer leaves a conversation, so unread counts and\n // replies left are fresh.\n useEffect(() => {\n let cancelled = false;\n for (const [id, client] of clients) {\n setLoads((all) => (all[id]?.status === \"ready\" ? all : { ...all, [id]: { status: \"loading\" } }));\n client.me().then(\n (me) => !cancelled && setLoads((all) => ({ ...all, [id]: { status: \"ready\", me } })),\n (error) =>\n !cancelled &&\n setLoads((all) => ({\n ...all,\n [id]: {\n status: \"error\",\n error: error instanceof ChatError ? error : new ChatError(\"internal_error\", 0),\n },\n })),\n );\n }\n return () => {\n cancelled = true;\n };\n }, [clients, generation, selectedKey]);\n\n const productName = useMemo(() => {\n const map = new Map<string, UserProduct>();\n for (const product of products) map.set(product.productId, product);\n return map;\n }, [products]);\n\n const [checkoutProject, setCheckoutProject] = useState<string>();\n const buyFor = (project: string) => async (request: ChatBuyRequest) => {\n setCheckoutProject(project);\n await addPackToCart({\n projectID: project,\n productId: request.productId,\n name: productName.get(request.productId)?.productName,\n priceCents: request.priceCents,\n host,\n });\n };\n\n const selectedClient = selection.chatProject ? clients.get(selection.chatProject) : undefined;\n const hasSelection = Boolean(selectedClient && (selection.thread || selection.channel));\n const back = () => onSelect({});\n\n let conversation = null;\n if (hasSelection && selectedClient && selection.chatProject) {\n const project = selection.chatProject;\n const load = loads[project];\n const me = load?.status === \"ready\" ? load.me : undefined;\n if (selection.thread) {\n const thread = me?.threads.find((t) => t.id === selection.thread);\n conversation = (\n <ChatThread\n key={`t:${project}:${selection.thread}`}\n projectID={project}\n client={selectedClient}\n threadId={selection.thread}\n title={(thread?.productId && productName.get(thread.productId)?.productName) || undefined}\n locale={locale}\n texts={overrides}\n onBack={back}\n onBuy={buyFor(project)}\n />\n );\n } else if (selection.channel) {\n const entitlement = me?.entitlements.channels.find((c) => c.channelId === selection.channel);\n conversation = (\n <Channel\n key={`c:${project}:${selection.channel}`}\n projectID={project}\n client={selectedClient}\n channelId={selection.channel}\n productId={entitlement?.productId}\n locale={locale}\n texts={overrides}\n onBack={back}\n onBuy={buyFor(project)}\n />\n );\n }\n }\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\">\n <div className={cn(hasSelection && \"hidden md:block\")}>\n <h3 className=\"text-2xl\">{texts.inboxHeading}</h3>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400\">{texts.inboxIntro}</p>\n </div>\n <div className=\"mt-6 grid min-h-0 flex-1 gap-4 md:grid-cols-[minmax(16rem,22rem)_1fr]\">\n <div className={cn(\"min-w-0\", hasSelection && \"hidden md:block\")}>\n <Inbox\n projects={projects}\n loads={loads}\n clients={clients}\n products={products}\n texts={texts}\n locale={locale}\n selection={selection}\n onSelect={onSelect}\n onRetry={() => setGeneration((g) => g + 1)}\n />\n </div>\n <div className={cn(\"min-h-[32rem] min-w-0 md:h-[calc(100dvh-10rem)]\", !hasSelection && \"hidden md:flex\")}>\n {conversation ?? (\n <div className=\"flex h-full w-full items-center justify-center rounded-xl border border-dashed border-neutral-300 p-6 text-sm text-neutral-600 dark:border-neutral-700 dark:text-neutral-400\">\n {texts.selectConversation}\n </div>\n )}\n </div>\n </div>\n {checkoutProject && (\n <Suspense fallback={null}>\n <BitsnapCheckout projectID={checkoutProject} locale={locale} />\n </Suspense>\n )}\n </div>\n );\n}\n\ntype InboxProps = {\n projects: string[];\n loads: Record<string, ProjectLoad>;\n clients: Map<string, ChatClient>;\n products: UserProduct[];\n texts: ChatTexts;\n locale: string;\n selection: PanelMessagesSelection;\n onSelect: (selection: PanelMessagesSelection) => void;\n onRetry: () => void;\n};\n\nconst rowClass =\n \"flex min-h-11 w-full min-w-0 items-start gap-3 rounded-xl p-3 text-left transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500\";\n\nfunction Inbox({ projects, loads, clients, products, texts, locale, selection, onSelect, onRetry }: InboxProps) {\n const [opening, setOpening] = useState<string>();\n const [openError, setOpenError] = useState<string>();\n const byProduct = new Map(products.map((p) => [p.productId, p] as const));\n const multiProject = projects.length > 1;\n const states = projects.map((id) => loads[id] ?? ({ status: \"loading\" } as const));\n\n if (states.length > 0 && states.every((s) => s.status === \"loading\")) {\n return (\n <div role=\"status\" aria-busy=\"true\" aria-label={texts.loading} className=\"flex flex-col gap-2\">\n {[0, 1, 2].map((i) => (\n <div key={i} aria-hidden=\"true\" className=\"h-20 rounded-xl bg-white motion-safe:animate-pulse dark:bg-neutral-800\" />\n ))}\n </div>\n );\n }\n\n if (states.length === 0 || states.every((s) => s.status === \"error\")) {\n return (\n <div role=\"alert\" className=\"flex flex-col items-start gap-3\" data-testid=\"chat-inbox-error\">\n <p className=\"text-sm text-neutral-700 dark:text-neutral-300\">{texts.inboxError}</p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex min-h-11 items-center rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 focus-visible:outline-2 focus-visible:outline-current dark:bg-white dark:text-neutral-900\"\n >\n {texts.retry}\n </button>\n </div>\n );\n }\n\n const rows: ReactNode[] = [];\n const starts: ReactNode[] = [];\n for (const project of projects) {\n const load = loads[project];\n if (load?.status !== \"ready\") continue;\n const { me } = load;\n const projectName = multiProject\n ? products.find((p) => p.projectId === project)?.projectName\n : undefined;\n\n for (const thread of me.threads) {\n const product = thread.productId ? byProduct.get(thread.productId) : undefined;\n const selected = selection.chatProject === project && selection.thread === thread.id;\n const meta = [repliesLeftText(texts, thread.repliesLeft, locale)];\n if (thread.expiresAt)\n meta.push(\n fill(thread.composerLocked ? texts.expiredOn : texts.expiresOn, {\n date: formatChatDate(thread.expiresAt, locale),\n }),\n );\n if (thread.responseHours > 0) meta.push(fill(texts.responseHours, { hours: thread.responseHours }));\n const last = thread.lastMessage;\n rows.push(\n <li key={`t:${project}:${thread.id}`}>\n <button\n type=\"button\"\n data-thread-id={thread.id}\n aria-current={selected ? \"true\" : undefined}\n onClick={() => onSelect({ chatProject: project, thread: thread.id })}\n className={cn(rowClass, selected ? \"bg-neutral-200 dark:bg-neutral-800\" : \"bg-white hover:bg-neutral-50 dark:bg-neutral-900 dark:hover:bg-neutral-800\")}\n >\n <MessageCircle className=\"mt-0.5 size-5 shrink-0 text-neutral-500 dark:text-neutral-400\" aria-hidden=\"true\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"flex items-baseline gap-2\">\n <span className={cn(\"truncate\", thread.unread > 0 ? \"font-semibold\" : \"font-medium\")}>\n {product?.productName || texts.chatFallbackTitle}\n </span>\n {last && (\n <time dateTime={last.createdAt} className=\"ml-auto shrink-0 text-xs text-neutral-500 dark:text-neutral-400\">\n {formatMessageTime(last.createdAt, locale)}\n </time>\n )}\n </span>\n {projectName && <span className=\"truncate text-xs text-neutral-500 dark:text-neutral-400\">{projectName}</span>}\n <span className=\"flex items-center gap-2\">\n <span className=\"line-clamp-1 flex-1 text-sm text-neutral-600 dark:text-neutral-300\">\n {last ? (last.deleted ? texts.deletedPlaceholder : last.body) : texts.noMessagesYet}\n </span>\n {thread.unread > 0 && (\n <span\n aria-label={fill(texts.unread, { count: thread.unread })}\n className=\"inline-flex min-w-5 shrink-0 items-center justify-center rounded-full bg-neutral-900 px-1.5 text-xs font-semibold text-white tabular-nums dark:bg-white dark:text-neutral-900\"\n >\n {thread.unread}\n </span>\n )}\n </span>\n <span className={cn(\"text-xs\", thread.repliesLeft <= 0 || thread.composerLocked ? \"text-amber-800 dark:text-amber-300\" : \"text-neutral-500 dark:text-neutral-400\")}>\n {meta.join(\" · \")}\n {thread.muted && ` · ${texts.mutedBadge}`}\n </span>\n </span>\n </button>\n </li>,\n );\n }\n\n for (const channel of me.channels) {\n const selected = selection.chatProject === project && selection.channel === channel.id;\n rows.push(\n <li key={`c:${project}:${channel.id}`}>\n <button\n type=\"button\"\n data-channel-id={channel.id}\n aria-current={selected ? \"true\" : undefined}\n onClick={() => onSelect({ chatProject: project, channel: channel.id })}\n className={cn(rowClass, selected ? \"bg-neutral-200 dark:bg-neutral-800\" : \"bg-white hover:bg-neutral-50 dark:bg-neutral-900 dark:hover:bg-neutral-800\")}\n >\n <Hash className=\"mt-0.5 size-5 shrink-0 text-neutral-500 dark:text-neutral-400\" aria-hidden=\"true\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{channel.name || texts.channelFallbackTitle}</span>\n {projectName && <span className=\"truncate text-xs text-neutral-500 dark:text-neutral-400\">{projectName}</span>}\n <span className=\"line-clamp-1 text-sm text-neutral-600 dark:text-neutral-300\">\n {channel.topic || texts.kindChannel}\n </span>\n {channel.memberCount > 0 && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fill(texts.members, { count: channel.memberCount })}\n </span>\n )}\n </span>\n </button>\n </li>,\n );\n }\n\n for (const pack of me.entitlements.packs) {\n const key = `${project}:${pack.productId}`;\n const name = byProduct.get(pack.productId)?.productName || pack.name || texts.chatFallbackTitle;\n starts.push(\n <li key={`p:${key}`} className=\"flex flex-col gap-2 rounded-xl bg-white p-3 dark:bg-neutral-900 sm:flex-row sm:items-center\">\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{name}</span>\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {[\n repliesLeftText(texts, pack.repliesLeftHint, locale),\n pack.expiresAt ? fill(texts.expiresOn, { date: formatChatDate(pack.expiresAt, locale) }) : \"\",\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </span>\n <button\n type=\"button\"\n disabled={opening === key}\n aria-busy={opening === key || undefined}\n onClick={async () => {\n const client = clients.get(project);\n if (!client) return;\n setOpening(key);\n setOpenError(undefined);\n try {\n const thread = await client.openThread(pack.productId);\n onSelect({ chatProject: project, thread: thread.id });\n } catch {\n setOpenError(key);\n } finally {\n setOpening(undefined);\n }\n }}\n className=\"inline-flex min-h-11 shrink-0 items-center justify-center rounded-md bg-neutral-900 px-4 text-sm font-medium text-white hover:bg-neutral-800 disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500 dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200\"\n >\n {opening === key ? texts.loading : texts.startChat}\n </button>\n {openError === key && (\n <p role=\"alert\" className=\"text-xs text-red-700 dark:text-red-400 sm:basis-full\">\n {texts.errorGeneric}\n </p>\n )}\n </li>,\n );\n }\n\n const listed = new Set(me.channels.map((c) => c.id));\n for (const entitlement of me.entitlements.channels) {\n if (entitlement.channelId && listed.has(entitlement.channelId)) continue;\n const name = byProduct.get(entitlement.productId)?.productName || texts.channelFallbackTitle;\n starts.push(\n <li key={`e:${project}:${entitlement.productId}`} className=\"flex flex-col gap-2 rounded-xl bg-white p-3 dark:bg-neutral-900 sm:flex-row sm:items-center\">\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate font-medium\">{name}</span>\n {!entitlement.channelId && (\n <span className=\"text-xs text-neutral-500 dark:text-neutral-400\">{texts.channelNotReady}</span>\n )}\n </span>\n {entitlement.channelId && (\n <button\n type=\"button\"\n onClick={() => onSelect({ chatProject: project, channel: entitlement.channelId })}\n className=\"inline-flex min-h-11 shrink-0 items-center justify-center rounded-md border border-neutral-300 px-4 text-sm font-medium hover:bg-neutral-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-500 dark:border-neutral-700 dark:hover:bg-neutral-800\"\n >\n {texts.openChannel}\n </button>\n )}\n </li>,\n );\n }\n }\n\n if (rows.length === 0 && starts.length === 0) {\n return (\n <p data-testid=\"chat-inbox-empty\" className=\"text-sm text-neutral-600 dark:text-neutral-400\">\n {texts.inboxEmpty}\n </p>\n );\n }\n\n return (\n <div className=\"flex flex-col gap-6\" data-testid=\"chat-inbox\">\n {rows.length > 0 && <ul className=\"flex flex-col gap-2\">{rows}</ul>}\n {starts.length > 0 && (\n <section className=\"flex flex-col gap-2\">\n <h4 className=\"text-sm font-semibold text-neutral-700 dark:text-neutral-300\">{texts.startSection}</h4>\n <ul className=\"flex flex-col gap-2\">{starts}</ul>\n </section>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useEffect","useMemo","useRef","useState","addPackToCart","input","addProductToCart","DEFAULT_CHAT_API_URL","DEFAULT_CHAT_ATTACHMENTS_URL","DEFAULT_CHAT_REALTIME_URL","ChatError","code","status","data","retry","createChatClient","options","baseUrl","DEFAULT_CHAT_API_URL","localOrigin","attachmentBase","DEFAULT_CHAT_ATTACHMENTS_URL","doFetch","args","call","method","path","body","response","json","text","isRecord","id","cachedToken","mapMe","productId","mapThread","threadId","opts","query","qs","suffix","page","arr","mapMessage","input","res","mapThreadState","file","progress","requestAttachment","ticket","resolve","reject","xhr","event","done","message","messageId","muted","str","s","parseRealtimeEvent","raw","eventId","value","num","n","optStr","time","m","mapPack","p","mapChannel","c","ent","AlertCircle","ArrowLeft","Clock","Lock","MoreHorizontal","Paperclip","Pin","Plus","SendHorizontal","X","useEffect","useId","useLayoutEffect","useRef","useState","CHAT_TEXT_TABLES","getChatTexts","overrides","locale","getLocale","resolveTexts","fill","template","values","match","key","repliesLeftText","texts","count","category","formatChatDate","iso","date","formatMessageTime","now","sameDay","rejectionText","reason","retryAfter","Fragment","jsx","jsxs","focusRing","prefersReducedMotion","formatBytes","bytes","scrollToBottom","element","smooth","quietButton","cn","primaryButton","ConversationView","host","c","texts","locale","kind","title","onBack","onBuy","className","menuOpen","setMenuOpen","useState","reporting","setReporting","notice","setNotice","draft","setDraft","reportSent","setReportSent","useEffect","timer","heading","meta","metaLine","ArrowLeft","LiveDot","item","open","MoreHorizontal","LoadingBody","ErrorBody","LockCard","MessageList","messageId","message","ReportForm","reason","note","Composer","items","repliesLeftText","date","formatChatDate","fill","live","label","width","index","errorText","code","canRetry","lock","prominent","buying","setBuying","failed","setFailed","amountText","setAmountText","normalized","amount","isPwyw","min","max","validAmount","isFreeClaim","email","setEmail","setCode","codeSent","setCodeSent","turnstileToken","setTurnstileToken","challengeAttempt","setChallengeAttempt","cooldown","setCooldown","codeInput","useRef","restart","n","english","body","action","canBuy","Lock","event","TurnstileChallenge","error","chatBuyError","authorLabel","mine","onReport","onEdit","scroller","stick","paintedHistory","knownIds","knownOldestAt","newIds","setNewIds","newMessages","setNewMessages","count","lastId","ids","previous","oldest","at","priorOldest","added","current","useLayoutEffect","el","reduced","MessageRow","getAttachmentURL","animateEntry","onDelete","onRetry","onDiscard","actions","setActions","confirmDelete","setConfirmDelete","attachmentUrl","setAttachmentUrl","attachmentError","setAttachmentError","attachmentVersion","setAttachmentVersion","attachmentExpiresAt","retryAttachment","value","openAttachment","tab","url","local","cancelled","refused","waiting","canAct","Paperclip","Pin","Clock","formatMessageTime","AlertCircle","rejectionText","composerBlock","id","useId","area","fileInput","previewRemoveTimer","selectedFile","setSelectedFile","previewLeaving","setPreviewLeaving","accept","setAccept","blocked","chooseFile","file","removeFile","submit","text","onKeyDown","X","Plus","SendHorizontal","REASONS","onCancel","onSubmit","setReason","setNote","sending","setSending","noteId","fallback","en","useCallback","useEffect","useRef","useState","loadNatsCore","startLive","options","pollMs","retryMs","load","suffix","stopped","connection","jwt","pollTimer","retryTimer","refreshTimer","stopPolling","fallback","connect","scheduleRefresh","expiresAt","at","delay","next","token","subject","s","nats","servers","DEFAULT_CHAT_REALTIME_URL","nc","decoder","error","msg","event","parseRealtimeEvent","PAGE","initial","newMessageId","c","bytes","i","hex","b","mergeMessages","current","incoming","byId","m","message","existing","at","bt","threadLock","thread","lockedFrom","error","reason","productId","price","asChatError","ChatError","useConversation","options","client","target","state","setState","useState","generation","setGeneration","stateRef","useRef","timers","refreshTimer","lastRead","targetKey","useEffect","cancelled","me","email","channel","threadId","t","e","page","refreshThread","useCallback","s","poll","onEvent","event","liveThreadId","stop","startLive","live","last","timer","updateMessage","id","patch","send","body","attachment","reuseId","text","optimistic","uploaded","uploadProgress","result","failed","discard","retry","uncertain","deleteMessage","before","getAttachmentURL","report","input","setMuted","muted","loadOlder","messages","hasMore","loadingOlder","oldest","reload","g","jsx","useWidgetClient","props","stored","setStored","useState","host","getPanelConfig","useEffect","getPanelSession","token","projectId","useMemo","createChatClient","useBuy","claim","useRef","lock","productId","request","commerceBase","isFreeClaim","response","result","key","verify","verified","bought","addPackToCart","ChatThread","client","locale","getLocale","texts","getChatTexts","conversation","useConversation","buy","ConversationView","Channel","Hash","MessageCircle","lazy","Suspense","useEffect","useMemo","useState","jsx","jsxs","BitsnapCheckout","lazy","isChatProduct","product","chatProjects","products","fallback","ids","p","chatProjectIds","map","PanelMessages","accessToken","chatApiUrl","projectID","host","selection","onSelect","locale","getLocale","overrides","texts","useMemo","getChatTexts","projects","chatIds","clients","id","createChatClient","loads","setLoads","useState","generation","setGeneration","selectedKey","useEffect","cancelled","client","all","me","error","ChatError","productName","checkoutProject","setCheckoutProject","buyFor","project","request","addPackToCart","selectedClient","hasSelection","back","conversation","load","thread","t","ChatThread","entitlement","c","Channel","cn","Inbox","g","Suspense","rowClass","onRetry","opening","setOpening","openError","setOpenError","byProduct","multiProject","states","s","i","rows","starts","projectName","selected","meta","repliesLeftText","fill","formatChatDate","last","MessageCircle","formatMessageTime","channel","Hash","pack","key","name","listed"]}
|