@linktr.ee/messaging-react 3.9.0-rc-1783568295 → 3.9.0-rc-1783575293

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 (87) hide show
  1. package/dist/Card-B7ccxsKf.cjs +2 -0
  2. package/dist/Card-B7ccxsKf.cjs.map +1 -0
  3. package/dist/Card-CXRSSrCh.cjs +2 -0
  4. package/dist/Card-CXRSSrCh.cjs.map +1 -0
  5. package/dist/Card-DaUKmwtb.js +105 -0
  6. package/dist/Card-DaUKmwtb.js.map +1 -0
  7. package/dist/Card-Q1Ee_UCB.js +693 -0
  8. package/dist/Card-Q1Ee_UCB.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-Bv79lb0u.js → index-BiBvPADG.js} +1194 -1168
  15. package/dist/index-BiBvPADG.js.map +1 -0
  16. package/dist/index-DuPa2mUh.cjs +2 -0
  17. package/dist/index-DuPa2mUh.cjs.map +1 -0
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.ts +161 -98
  20. package/dist/index.js +1 -1
  21. package/dist/testing.d.ts +1 -0
  22. package/package.json +2 -2
  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 +8 -1
  30. package/src/components/CustomMessage/LockedAttachment/components/Media/types.ts +79 -0
  31. package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +155 -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} +6 -7
  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 +30 -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/components/CustomMessageInput/index.tsx +0 -3
  55. package/src/index.ts +3 -8
  56. package/src/stream-custom-data.ts +1 -0
  57. package/src/styles.css +12 -0
  58. package/src/types.ts +1 -1
  59. package/dist/Card-B8Zz3GRL.js +0 -132
  60. package/dist/Card-B8Zz3GRL.js.map +0 -1
  61. package/dist/Card-BmYSOINp.cjs +0 -2
  62. package/dist/Card-BmYSOINp.cjs.map +0 -1
  63. package/dist/Card-BnJLBTl6.js +0 -163
  64. package/dist/Card-BnJLBTl6.js.map +0 -1
  65. package/dist/Card-DWT-IkeK.cjs +0 -2
  66. package/dist/Card-DWT-IkeK.cjs.map +0 -1
  67. package/dist/Card-Gapf9yEL.js +0 -107
  68. package/dist/Card-Gapf9yEL.js.map +0 -1
  69. package/dist/Card-WoQGTJ5n.cjs +0 -2
  70. package/dist/Card-WoQGTJ5n.cjs.map +0 -1
  71. package/dist/LockedThumbnail-Cu3qwKAd.js +0 -220
  72. package/dist/LockedThumbnail-Cu3qwKAd.js.map +0 -1
  73. package/dist/LockedThumbnail-DFByBLYS.cjs +0 -2
  74. package/dist/LockedThumbnail-DFByBLYS.cjs.map +0 -1
  75. package/dist/index-Bv79lb0u.js.map +0 -1
  76. package/dist/index-C1Zaiccm.cjs +0 -2
  77. package/dist/index-C1Zaiccm.cjs.map +0 -1
  78. package/src/components/LockedAttachment/LockedAttachment.stories.tsx +0 -450
  79. package/src/components/LockedAttachment/components/Composer/Card.tsx +0 -221
  80. package/src/components/LockedAttachment/components/Composer/index.ts +0 -2
  81. package/src/components/LockedAttachment/components/Received/Card.tsx +0 -191
  82. package/src/components/LockedAttachment/components/Received/index.ts +0 -2
  83. package/src/components/LockedAttachment/components/Sent/Card.tsx +0 -177
  84. package/src/components/LockedAttachment/components/Sent/index.ts +0 -2
  85. package/src/components/LockedAttachment/components/_shared/LockBadge.tsx +0 -39
  86. package/src/components/LockedAttachment/index.tsx +0 -63
  87. 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-C1Zaiccm.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.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-DuPa2mUh.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.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;
@@ -1184,6 +1225,7 @@ export declare interface MessageMetadata {
1184
1225
  amount_text?: string;
1185
1226
  payment_status?: PaymentStatus;
1186
1227
  attachment_title?: string;
1228
+ attachment_content_type?: 'text' | 'media';
1187
1229
  attachment_mime_type?: string;
1188
1230
  attachment_thumbnail?: string;
1189
1231
  attachment_detail?: string;
@@ -1396,30 +1438,15 @@ declare interface PdfAttachmentSharedProps extends MessageAttachmentBaseProps {
1396
1438
  onClick?: (index: number) => boolean | void;
1397
1439
  }
1398
1440
 
1399
- export declare interface ReceivedCardProps extends LockedAttachmentBaseProps {
1400
- /**
1401
- * Called when the recipient clicks Unlock on an unpaid attachment.
1402
- * Use this to open a checkout flow. Omit to hide the Unlock button.
1403
- */
1404
- onUnlockClick?: () => void;
1441
+ declare type PreviewProps = MediaCardPreviewProps & {
1442
+ /** Drafting is always the sender — doesn't apply here. */
1443
+ isMine?: never;
1405
1444
  /**
1406
- * Called to fetch the attachment source fired automatically when
1407
- * `paymentStatus` transitions to `'paid'`, or immediately on click when
1408
- * `paymentStatus` is already `'paid'`. Return a `LockedAttachmentSource`
1409
- * to unlock the card.
1445
+ * "text" renders as a chat bubble instead of a media thumbnail card.
1446
+ * Media-only fields below are ignored in text mode. Defaults to `"media"`.
1410
1447
  */
1411
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1412
- /**
1413
- * Called when the recipient clicks Download on an unlocked card.
1414
- * Omit to hide the Download button.
1415
- */
1416
- onDownloadClick?: () => void;
1417
- /**
1418
- * When true, shows a loading spinner on the Unlock button.
1419
- * Driven by the LockedAttachmentContext (e.g. checkout in progress).
1420
- */
1421
- isUnlocking?: boolean;
1422
- }
1448
+ contentType?: 'text' | 'media';
1449
+ };
1423
1450
 
1424
1451
  /**
1425
1452
  * Resolves the member to display as the conversation's "other party".
@@ -1443,17 +1470,56 @@ export declare function resolveMediaFromMessage(message: LocalMessage): MediaMes
1443
1470
  */
1444
1471
  export declare function resolveParticipantDisplayName(user?: ParticipantDisplayUser | null): string;
1445
1472
 
1446
- export declare interface SentCardProps extends LockedAttachmentBaseProps {
1447
- /** Placeholder shown in the title slot when no title is set. */
1448
- placeholderTitle?: string;
1449
- /** Fired the first time the sender taps the thumbnail to preview their own attachment. */
1450
- onPreviewClick?: () => void;
1451
- /**
1452
- * Lazily loads the underlying source so the sender can preview the attachment.
1453
- * Called the first time the thumbnail is tapped; the returned source is cached
1454
- * and reused on subsequent toggles.
1455
- */
1456
- onFetchSource?: () => Promise<LockedAttachmentSource | void>;
1473
+ declare type SentMediaOtherProps = MediaCardOtherProps & {
1474
+ contentType?: 'media';
1475
+ };
1476
+
1477
+ declare type SentMediaOwnProps = MediaCardOwnProps & {
1478
+ contentType?: 'media';
1479
+ /** Sender's view has no unlock/download flow doesn't apply here. */
1480
+ onUnlockClick?: never;
1481
+ isUnlocking?: never;
1482
+ onDownloadClick?: never;
1483
+ };
1484
+
1485
+ declare type SentTextOtherProps = TextCardOtherProps & {
1486
+ contentType: 'text';
1487
+ };
1488
+
1489
+ declare type SentTextOwnProps = TextCardOwnProps & {
1490
+ contentType: 'text';
1491
+ /** Sender's view has no unlock flow — doesn't apply here. */
1492
+ onUnlockClick?: never;
1493
+ isUnlocking?: never;
1494
+ };
1495
+
1496
+ declare interface TextCardBaseProps {
1497
+ /** Amount label. */
1498
+ amountText?: string;
1499
+ }
1500
+
1501
+ declare type TextCardOtherProps = TextCardSentBaseProps & {
1502
+ /** Recipient's view. */
1503
+ isMine: false;
1504
+ /** Spinner on Unlock while checkout runs. */
1505
+ isUnlocking?: boolean;
1506
+ /** Opens checkout on click. Omit to hide the button. */
1507
+ onUnlockClick?: () => void;
1508
+ };
1509
+
1510
+ declare type TextCardOwnProps = TextCardSentBaseProps & {
1511
+ /** Sender's own view. */
1512
+ isMine: true;
1513
+ };
1514
+
1515
+ declare interface TextCardSentBaseProps extends TextCardBaseProps {
1516
+ /** False once sent. */
1517
+ isDraft?: false;
1518
+ isPreview?: false;
1519
+ /** Revealed content once paid. */
1520
+ renderedText?: default_2.ReactNode;
1521
+ /** Drives the lock/skeleton/badge state. */
1522
+ paymentStatus?: PaymentStatus;
1457
1523
  }
1458
1524
 
1459
1525
  /**
@@ -1524,9 +1590,6 @@ declare interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps
1524
1590
  onClick?: (index: number) => boolean | void;
1525
1591
  }
1526
1592
 
1527
- /** @deprecated Renamed to `ReceivedCardProps`. */
1528
- export declare type VisitorCardProps = ReceivedCardProps;
1529
-
1530
1593
  export declare type VoteSelection = 'up' | 'down' | null;
1531
1594
 
1532
1595
  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 c, i as u, 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 P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-Bv79lb0u.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 c, i as u, 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 P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-BiBvPADG.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.9.0-rc-1783568295",
3
+ "version": "3.9.0-rc-1783575293",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@linktr.ee/component-library": "11.8.6",
53
- "@linktr.ee/messaging-core": "2.3.0-rc-1783568295",
53
+ "@linktr.ee/messaging-core": "2.3.0-rc-1783575293",
54
54
  "@phosphor-icons/react": "^2.1.10"
55
55
  },
56
56
  "devDependencies": {
@@ -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 = {