@linktr.ee/messaging-react 3.10.0 → 3.11.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.
Files changed (86) hide show
  1. package/dist/Card-415OfRhw.cjs +2 -0
  2. package/dist/Card-415OfRhw.cjs.map +1 -0
  3. package/dist/Card-CyWdUMCB.cjs +2 -0
  4. package/dist/Card-CyWdUMCB.cjs.map +1 -0
  5. package/dist/Card-V5d6qhDT.js +694 -0
  6. package/dist/Card-V5d6qhDT.js.map +1 -0
  7. package/dist/Card-qS17qnN0.js +103 -0
  8. package/dist/Card-qS17qnN0.js.map +1 -0
  9. package/dist/LoadingDots-Ct3WmVlT.js +59 -0
  10. package/dist/LoadingDots-Ct3WmVlT.js.map +1 -0
  11. package/dist/LoadingDots-D9GGphgM.cjs +2 -0
  12. package/dist/LoadingDots-D9GGphgM.cjs.map +1 -0
  13. package/dist/assets/index.css +1 -1
  14. package/dist/index-Bx_zjNMR.cjs +2 -0
  15. package/dist/index-Bx_zjNMR.cjs.map +1 -0
  16. package/dist/{index-BxzzWK4K.js → index-DFZrAiLB.js} +1276 -1249
  17. package/dist/index-DFZrAiLB.js.map +1 -0
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.ts +164 -101
  20. package/dist/index.js +1 -1
  21. package/dist/testing.d.ts +1 -0
  22. package/package.json +1 -1
  23. package/src/components/ChannelList/CustomChannelPreview.test.tsx +95 -0
  24. package/src/components/ChannelList/CustomChannelPreview.tsx +21 -9
  25. package/src/components/CustomMessage/CustomMessage.stories.tsx +54 -1
  26. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.stories.tsx +541 -0
  27. package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +117 -0
  28. package/src/components/CustomMessage/LockedAttachment/components/Media/Card.tsx +358 -0
  29. package/src/components/{LockedAttachment/components/Received → CustomMessage/LockedAttachment/components/Media}/CardActions.tsx +9 -2
  30. package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +79 -0
  31. package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +153 -0
  32. package/src/components/CustomMessage/LockedAttachment/components/Text/types.ts +58 -0
  33. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/CardBody.tsx +8 -7
  34. package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +92 -0
  35. package/src/components/{LockedAttachment → CustomMessage/LockedAttachment}/components/_shared/GalleryThumbnail.tsx +31 -39
  36. package/src/components/CustomMessage/LockedAttachment/components/_shared/LoadingDots.tsx +36 -0
  37. package/src/components/CustomMessage/LockedAttachment/components/_shared/LockBadge.tsx +63 -0
  38. package/src/components/{LockedAttachment/components/_shared/LockedCardShell.tsx → CustomMessage/LockedAttachment/components/_shared/MediaShell.tsx} +8 -8
  39. package/src/components/CustomMessage/LockedAttachment/components/_shared/PreviewToggle.tsx +58 -0
  40. package/src/components/CustomMessage/LockedAttachment/components/_shared/PriceLockRow.tsx +21 -0
  41. package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +74 -0
  42. package/src/components/{LockedAttachment/components/_shared/LockedThumbnail.tsx → CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx} +11 -7
  43. package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +29 -0
  44. package/src/components/CustomMessage/LockedAttachment/components/_shared/TextShell.tsx +28 -0
  45. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.test.ts +67 -0
  46. package/src/components/CustomMessage/LockedAttachment/components/_shared/usePreviewToggle.ts +89 -0
  47. package/src/components/CustomMessage/LockedAttachment/components/_shared/useReceivedSource.ts +83 -0
  48. package/src/components/CustomMessage/LockedAttachment/index.tsx +105 -0
  49. package/src/components/CustomMessage/LockedAttachment/types.ts +44 -0
  50. package/src/components/CustomMessage/MessageTag.tsx +8 -0
  51. package/src/components/CustomMessage/context.tsx +1 -1
  52. package/src/components/CustomMessage/index.tsx +76 -42
  53. package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +5 -3
  54. package/src/index.ts +3 -8
  55. package/src/stream-custom-data.ts +1 -0
  56. package/src/styles.css +12 -0
  57. package/src/types.ts +1 -1
  58. package/dist/Card-B-L2MST4.js +0 -107
  59. package/dist/Card-B-L2MST4.js.map +0 -1
  60. package/dist/Card-B56QxJW3.cjs +0 -2
  61. package/dist/Card-B56QxJW3.cjs.map +0 -1
  62. package/dist/Card-Bqh-BQaX.js +0 -132
  63. package/dist/Card-Bqh-BQaX.js.map +0 -1
  64. package/dist/Card-ZCE6Lhfc.cjs +0 -2
  65. package/dist/Card-ZCE6Lhfc.cjs.map +0 -1
  66. package/dist/Card-g4JD-2ES.js +0 -163
  67. package/dist/Card-g4JD-2ES.js.map +0 -1
  68. package/dist/Card-wac6AMNc.cjs +0 -2
  69. package/dist/Card-wac6AMNc.cjs.map +0 -1
  70. package/dist/LockedThumbnail-BAQmoIff.js +0 -220
  71. package/dist/LockedThumbnail-BAQmoIff.js.map +0 -1
  72. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs +0 -2
  73. package/dist/LockedThumbnail-Cr0Gqs3Y.cjs.map +0 -1
  74. package/dist/index-BPhZyaxT.cjs +0 -2
  75. package/dist/index-BPhZyaxT.cjs.map +0 -1
  76. package/dist/index-BxzzWK4K.js.map +0 -1
  77. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +0 -450
  78. package/src/components/LockedAttachment/components/Composer/Card.tsx +0 -221
  79. package/src/components/LockedAttachment/components/Composer/index.ts +0 -2
  80. package/src/components/LockedAttachment/components/Received/Card.tsx +0 -191
  81. package/src/components/LockedAttachment/components/Received/index.ts +0 -2
  82. package/src/components/LockedAttachment/components/Sent/Card.tsx +0 -177
  83. package/src/components/LockedAttachment/components/Sent/index.ts +0 -2
  84. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +0 -39
  85. package/src/components/LockedAttachment/index.tsx +0 -63
  86. package/src/components/LockedAttachment/types.ts +0 -51
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BPhZyaxT.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-Bx_zjNMR.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -404,44 +404,12 @@ export declare interface ChannelViewProps {
404
404
  onParticipantNameClick?: () => void;
405
405
  }
406
406
 
407
- export declare interface ComposerCardProps extends LockedAttachmentBaseProps {
408
- /** Placeholder shown in the title slot before the composer types one. */
409
- placeholderTitle?: string;
410
- /** Placeholder shown in the amount slot before one is configured. */
411
- placeholderAmountText?: string;
412
- /**
413
- * When provided, renders a dismiss X in the thumbnail corner. Called when
414
- * the composer clicks it to remove the attachment.
415
- */
416
- onDismiss?: () => void;
417
- /** Fired the first time the composer taps the thumbnail to preview. */
418
- onPreviewClick?: () => void;
419
- /**
420
- * Lazily loads the underlying source so the composer can preview the
421
- * attachment they're about to send. Called the first time the thumbnail is
422
- * tapped; the returned source is cached and reused on subsequent toggles.
423
- */
424
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
425
- /**
426
- * When provided, renders a pencil button in the body bottom-right that the
427
- * composer can use to edit the attachment metadata (e.g. open the price /
428
- * gallery editor). Matches the Composer "Button" instance in Figma.
429
- */
430
- onEditClick?: () => void;
431
- }
432
-
433
407
  /** Shared props for the `Composer` state of every attachment type. */
434
408
  declare interface ComposerExtras {
435
409
  /** Renders a dismiss `×` button overlaid on the attachment. */
436
410
  onDismiss?: () => void;
437
411
  }
438
412
 
439
- /**
440
- * @deprecated Renamed to `SentCardProps`. Drafting usages (with `onDismiss`)
441
- * should migrate to `ComposerCardProps`.
442
- */
443
- export declare type CreatorCardProps = SentCardProps;
444
-
445
413
  export declare const CustomMessageProvider: Provider<Partial<CustomMessageRegistry>>;
446
414
 
447
415
  export declare interface CustomMessageRegistry {
@@ -450,6 +418,19 @@ export declare interface CustomMessageRegistry {
450
418
 
451
419
  declare type DmAgentSystemType = 'SYSTEM_DM_AGENT_PAUSED' | 'SYSTEM_DM_AGENT_RESUMED';
452
420
 
421
+ declare type DraftProps = MediaCardDraftProps & {
422
+ /** Drafting is always the sender — doesn't apply here. */
423
+ isMine?: never;
424
+ /**
425
+ * "text" renders as a chat bubble showing the real draft `text` instead of
426
+ * a media thumbnail card. Media-only fields below are ignored in text mode.
427
+ * Defaults to `"media"`.
428
+ */
429
+ contentType?: 'text' | 'media';
430
+ /** The message's own text, shown as-is (not redacted yet). Only used when `contentType` is `"text"`. */
431
+ renderedText?: default_2.ReactNode;
432
+ };
433
+
453
434
  export declare interface Faq {
454
435
  id: string;
455
436
  question: string;
@@ -479,6 +460,9 @@ export declare interface FaqListProps {
479
460
  avatarName?: string;
480
461
  }
481
462
 
463
+ /** Fetches the underlying source; `void` return means none available. */
464
+ declare type FetchSourceHandler = () => Promise<LockedAttachmentSource | void>;
465
+
482
466
  declare interface FileAttachmentSharedProps extends MessageAttachmentBaseProps {
483
467
  /** Source URL of the file (used as the download target). */
484
468
  src?: string;
@@ -728,56 +712,113 @@ export declare interface LinkAttachmentSentCardProps extends LinkAttachmentBaseP
728
712
  onClick?: () => void;
729
713
  }
730
714
 
731
- export declare const LockedAttachment: {
732
- Composer: (props: ComposerCardProps) => JSX_2.Element;
733
- Sent: (props: SentCardProps) => JSX_2.Element;
734
- Received: (props: ReceivedCardProps) => JSX_2.Element;
735
- Creator: (props: SentCardProps) => JSX_2.Element;
736
- Visitor: (props: ReceivedCardProps) => JSX_2.Element;
737
- };
738
-
739
- declare interface LockedAttachmentBaseProps {
740
- title?: string;
741
- mimeType?: string;
742
- thumbnailUrl?: string;
743
- detail?: string;
744
- amountText?: string;
745
- paymentStatus?: PaymentStatus;
746
- /**
747
- * When provided with 2+ items, the card renders as a mixed-media carousel
748
- * (e.g. a couple of photos + a video) instead of a single thumbnail. Each
749
- * item brings its own thumbnail and optional source so that
750
- * `LockedAttachment.Composer` / `.Sent` / `.Received` can all share the
751
- * same carousel chrome.
752
- */
753
- gallery?: LockedAttachmentGalleryItem[];
754
- }
715
+ export declare const LockedAttachment: default_2.FC<LockedAttachmentProps>;
755
716
 
756
717
  export declare interface LockedAttachmentContextValue {
718
+ /** Whether the given message is mid-checkout. */
757
719
  isUnlocking: (id: string) => boolean;
720
+ /** Opens the checkout flow for a message. */
758
721
  onUnlockClick?: (message: LocalMessage, channel: Channel) => void;
722
+ /** Handles a Download click for a message. */
759
723
  onDownloadClick?: (message: LocalMessage, channel: Channel) => void;
724
+ /** Reveals the locked media's source. Unused for text — payment writes `message.text` directly. */
760
725
  onFetchSource?: (message: LocalMessage, channel: Channel) => Promise<LockedAttachmentSource | void>;
761
726
  }
762
727
 
763
728
  export declare interface LockedAttachmentGalleryItem {
764
- /** MIME type of this carousel item — drives the per-item play / lock affordance. */
729
+ /** Drives the per-item play/lock affordance. */
765
730
  mimeType: string;
766
- /** Poster image used for both the locked (blurred) and unlocked preview state. */
731
+ /** Poster image, locked or unlocked. */
767
732
  thumbnailUrl?: string;
768
- /** Underlying source (image or video URL) — shown only when unlocked. */
733
+ /** Media source, shown only when unlocked. */
769
734
  sourceUrl?: string;
770
735
  }
771
736
 
737
+ /**
738
+ * The card for a paid attachment — a still-drafting `isDraft` card, a static
739
+ * receiver-facing `isPreview` mockup, or a sent text/media card discriminated
740
+ * by `isMine` + `contentType`. Internally lazy-loads only the one underlying
741
+ * implementation the resolved branch needs.
742
+ */
743
+ export declare type LockedAttachmentProps = DraftProps | PreviewProps | SentTextOwnProps | SentTextOtherProps | SentMediaOwnProps | SentMediaOtherProps;
744
+
772
745
  export declare interface LockedAttachmentSource {
773
- /** Proxied URL used by the media player for in-app playback. */
746
+ /** Proxied URL for in-app playback. */
774
747
  sourceUrl: string;
775
- /** URL opened when the visitor clicks Download — may be a file or a web destination. */
748
+ /** Opened when the visitor clicks Download. */
776
749
  redeemUrl?: string;
777
- /** Thumbnail URL from the fetched asset — overrides the metadata thumbnail when present. */
750
+ /** Overrides the metadata thumbnail when present. */
778
751
  thumbnailUrl?: string;
779
752
  }
780
753
 
754
+ declare interface MediaCardBaseProps {
755
+ /** Attachment title. */
756
+ title?: string;
757
+ /** Fallback shown when `title` is unset. */
758
+ placeholderTitle?: string;
759
+ /** Drives icon/thumbnail rendering. */
760
+ mimeType?: string;
761
+ /** Poster image, locked or unlocked. */
762
+ thumbnailUrl?: string;
763
+ /** Secondary description text. */
764
+ detail?: string;
765
+ /** Amount label. */
766
+ amountText?: string;
767
+ /** 2+ items renders as a carousel instead of a single thumbnail. */
768
+ gallery?: LockedAttachmentGalleryItem[];
769
+ }
770
+
771
+ declare interface MediaCardDraftProps extends MediaCardBaseProps {
772
+ /** True while composing — shows the real (unredacted) thumbnail. */
773
+ isDraft: true;
774
+ isPreview?: false;
775
+ /** Fallback amount before a price is set. */
776
+ placeholderAmountText?: string;
777
+ /** Lazily fetches the source on first tap; cached and reused after. */
778
+ onFetchSource?: FetchSourceHandler;
779
+ /** Fired on first tap to preview. */
780
+ onPreviewClick?: () => void;
781
+ /** Removes the draft attachment. */
782
+ onDismiss?: () => void;
783
+ /** Renders a pencil button to edit the attachment metadata. */
784
+ onEditClick?: () => void;
785
+ }
786
+
787
+ declare type MediaCardOtherProps = MediaCardSentBaseProps & {
788
+ /** Recipient's view. */
789
+ isMine: false;
790
+ /** Opens checkout on click. Omit to hide the button. */
791
+ onUnlockClick?: () => void;
792
+ /** Omit to hide the Download button. */
793
+ onDownloadClick?: () => void;
794
+ /** Spinner on Unlock while checkout runs. */
795
+ isUnlocking?: boolean;
796
+ };
797
+
798
+ declare type MediaCardOwnProps = MediaCardSentBaseProps & {
799
+ /** Sender's own view. */
800
+ isMine: true;
801
+ /** Fired on first tap to preview own attachment. */
802
+ onPreviewClick?: () => void;
803
+ };
804
+
805
+ declare interface MediaCardPreviewProps extends MediaCardBaseProps {
806
+ /** True to render exactly as the recipient will see it before purchase — always locked/blurred. */
807
+ isDraft?: false;
808
+ isPreview: true;
809
+ /** Fallback amount before a price is set. */
810
+ placeholderAmountText?: string;
811
+ }
812
+
813
+ declare interface MediaCardSentBaseProps extends MediaCardBaseProps {
814
+ /** False once sent. */
815
+ isDraft?: false;
816
+ isPreview?: false;
817
+ /** Drives the lock/skeleton/badge state. */
818
+ paymentStatus?: PaymentStatus;
819
+ onFetchSource?: FetchSourceHandler;
820
+ }
821
+
781
822
  export declare const MediaMessage: default_2.FC<MediaMessageProps> & {
782
823
  Creator: default_2.FC<{
783
824
  message: LocalMessage;
@@ -1233,6 +1274,7 @@ export declare interface MessageMetadata {
1233
1274
  amount_text?: string;
1234
1275
  payment_status?: PaymentStatus;
1235
1276
  attachment_title?: string;
1277
+ attachment_content_type?: 'text' | 'media';
1236
1278
  attachment_mime_type?: string;
1237
1279
  attachment_thumbnail?: string;
1238
1280
  attachment_detail?: string;
@@ -1445,6 +1487,16 @@ declare interface PdfAttachmentSharedProps extends MessageAttachmentBaseProps {
1445
1487
  onClick?: (index: number) => boolean | void;
1446
1488
  }
1447
1489
 
1490
+ declare type PreviewProps = MediaCardPreviewProps & {
1491
+ /** Drafting is always the sender — doesn't apply here. */
1492
+ isMine?: never;
1493
+ /**
1494
+ * "text" renders as a chat bubble instead of a media thumbnail card.
1495
+ * Media-only fields below are ignored in text mode. Defaults to `"media"`.
1496
+ */
1497
+ contentType?: 'text' | 'media';
1498
+ };
1499
+
1448
1500
  export declare interface ReceivedBubbleProps extends MessageBubbleBaseProps {
1449
1501
  /**
1450
1502
  * Optional header rendered above the text inside the bubble — used by the
@@ -1453,31 +1505,6 @@ export declare interface ReceivedBubbleProps extends MessageBubbleBaseProps {
1453
1505
  header?: default_2.ReactNode;
1454
1506
  }
1455
1507
 
1456
- export declare interface ReceivedCardProps extends LockedAttachmentBaseProps {
1457
- /**
1458
- * Called when the recipient clicks Unlock on an unpaid attachment.
1459
- * Use this to open a checkout flow. Omit to hide the Unlock button.
1460
- */
1461
- onUnlockClick?: () => void;
1462
- /**
1463
- * Called to fetch the attachment source — fired automatically when
1464
- * `paymentStatus` transitions to `'paid'`, or immediately on click when
1465
- * `paymentStatus` is already `'paid'`. Return a `LockedAttachmentSource`
1466
- * to unlock the card.
1467
- */
1468
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1469
- /**
1470
- * Called when the recipient clicks Download on an unlocked card.
1471
- * Omit to hide the Download button.
1472
- */
1473
- onDownloadClick?: () => void;
1474
- /**
1475
- * When true, shows a loading spinner on the Unlock button.
1476
- * Driven by the LockedAttachmentContext (e.g. checkout in progress).
1477
- */
1478
- isUnlocking?: boolean;
1479
- }
1480
-
1481
1508
  /**
1482
1509
  * Resolves the member to display as the conversation's "other party".
1483
1510
  *
@@ -1503,17 +1530,56 @@ export declare function resolveParticipantDisplayName(user?: ParticipantDisplayU
1503
1530
  export declare interface SentBubbleProps extends MessageBubbleBaseProps {
1504
1531
  }
1505
1532
 
1506
- export declare interface SentCardProps extends LockedAttachmentBaseProps {
1507
- /** Placeholder shown in the title slot when no title is set. */
1508
- placeholderTitle?: string;
1509
- /** Fired the first time the sender taps the thumbnail to preview their own attachment. */
1510
- onPreviewClick?: () => void;
1511
- /**
1512
- * Lazily loads the underlying source so the sender can preview the attachment.
1513
- * Called the first time the thumbnail is tapped; the returned source is cached
1514
- * and reused on subsequent toggles.
1515
- */
1516
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1533
+ declare type SentMediaOtherProps = MediaCardOtherProps & {
1534
+ contentType?: 'media';
1535
+ };
1536
+
1537
+ declare type SentMediaOwnProps = MediaCardOwnProps & {
1538
+ contentType?: 'media';
1539
+ /** Sender's view has no unlock/download flow doesn't apply here. */
1540
+ onUnlockClick?: never;
1541
+ isUnlocking?: never;
1542
+ onDownloadClick?: never;
1543
+ };
1544
+
1545
+ declare type SentTextOtherProps = TextCardOtherProps & {
1546
+ contentType: 'text';
1547
+ };
1548
+
1549
+ declare type SentTextOwnProps = TextCardOwnProps & {
1550
+ contentType: 'text';
1551
+ /** Sender's view has no unlock flow — doesn't apply here. */
1552
+ onUnlockClick?: never;
1553
+ isUnlocking?: never;
1554
+ };
1555
+
1556
+ declare interface TextCardBaseProps {
1557
+ /** Amount label. */
1558
+ amountText?: string;
1559
+ }
1560
+
1561
+ declare type TextCardOtherProps = TextCardSentBaseProps & {
1562
+ /** Recipient's view. */
1563
+ isMine: false;
1564
+ /** Spinner on Unlock while checkout runs. */
1565
+ isUnlocking?: boolean;
1566
+ /** Opens checkout on click. Omit to hide the button. */
1567
+ onUnlockClick?: () => void;
1568
+ };
1569
+
1570
+ declare type TextCardOwnProps = TextCardSentBaseProps & {
1571
+ /** Sender's own view. */
1572
+ isMine: true;
1573
+ };
1574
+
1575
+ declare interface TextCardSentBaseProps extends TextCardBaseProps {
1576
+ /** False once sent. */
1577
+ isDraft?: false;
1578
+ isPreview?: false;
1579
+ /** Revealed content once paid. */
1580
+ renderedText?: default_2.ReactNode;
1581
+ /** Drives the lock/skeleton/badge state. */
1582
+ paymentStatus?: PaymentStatus;
1517
1583
  }
1518
1584
 
1519
1585
  /**
@@ -1584,9 +1650,6 @@ declare interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps
1584
1650
  onClick?: (index: number) => boolean | void;
1585
1651
  }
1586
1652
 
1587
- /** @deprecated Renamed to `ReceivedCardProps`. */
1588
- export declare type VisitorCardProps = ReceivedCardProps;
1589
-
1590
1653
  export declare type VoteSelection = 'up' | 'down' | null;
1591
1654
 
1592
1655
  export { }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as u, i as c, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as b, u as P, v as f, w as x, x as y, y as B, z as S, B as q, D as z, E as D, G as E, H as V } from "./index-BxzzWK4K.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as u, i as c, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as b, u as P, v as f, w as x, x as y, y as B, z as S, B as q, D as z, E as D, G as E, H as V } from "./index-DFZrAiLB.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/dist/testing.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare interface MessageMetadata {
47
47
  amount_text?: string;
48
48
  payment_status?: PaymentStatus;
49
49
  attachment_title?: string;
50
+ attachment_content_type?: 'text' | 'media';
50
51
  attachment_mime_type?: string;
51
52
  attachment_thumbnail?: string;
52
53
  attachment_detail?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -260,6 +260,101 @@ describe('CustomChannelPreview', () => {
260
260
  expect(screen.getByText('No messages yet')).toBeInTheDocument()
261
261
  })
262
262
 
263
+ it('shows a locked indicator for a text-only attachment message, even when paid', () => {
264
+ // The channel-list preview always shows the locked wording for an
265
+ // attachment-type message and never echoes `message.text` — the
266
+ // revealed content is only ever shown inside the full conversation.
267
+ const channel = createMockChannel([
268
+ {
269
+ id: 'msg-1',
270
+ text: 'REDACTED',
271
+ type: 'regular',
272
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
273
+ user: { id: 'participant-1', name: 'Alice' },
274
+ metadata: {
275
+ custom_type: 'MESSAGE_ATTACHMENT',
276
+ attachment_content_type: 'text',
277
+ payment_status: 'paid',
278
+ },
279
+ },
280
+ ])
281
+
282
+ renderWithProviders(
283
+ <CustomChannelPreview {...defaultProps} channel={channel} />
284
+ )
285
+
286
+ expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
287
+ expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
288
+ })
289
+
290
+ it('shows a locked indicator for an unpaid text-only attachment message', () => {
291
+ const channel = createMockChannel([
292
+ {
293
+ id: 'msg-1',
294
+ text: 'REDACTED',
295
+ type: 'regular',
296
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
297
+ user: { id: 'participant-1', name: 'Alice' },
298
+ metadata: {
299
+ custom_type: 'MESSAGE_ATTACHMENT',
300
+ attachment_content_type: 'text',
301
+ payment_status: 'pending',
302
+ },
303
+ },
304
+ ])
305
+
306
+ renderWithProviders(
307
+ <CustomChannelPreview {...defaultProps} channel={channel} />
308
+ )
309
+
310
+ expect(screen.getByText('🔒 Sent a locked message')).toBeInTheDocument()
311
+ expect(screen.queryByText(/REDACTED/)).not.toBeInTheDocument()
312
+ })
313
+
314
+ it('shows a locked indicator for a media attachment message, even when paid', () => {
315
+ const channel = createMockChannel([
316
+ {
317
+ id: 'msg-1',
318
+ type: 'regular',
319
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
320
+ user: { id: 'participant-1', name: 'Alice' },
321
+ metadata: {
322
+ custom_type: 'MESSAGE_ATTACHMENT',
323
+ attachment_content_type: 'media',
324
+ payment_status: 'paid',
325
+ },
326
+ },
327
+ ])
328
+
329
+ renderWithProviders(
330
+ <CustomChannelPreview {...defaultProps} channel={channel} />
331
+ )
332
+
333
+ expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
334
+ })
335
+
336
+ it('shows a locked indicator for an unpaid media attachment message', () => {
337
+ const channel = createMockChannel([
338
+ {
339
+ id: 'msg-1',
340
+ type: 'regular',
341
+ created_at: new Date('2026-01-01T00:00:00.000Z'),
342
+ user: { id: 'participant-1', name: 'Alice' },
343
+ metadata: {
344
+ custom_type: 'MESSAGE_ATTACHMENT',
345
+ attachment_content_type: 'media',
346
+ payment_status: 'pending',
347
+ },
348
+ },
349
+ ])
350
+
351
+ renderWithProviders(
352
+ <CustomChannelPreview {...defaultProps} channel={channel} />
353
+ )
354
+
355
+ expect(screen.getByText('🔒 Sent a locked attachment')).toBeInTheDocument()
356
+ })
357
+
263
358
  it('updates starred state when the channel membership changes', () => {
264
359
  const channel = createMockChannel([])
265
360
 
@@ -1,6 +1,9 @@
1
1
  import classNames from 'classnames'
2
2
  import React from 'react'
3
- import { ChannelPreviewUIComponentProps, useChatContext } from 'stream-chat-react'
3
+ import {
4
+ ChannelPreviewUIComponentProps,
5
+ useChatContext,
6
+ } from 'stream-chat-react'
4
7
 
5
8
  import { useChannelStar } from '../../hooks/useChannelStar'
6
9
  import { formatRelativeTime } from '../../utils/formatRelativeTime'
@@ -85,14 +88,23 @@ const CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(
85
88
  viewerLanguage,
86
89
  })
87
90
 
88
- const isTip = lastMessage?.metadata?.custom_type === 'MESSAGE_TIP'
89
- if (isTip) return displayText ? `💵 ${displayText}` : '💵 Sent a tip'
90
- const isPaid = lastMessage?.metadata?.custom_type === 'MESSAGE_PAID'
91
- if (isPaid) return displayText ? `💰 ${displayText}` : '💰 Sent a message'
92
- const isPaidAttachment =
93
- lastMessage?.metadata?.custom_type === 'MESSAGE_ATTACHMENT'
94
- if (isPaidAttachment)
95
- return displayText ? `📎 ${displayText}` : '📎 Sent an attachment'
91
+ const { custom_type, attachment_content_type } =
92
+ lastMessage?.metadata || {}
93
+
94
+ if (custom_type === 'MESSAGE_TIP') {
95
+ return displayText ? displayText : 'Sent a tip'
96
+ }
97
+ if (custom_type === 'MESSAGE_PAID') {
98
+ return displayText ? displayText : 'Sent a message'
99
+ }
100
+ if (custom_type === 'MESSAGE_ATTACHMENT') {
101
+ if (attachment_content_type === 'text') {
102
+ return '🔒 Sent a locked message'
103
+ }
104
+ if (attachment_content_type === 'media') {
105
+ return '🔒 Sent a locked attachment'
106
+ }
107
+ }
96
108
 
97
109
  if (displayText) return displayText
98
110
 
@@ -126,6 +126,7 @@ interface TemplateProps {
126
126
  attachment_mime_type?: string
127
127
  attachment_thumbnail?: string
128
128
  attachment_detail?: string
129
+ attachment_content_type?: 'text' | 'media'
129
130
  }
130
131
  }>
131
132
  }
@@ -416,7 +417,59 @@ LockedAttachment.args = {
416
417
  ],
417
418
  }
418
419
 
419
-
420
+ export const LockedTextAttachment: StoryFn<TemplateProps> = Template.bind({})
421
+ LockedTextAttachment.args = {
422
+ messages: [
423
+ {
424
+ id: 'msg-1',
425
+ text: 'Can you send me the details privately?',
426
+ user: storyUsers.visitor,
427
+ },
428
+ {
429
+ id: 'msg-2',
430
+ // Empty text: the real content is redacted server-side until purchase,
431
+ // so the bubble renders the blurred placeholder instead.
432
+ text: '',
433
+ user: storyUsers.creator,
434
+ metadata: {
435
+ custom_type: 'MESSAGE_ATTACHMENT',
436
+ amount_text: 'AU$4.99',
437
+ attachment_content_type: 'text',
438
+ },
439
+ },
440
+ {
441
+ id: 'msg-3',
442
+ text: '',
443
+ user: storyUsers.creator,
444
+ metadata: {
445
+ custom_type: 'MESSAGE_ATTACHMENT',
446
+ amount_text: 'AU$4.99',
447
+ payment_status: 'paid',
448
+ attachment_content_type: 'text',
449
+ },
450
+ },
451
+ {
452
+ id: 'msg-4',
453
+ // Buyer reveal: backend writes the real text back onto the message.
454
+ text: "Here's the address — see you Thursday at 7!",
455
+ user: storyUsers.creator,
456
+ metadata: {
457
+ custom_type: 'MESSAGE_ATTACHMENT',
458
+ amount_text: 'AU$4.99',
459
+ payment_status: 'paid',
460
+ attachment_content_type: 'text',
461
+ },
462
+ },
463
+ ],
464
+ }
465
+ LockedTextAttachment.parameters = {
466
+ docs: {
467
+ description: {
468
+ story:
469
+ 'A paid text-only message renders as a regular bubble with blurred placeholder copy (msg-2: pending, msg-3: purchased-but-not-yet-revealed) until the real text is revealed (msg-4).',
470
+ },
471
+ },
472
+ }
420
473
 
421
474
  export const ChatbotVariants: StoryFn<TemplateProps> = Template.bind({})
422
475
  ChatbotVariants.args = {