@repzo/widget-react-native 1.0.9 → 1.2.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/CHANGELOG.md +400 -0
- package/README.md +104 -5
- package/lib/commonjs/agent-ui-blocks.js +2 -1
- package/lib/commonjs/agent-ui-blocks.js.map +1 -1
- package/lib/commonjs/api/client.js +25 -0
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/attachments/pickers.js +3 -6
- package/lib/commonjs/attachments/pickers.js.map +1 -1
- package/lib/commonjs/device-id.js +31 -3
- package/lib/commonjs/device-id.js.map +1 -1
- package/lib/commonjs/i18n.js +40 -4
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/locales.js +34 -1
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/push/push-notifications.js +9 -8
- package/lib/commonjs/push/push-notifications.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +507 -26
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/storage.js +13 -1
- package/lib/commonjs/storage.js.map +1 -1
- package/lib/commonjs/types.js +20 -2
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +138 -49
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/AgentUiBlocks.js +35 -15
- package/lib/commonjs/ui/components/AgentUiBlocks.js.map +1 -1
- package/lib/commonjs/ui/components/Launcher.js +4 -1
- package/lib/commonjs/ui/components/Launcher.js.map +1 -1
- package/lib/commonjs/ui/components/Markdown.js +32 -2
- package/lib/commonjs/ui/components/Markdown.js.map +1 -1
- package/lib/commonjs/ui/components/Post.js +1241 -0
- package/lib/commonjs/ui/components/Post.js.map +1 -0
- package/lib/commonjs/ui/components/RatingCard.js +6 -3
- package/lib/commonjs/ui/components/RatingCard.js.map +1 -1
- package/lib/commonjs/ui/hooks/use-widget-insets.js +0 -1
- package/lib/commonjs/ui/hooks/use-widget-insets.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +67 -29
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/utils/html.js +9 -1
- package/lib/commonjs/ui/utils/html.js.map +1 -1
- package/lib/commonjs/ui/utils/native-gradient.js +0 -1
- package/lib/commonjs/ui/utils/native-gradient.js.map +1 -1
- package/lib/commonjs/ui/utils/post-html.js +276 -0
- package/lib/commonjs/ui/utils/post-html.js.map +1 -0
- package/lib/commonjs/ui/utils/safe-url.js +16 -0
- package/lib/commonjs/ui/utils/safe-url.js.map +1 -1
- package/lib/module/agent-ui-blocks.js +2 -1
- package/lib/module/agent-ui-blocks.js.map +1 -1
- package/lib/module/api/client.js +25 -0
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/attachments/pickers.js +3 -6
- package/lib/module/attachments/pickers.js.map +1 -1
- package/lib/module/device-id.js +31 -3
- package/lib/module/device-id.js.map +1 -1
- package/lib/module/i18n.js +40 -4
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/locales.js +34 -1
- package/lib/module/locales.js.map +1 -1
- package/lib/module/push/push-notifications.js +9 -8
- package/lib/module/push/push-notifications.js.map +1 -1
- package/lib/module/state/widget-store.js +507 -25
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/storage.js +13 -1
- package/lib/module/storage.js.map +1 -1
- package/lib/module/types.js +20 -2
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +139 -50
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/AgentUiBlocks.js +35 -15
- package/lib/module/ui/components/AgentUiBlocks.js.map +1 -1
- package/lib/module/ui/components/Launcher.js +4 -1
- package/lib/module/ui/components/Launcher.js.map +1 -1
- package/lib/module/ui/components/Markdown.js +32 -3
- package/lib/module/ui/components/Markdown.js.map +1 -1
- package/lib/module/ui/components/Post.js +1236 -0
- package/lib/module/ui/components/Post.js.map +1 -0
- package/lib/module/ui/components/RatingCard.js +6 -3
- package/lib/module/ui/components/RatingCard.js.map +1 -1
- package/lib/module/ui/hooks/use-widget-insets.js +0 -1
- package/lib/module/ui/hooks/use-widget-insets.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +68 -30
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/utils/html.js +9 -2
- package/lib/module/ui/utils/html.js.map +1 -1
- package/lib/module/ui/utils/native-gradient.js +0 -1
- package/lib/module/ui/utils/native-gradient.js.map +1 -1
- package/lib/module/ui/utils/post-html.js +271 -0
- package/lib/module/ui/utils/post-html.js.map +1 -0
- package/lib/module/ui/utils/safe-url.js +15 -0
- package/lib/module/ui/utils/safe-url.js.map +1 -1
- package/lib/typescript/commonjs/agent-ui-blocks.d.ts.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +19 -1
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/commonjs/device-id.d.ts.map +1 -1
- package/lib/typescript/commonjs/i18n.d.ts +35 -1
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/push/push-notifications.d.ts +6 -2
- package/lib/typescript/commonjs/push/push-notifications.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +97 -8
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/storage.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +91 -8
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AgentUiBlocks.d.ts +14 -0
- package/lib/typescript/commonjs/ui/components/AgentUiBlocks.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Launcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Markdown.d.ts +8 -0
- package/lib/typescript/commonjs/ui/components/Markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/Post.d.ts +69 -0
- package/lib/typescript/commonjs/ui/components/Post.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/RatingCard.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/use-widget-insets.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +10 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/html.d.ts +1 -0
- package/lib/typescript/commonjs/ui/utils/html.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/native-gradient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/post-html.d.ts +54 -0
- package/lib/typescript/commonjs/ui/utils/post-html.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/safe-url.d.ts +6 -0
- package/lib/typescript/commonjs/ui/utils/safe-url.d.ts.map +1 -1
- package/lib/typescript/module/agent-ui-blocks.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +19 -1
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -1
- package/lib/typescript/module/device-id.d.ts.map +1 -1
- package/lib/typescript/module/i18n.d.ts +35 -1
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/push/push-notifications.d.ts +6 -2
- package/lib/typescript/module/push/push-notifications.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +97 -8
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/storage.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +91 -8
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AgentUiBlocks.d.ts +14 -0
- package/lib/typescript/module/ui/components/AgentUiBlocks.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Launcher.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Markdown.d.ts +8 -0
- package/lib/typescript/module/ui/components/Markdown.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/Post.d.ts +69 -0
- package/lib/typescript/module/ui/components/Post.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/RatingCard.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/use-widget-insets.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +10 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/html.d.ts +1 -0
- package/lib/typescript/module/ui/utils/html.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/native-gradient.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/post-html.d.ts +54 -0
- package/lib/typescript/module/ui/utils/post-html.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/safe-url.d.ts +6 -0
- package/lib/typescript/module/ui/utils/safe-url.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/agent-ui-blocks.ts +2 -1
- package/src/api/client.ts +27 -0
- package/src/attachments/pickers.ts +3 -6
- package/src/device-id.ts +34 -3
- package/src/i18n.ts +39 -2
- package/src/index.tsx +4 -0
- package/src/locales.ts +33 -0
- package/src/push/push-notifications.ts +10 -8
- package/src/state/widget-store.ts +480 -24
- package/src/storage.ts +18 -1
- package/src/types.ts +91 -8
- package/src/ui/WidgetProvider.tsx +166 -48
- package/src/ui/components/AgentUiBlocks.tsx +40 -12
- package/src/ui/components/Launcher.tsx +6 -1
- package/src/ui/components/Markdown.tsx +34 -3
- package/src/ui/components/Post.tsx +943 -0
- package/src/ui/components/RatingCard.tsx +6 -2
- package/src/ui/hooks/use-widget-insets.ts +0 -1
- package/src/ui/screens/chat/ChatView.tsx +77 -42
- package/src/ui/screens/chat/ChatView.types.ts +10 -0
- package/src/ui/utils/html.ts +10 -2
- package/src/ui/utils/native-gradient.ts +0 -1
- package/src/ui/utils/post-html.ts +256 -0
- package/src/ui/utils/safe-url.ts +15 -0
package/src/storage.ts
CHANGED
|
@@ -44,7 +44,6 @@ function createMemoryAdapter(): RepzoWidgetStorageAdapter {
|
|
|
44
44
|
|
|
45
45
|
function resolveAsyncStorage(): RepzoWidgetStorageAdapter | null {
|
|
46
46
|
try {
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
48
47
|
const mod = require('@react-native-async-storage/async-storage');
|
|
49
48
|
const asyncStorage = mod?.default ?? mod;
|
|
50
49
|
if (!asyncStorage?.getItem) return null;
|
|
@@ -101,10 +100,27 @@ export const Storage = {
|
|
|
101
100
|
|
|
102
101
|
let secureAdapter: RepzoWidgetStorageAdapter | null = null;
|
|
103
102
|
|
|
103
|
+
let warnedPlaintext = false;
|
|
104
|
+
|
|
104
105
|
export function setSecureStorageAdapter(adapter: RepzoWidgetStorageAdapter | null): void {
|
|
105
106
|
secureAdapter = adapter;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Development builds only, once per run: the bearer token is about to land on
|
|
111
|
+
* disk unencrypted. The in-memory fallback writes nothing, so it stays quiet.
|
|
112
|
+
*/
|
|
113
|
+
function warnPlaintextOnce(): void {
|
|
114
|
+
if (warnedPlaintext || typeof __DEV__ === 'undefined' || !__DEV__) return;
|
|
115
|
+
if (activeAdapter() === memoryAdapter) return;
|
|
116
|
+
warnedPlaintext = true;
|
|
117
|
+
console.warn(
|
|
118
|
+
'[repzo-widget] no secure storage adapter is set, so the visitor token is stored in plain ' +
|
|
119
|
+
'text. Call RepzoWidget.setSecureStorageAdapter() before boot() — see "Secure token ' +
|
|
120
|
+
'storage" in the README.'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
export const SecureStorage = {
|
|
109
125
|
async getItem(key: string): Promise<string | null> {
|
|
110
126
|
if (!secureAdapter) return Storage.getItem(key);
|
|
@@ -126,6 +142,7 @@ export const SecureStorage = {
|
|
|
126
142
|
|
|
127
143
|
async setItem(key: string, value: string): Promise<void> {
|
|
128
144
|
if (!secureAdapter) {
|
|
145
|
+
warnPlaintextOnce();
|
|
129
146
|
await Storage.setItem(key, value);
|
|
130
147
|
return;
|
|
131
148
|
}
|
package/src/types.ts
CHANGED
|
@@ -164,12 +164,18 @@ export interface WidgetUser {
|
|
|
164
164
|
/**
|
|
165
165
|
* The user's account (company) in your app. Matched by `id`, so a renamed
|
|
166
166
|
* account updates the same CRM account instead of creating another.
|
|
167
|
+
* @since 1.0.8
|
|
167
168
|
*/
|
|
168
169
|
account?: WidgetUserAccount;
|
|
169
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* When they signed up in your app — a Date, unix seconds/ms, or an ISO
|
|
172
|
+
* string. Shown as "Signed up" in the inbox.
|
|
173
|
+
* @since 1.0.9
|
|
174
|
+
*/
|
|
170
175
|
signedUpAt?: Date | number | string;
|
|
171
176
|
}
|
|
172
177
|
|
|
178
|
+
/** The user's account (company) in your app, sent as `WidgetUser.account`. @since 1.0.8 */
|
|
173
179
|
export interface WidgetUserAccount {
|
|
174
180
|
/** Your app's stable account id — required. */
|
|
175
181
|
id: string;
|
|
@@ -237,6 +243,13 @@ export interface BootOptions {
|
|
|
237
243
|
* `<RepzoWidgetProvider>` to be mounted.
|
|
238
244
|
*/
|
|
239
245
|
inAppNotifications?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Show Send posts from Workstation (Send → Post, "Show on: Mobile") over the
|
|
248
|
+
* app (default true): a card that opens into the full post, with replies or
|
|
249
|
+
* reactions. Requires `<RepzoWidgetProvider>`.
|
|
250
|
+
* @since 1.2.0
|
|
251
|
+
*/
|
|
252
|
+
posts?: boolean;
|
|
240
253
|
}
|
|
241
254
|
|
|
242
255
|
/** An option in the composer's attach menu. */
|
|
@@ -490,13 +503,6 @@ export interface RepzoWidgetStorageAdapter {
|
|
|
490
503
|
removeItem(key: string): Promise<void> | void;
|
|
491
504
|
}
|
|
492
505
|
|
|
493
|
-
/**
|
|
494
|
-
* `RepzoWidgetError.code`. The last two come from the server's 403 body and
|
|
495
|
-
* are terminal — the SDK stops realtime and does not retry on its own:
|
|
496
|
-
* - `messenger_api_disabled` — the channel's Messenger API is switched off.
|
|
497
|
-
* - `identity_verification_failed` — `userHash` missing/wrong while the
|
|
498
|
-
* channel enforces identity verification.
|
|
499
|
-
*/
|
|
500
506
|
/** A bad-rating reason on the rating card, labelled per language. */
|
|
501
507
|
export interface RatingReason {
|
|
502
508
|
id: string;
|
|
@@ -516,6 +522,13 @@ export interface ConversationRatingResult {
|
|
|
516
522
|
locked: boolean;
|
|
517
523
|
}
|
|
518
524
|
|
|
525
|
+
/**
|
|
526
|
+
* `RepzoWidgetError.code`. The last two come from the server's 403 body and
|
|
527
|
+
* are terminal — the SDK stops realtime and does not retry on its own:
|
|
528
|
+
* - `messenger_api_disabled` — the channel's Messenger API is switched off.
|
|
529
|
+
* - `identity_verification_failed` — `userHash` missing/wrong while the
|
|
530
|
+
* channel enforces identity verification.
|
|
531
|
+
*/
|
|
519
532
|
export type RepzoWidgetErrorCode =
|
|
520
533
|
| 'network'
|
|
521
534
|
| 'http'
|
|
@@ -563,6 +576,18 @@ export interface WidgetState {
|
|
|
563
576
|
* app. Only set while the messenger is closed and its thread is unread.
|
|
564
577
|
*/
|
|
565
578
|
inAppNotification: InAppNotification | null;
|
|
579
|
+
/**
|
|
580
|
+
* The Send post this visitor should see now (Send → Post shown on Mobile),
|
|
581
|
+
* drawn over the app. One at a time: the next one loads once this one is
|
|
582
|
+
* closed. Always `null` with `boot({ posts: false })`.
|
|
583
|
+
* @since 1.2.0
|
|
584
|
+
*/
|
|
585
|
+
post: WidgetPost | null;
|
|
586
|
+
/**
|
|
587
|
+
* The post is open in full (tapped from its card, or sent as a full post).
|
|
588
|
+
* @since 1.2.0
|
|
589
|
+
*/
|
|
590
|
+
postExpanded: boolean;
|
|
566
591
|
}
|
|
567
592
|
|
|
568
593
|
export interface InAppNotification {
|
|
@@ -574,3 +599,61 @@ export interface InAppNotification {
|
|
|
574
599
|
preview: string;
|
|
575
600
|
hasAttachments: boolean;
|
|
576
601
|
}
|
|
602
|
+
|
|
603
|
+
/** What a visitor did with a post: saw it, opened it, tapped a link, reacted, replied, minimized or closed it. @since 1.2.0 */
|
|
604
|
+
export type PostInteractionKind = 'view' | 'open' | 'click' | 'reaction' | 'reply' | 'minimize' | 'dismiss';
|
|
605
|
+
|
|
606
|
+
/** Something the visitor already replied to a post (shown under it). @since 1.2.0 */
|
|
607
|
+
export interface PostReply {
|
|
608
|
+
text: string;
|
|
609
|
+
/** ISO time. */
|
|
610
|
+
at: string;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* A Send post as Workstation sends it for this visitor: already personalized
|
|
615
|
+
* and sanitized down to paragraphs, bold/italic/code, links, lists, code
|
|
616
|
+
* blocks, a button (`div[data-post-button]`) and social links
|
|
617
|
+
* (`div[data-post-social]`). Fields the SDK doesn't recognize fall back to the
|
|
618
|
+
* plainest option.
|
|
619
|
+
* @since 1.2.0
|
|
620
|
+
*/
|
|
621
|
+
export interface WidgetPost {
|
|
622
|
+
/** The Send campaign id. */
|
|
623
|
+
id: string;
|
|
624
|
+
bodyHtml: string;
|
|
625
|
+
/** First words of the post, for its card. */
|
|
626
|
+
snippet: string;
|
|
627
|
+
sender: { name: string; initials: string; avatarUrl: string | null } | null;
|
|
628
|
+
workspaceName: string;
|
|
629
|
+
/** `small` opens as a sheet, `large` as a full screen. */
|
|
630
|
+
format: 'small' | 'large';
|
|
631
|
+
replyType: 'text' | 'reaction' | 'none';
|
|
632
|
+
reactions: string[];
|
|
633
|
+
/** `badge`: a small pill; `snippet`: a card with the first words; `full`: opens right away. */
|
|
634
|
+
sentAs: 'badge' | 'snippet' | 'full';
|
|
635
|
+
/** The reaction this visitor already picked. */
|
|
636
|
+
reacted: string | null;
|
|
637
|
+
/** What they already replied, oldest first. */
|
|
638
|
+
replies: PostReply[];
|
|
639
|
+
/** They minimized it ("not now"): it waits as its badge or card instead of opening by itself. */
|
|
640
|
+
minimized: boolean;
|
|
641
|
+
/**
|
|
642
|
+
* The post's conversation with them, oldest first: their replies and the
|
|
643
|
+
* team's answers from the inbox. Older Workstation versions send only
|
|
644
|
+
* `replies`, which then fill it.
|
|
645
|
+
*/
|
|
646
|
+
thread: PostThreadMessage[];
|
|
647
|
+
/** That conversation, to continue it in the messenger (null before their first reply). */
|
|
648
|
+
conversationId: string | null;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** One message under a post: theirs, or a teammate's answer. @since 1.2.0 */
|
|
652
|
+
export interface PostThreadMessage {
|
|
653
|
+
from: 'person' | 'team';
|
|
654
|
+
text: string;
|
|
655
|
+
/** The teammate's name on team answers. */
|
|
656
|
+
senderName: string | null;
|
|
657
|
+
/** ISO time. */
|
|
658
|
+
at: string;
|
|
659
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* the modal (or never mounting it) never drops a conversation.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { useCallback, useMemo } from 'react';
|
|
9
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
10
10
|
import {
|
|
11
11
|
ActivityIndicator,
|
|
12
12
|
Modal,
|
|
@@ -29,16 +29,18 @@ import {
|
|
|
29
29
|
import { controller } from '../controller';
|
|
30
30
|
import { buildLabels, createTranslator, formatLabel, localizeWelcomeText } from '../i18n';
|
|
31
31
|
import { useWidgetState } from '../state/use-widget-state';
|
|
32
|
-
import type { AgentUiAction, OutgoingAttachment, WidgetSpaceType } from '../types';
|
|
32
|
+
import type { AgentUiAction, OutgoingAttachment, PostInteractionKind, WidgetSpaceType } from '../types';
|
|
33
33
|
import { WidgetErrorBoundary } from './components/ErrorBoundary';
|
|
34
34
|
import { AlertIcon } from './components/icons';
|
|
35
35
|
import { InAppNotification } from './components/InAppNotification';
|
|
36
|
+
import { PostOverlay } from './components/Post';
|
|
36
37
|
import { OutboundMessageCard } from './components/OutboundMessageCard';
|
|
37
38
|
import { WidgetPanel } from './components/WidgetPanel';
|
|
38
39
|
import { useKeyboardOverlap } from './hooks/use-keyboard-overlap';
|
|
39
40
|
import { TopInsetContext } from './hooks/use-top-inset';
|
|
40
41
|
import { useWidgetInsets } from './hooks/use-widget-insets';
|
|
41
42
|
import { ChatView } from './screens/chat/ChatView';
|
|
43
|
+
import type { ChatLabels } from './screens/chat/ChatView.types';
|
|
42
44
|
import { ConversationsList } from './screens/conversations/ConversationsList';
|
|
43
45
|
import { HomeView, resolveHeaderTextColor } from './screens/home/HomeView';
|
|
44
46
|
import { PreChatForm } from './screens/pre-chat/PreChatForm';
|
|
@@ -61,6 +63,9 @@ export interface RepzoWidgetProviderProps {
|
|
|
61
63
|
presentationStyle?: 'pageSheet' | 'fullScreen';
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/** Longest the messenger's iOS slide-out is waited on when `onDismiss` doesn't arrive. */
|
|
67
|
+
const MESSENGER_DISMISS_FALLBACK_MS = 600;
|
|
68
|
+
|
|
64
69
|
export function RepzoWidgetProvider({
|
|
65
70
|
children,
|
|
66
71
|
presentationStyle = 'pageSheet',
|
|
@@ -135,6 +140,17 @@ export function RepzoWidgetProvider({
|
|
|
135
140
|
professional: labels.blockProfessional,
|
|
136
141
|
priceOffer: labels.blockPriceOffer,
|
|
137
142
|
working: labels.working,
|
|
143
|
+
subtotal: labels.blockSubtotal,
|
|
144
|
+
discount: labels.blockDiscount,
|
|
145
|
+
tax: labels.blockTax,
|
|
146
|
+
shipping: labels.blockShipping,
|
|
147
|
+
total: labels.blockTotal,
|
|
148
|
+
minutes: labels.blockMinutes,
|
|
149
|
+
save: labels.blockSave,
|
|
150
|
+
anyAvailable: labels.blockAnyAvailable,
|
|
151
|
+
withProvider: labels.blockWithProvider,
|
|
152
|
+
bookTime: labels.blockBookTime,
|
|
153
|
+
validUntil: labels.blockValidUntil,
|
|
138
154
|
}),
|
|
139
155
|
[labels]
|
|
140
156
|
);
|
|
@@ -156,6 +172,62 @@ export function RepzoWidgetProvider({
|
|
|
156
172
|
[labels]
|
|
157
173
|
);
|
|
158
174
|
|
|
175
|
+
// Stable across renders so each message bubble only re-renders when its
|
|
176
|
+
// own message changes, not on every store update or keystroke.
|
|
177
|
+
const chatLabels = useMemo<ChatLabels>(
|
|
178
|
+
() => ({
|
|
179
|
+
back: labels.back,
|
|
180
|
+
loading: labels.loading,
|
|
181
|
+
loadEarlier: labels.loadEarlier,
|
|
182
|
+
emptyConversation: labels.emptyConversation,
|
|
183
|
+
messagePlaceholder: labels.messagePlaceholder,
|
|
184
|
+
failedToSend: labels.failedToSend,
|
|
185
|
+
thinking: labels.thinking,
|
|
186
|
+
team: labels.team,
|
|
187
|
+
statusOnline: labels.homeStatusOnline,
|
|
188
|
+
statusAway: labels.homeStatusAway,
|
|
189
|
+
close: labels.close,
|
|
190
|
+
today: labels.today,
|
|
191
|
+
yesterday: labels.yesterday,
|
|
192
|
+
conversationUpdated: labels.conversationUpdated,
|
|
193
|
+
messageDeleted: labels.messageDeleted,
|
|
194
|
+
messageSent: labels.messageSent,
|
|
195
|
+
messageSeen: labels.messageSeen,
|
|
196
|
+
waitingForTeammate: labels.waitingForTeammate,
|
|
197
|
+
joinedConversation: labels.joinedConversation,
|
|
198
|
+
procedureSend: labels.procedureSend,
|
|
199
|
+
procedureSent: labels.procedureSent,
|
|
200
|
+
procedureShareDetails: labels.procedureShareDetails,
|
|
201
|
+
procedureDetailsSent: labels.procedureDetailsSent,
|
|
202
|
+
procedureHide: labels.procedureHide,
|
|
203
|
+
procedureFieldCount: labels.procedureFieldCount,
|
|
204
|
+
procedureFieldCountOne: labels.procedureFieldCountOne,
|
|
205
|
+
procedureTitle: labels.procedureTitle,
|
|
206
|
+
procedureEmailPlaceholder: labels.procedureEmailPlaceholder,
|
|
207
|
+
procedurePhonePlaceholder: labels.procedurePhonePlaceholder,
|
|
208
|
+
blocks: blockLabels,
|
|
209
|
+
rating: {
|
|
210
|
+
question: labels.ratingQuestion,
|
|
211
|
+
reasonPrompt: labels.ratingReasonPrompt,
|
|
212
|
+
commentPlaceholder: labels.ratingCommentPlaceholder,
|
|
213
|
+
submit: labels.ratingSubmit,
|
|
214
|
+
done: labels.ratingDone,
|
|
215
|
+
thanks: labels.ratingThanks,
|
|
216
|
+
locked: labels.ratingLocked,
|
|
217
|
+
expired: labels.ratingExpired,
|
|
218
|
+
failed: labels.ratingFailed,
|
|
219
|
+
scale: [labels.rating1, labels.rating2, labels.rating3, labels.rating4, labels.rating5],
|
|
220
|
+
},
|
|
221
|
+
attach: attachLabels,
|
|
222
|
+
viewer: {
|
|
223
|
+
close: labels.close,
|
|
224
|
+
loadFailed: labels.imageLoadFailed,
|
|
225
|
+
openExternally: labels.openImageExternally,
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
[labels, blockLabels, attachLabels]
|
|
229
|
+
);
|
|
230
|
+
|
|
159
231
|
// Read per render: `boot({ attachments })` sets it outside React state.
|
|
160
232
|
const attachmentSources = theme.attachmentsEnabled ? getAttachmentSources() : [];
|
|
161
233
|
|
|
@@ -167,6 +239,57 @@ export function RepzoWidgetProvider({
|
|
|
167
239
|
);
|
|
168
240
|
const handleDismissNotification = useCallback(() => store.dismissInAppNotification(), [store]);
|
|
169
241
|
|
|
242
|
+
// iOS can't present the post's modal while the messenger's is still sliding
|
|
243
|
+
// away (it would silently never show), so a post waits for that to finish.
|
|
244
|
+
// `onDismiss` is the signal; the timer covers React Native versions where it
|
|
245
|
+
// doesn't fire.
|
|
246
|
+
const [messengerOnScreen, setMessengerOnScreen] = useState(state.isOpen);
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (state.isOpen || Platform.OS !== 'ios') {
|
|
249
|
+
setMessengerOnScreen(state.isOpen);
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const timer = setTimeout(() => setMessengerOnScreen(false), MESSENGER_DISMISS_FALLBACK_MS);
|
|
253
|
+
return () => clearTimeout(timer);
|
|
254
|
+
}, [state.isOpen]);
|
|
255
|
+
const handleMessengerDismiss = useCallback(() => setMessengerOnScreen(false), []);
|
|
256
|
+
|
|
257
|
+
const handlePostShown = useCallback((campaignId: string) => store.postDidShow(campaignId), [store]);
|
|
258
|
+
const handlePostExpand = useCallback((campaignId: string) => store.expandPost(campaignId), [store]);
|
|
259
|
+
const handlePostMinimize = useCallback((campaignId: string) => store.minimizePost(campaignId), [store]);
|
|
260
|
+
const handlePostCollapse = useCallback((campaignId: string) => store.collapsePost(campaignId), [store]);
|
|
261
|
+
// The post steps aside first: iOS can't present the messenger while the post's modal is still up.
|
|
262
|
+
const handlePostOpenConversation = useCallback(
|
|
263
|
+
(campaignId: string, conversationId: string) => {
|
|
264
|
+
store.collapsePost(campaignId);
|
|
265
|
+
setTimeout(() => void store.openConversation(conversationId), Platform.OS === 'ios' ? 400 : 0);
|
|
266
|
+
},
|
|
267
|
+
[store]
|
|
268
|
+
);
|
|
269
|
+
const handlePostInteract = useCallback(
|
|
270
|
+
(campaignId: string, kind: Exclude<PostInteractionKind, 'view' | 'open' | 'minimize'>, value?: string) =>
|
|
271
|
+
store.recordPostInteraction(campaignId, kind, value),
|
|
272
|
+
[store]
|
|
273
|
+
);
|
|
274
|
+
const postLabels = useMemo(
|
|
275
|
+
() => ({
|
|
276
|
+
from: labels.postFrom,
|
|
277
|
+
newMessage: labels.postNewMessage,
|
|
278
|
+
openHint: labels.postOpenHint,
|
|
279
|
+
close: labels.postClose,
|
|
280
|
+
minimize: labels.postMinimize,
|
|
281
|
+
replyPlaceholder: labels.postReplyPlaceholder,
|
|
282
|
+
send: labels.postSend,
|
|
283
|
+
sent: labels.postSent,
|
|
284
|
+
replyFailed: labels.postReplyFailed,
|
|
285
|
+
reactions: labels.postReactions,
|
|
286
|
+
thanks: labels.postThanks,
|
|
287
|
+
conversation: labels.postConversation,
|
|
288
|
+
continueChat: labels.postContinueChat,
|
|
289
|
+
}),
|
|
290
|
+
[labels]
|
|
291
|
+
);
|
|
292
|
+
|
|
170
293
|
const handleTabChange = useCallback((tab: WidgetSpaceType) => store.setActiveTab(tab), [store]);
|
|
171
294
|
|
|
172
295
|
// Rejections reach ChatView, which keeps unsent attachments for a retry.
|
|
@@ -176,6 +299,21 @@ export function RepzoWidgetProvider({
|
|
|
176
299
|
[store]
|
|
177
300
|
);
|
|
178
301
|
|
|
302
|
+
const handleIdentifyVisitor = useCallback(
|
|
303
|
+
(data: { email?: string; name?: string; phone?: string }) => store.identifyVisitor(data),
|
|
304
|
+
[store]
|
|
305
|
+
);
|
|
306
|
+
const handleRateConversation = useCallback(
|
|
307
|
+
(
|
|
308
|
+
messageId: string,
|
|
309
|
+
ratingId: string,
|
|
310
|
+
data: { rating: number; reason?: string; comment?: string }
|
|
311
|
+
) => store.rateConversation(messageId, ratingId, data),
|
|
312
|
+
[store]
|
|
313
|
+
);
|
|
314
|
+
const handleLoadMore = useCallback(() => void store.loadMoreMessages(), [store]);
|
|
315
|
+
const handleTyping = useCallback((isTyping: boolean) => store.notifyTyping(isTyping), [store]);
|
|
316
|
+
|
|
179
317
|
const handlePreChatSubmit = useCallback(
|
|
180
318
|
async (data: Record<string, string>) => {
|
|
181
319
|
try {
|
|
@@ -298,55 +436,14 @@ export function RepzoWidgetProvider({
|
|
|
298
436
|
defaultLocale={state.config?.defaultLocale}
|
|
299
437
|
prefillText={state.prefillText}
|
|
300
438
|
offlineMessage={theme.offlineMessage}
|
|
301
|
-
labels={
|
|
302
|
-
back: labels.back,
|
|
303
|
-
loading: labels.loading,
|
|
304
|
-
loadEarlier: labels.loadEarlier,
|
|
305
|
-
emptyConversation: labels.emptyConversation,
|
|
306
|
-
messagePlaceholder: labels.messagePlaceholder,
|
|
307
|
-
failedToSend: labels.failedToSend,
|
|
308
|
-
thinking: labels.thinking,
|
|
309
|
-
team: labels.team,
|
|
310
|
-
statusOnline: labels.homeStatusOnline,
|
|
311
|
-
statusAway: labels.homeStatusAway,
|
|
312
|
-
close: labels.close,
|
|
313
|
-
today: labels.today,
|
|
314
|
-
yesterday: labels.yesterday,
|
|
315
|
-
conversationUpdated: labels.conversationUpdated,
|
|
316
|
-
messageDeleted: labels.messageDeleted,
|
|
317
|
-
messageSent: labels.messageSent,
|
|
318
|
-
messageSeen: labels.messageSeen,
|
|
319
|
-
waitingForTeammate: labels.waitingForTeammate,
|
|
320
|
-
joinedConversation: labels.joinedConversation,
|
|
321
|
-
procedureSend: labels.procedureSend,
|
|
322
|
-
procedureSent: labels.procedureSent,
|
|
323
|
-
blocks: blockLabels,
|
|
324
|
-
rating: {
|
|
325
|
-
question: labels.ratingQuestion,
|
|
326
|
-
reasonPrompt: labels.ratingReasonPrompt,
|
|
327
|
-
commentPlaceholder: labels.ratingCommentPlaceholder,
|
|
328
|
-
submit: labels.ratingSubmit,
|
|
329
|
-
done: labels.ratingDone,
|
|
330
|
-
thanks: labels.ratingThanks,
|
|
331
|
-
locked: labels.ratingLocked,
|
|
332
|
-
expired: labels.ratingExpired,
|
|
333
|
-
failed: labels.ratingFailed,
|
|
334
|
-
scale: [labels.rating1, labels.rating2, labels.rating3, labels.rating4, labels.rating5],
|
|
335
|
-
},
|
|
336
|
-
attach: attachLabels,
|
|
337
|
-
viewer: {
|
|
338
|
-
close: labels.close,
|
|
339
|
-
loadFailed: labels.imageLoadFailed,
|
|
340
|
-
openExternally: labels.openImageExternally,
|
|
341
|
-
},
|
|
342
|
-
}}
|
|
439
|
+
labels={chatLabels}
|
|
343
440
|
attachmentSources={attachmentSources}
|
|
344
441
|
onPickAttachments={pickAttachments}
|
|
345
442
|
onSendMessage={handleSendMessage}
|
|
346
|
-
onIdentifyVisitor={
|
|
347
|
-
onRateConversation={
|
|
348
|
-
onLoadMore={
|
|
349
|
-
onTyping={
|
|
443
|
+
onIdentifyVisitor={handleIdentifyVisitor}
|
|
444
|
+
onRateConversation={handleRateConversation}
|
|
445
|
+
onLoadMore={handleLoadMore}
|
|
446
|
+
onTyping={handleTyping}
|
|
350
447
|
onBack={handleBack}
|
|
351
448
|
onClose={handleClose}
|
|
352
449
|
/>
|
|
@@ -421,6 +518,26 @@ export function RepzoWidgetProvider({
|
|
|
421
518
|
return (
|
|
422
519
|
<>
|
|
423
520
|
{children}
|
|
521
|
+
{/* A post steps aside while the messenger is open, and its card while a reply card shows. */}
|
|
522
|
+
{state.post && !state.isOpen && !messengerOnScreen && (state.postExpanded || !state.inAppNotification) ? (
|
|
523
|
+
<WidgetErrorBoundary resetKey={state.post.id} fallback={() => null}>
|
|
524
|
+
<PostOverlay
|
|
525
|
+
// A new post remounts, so it reports its view.
|
|
526
|
+
key={state.post.id}
|
|
527
|
+
post={state.post}
|
|
528
|
+
expanded={state.postExpanded}
|
|
529
|
+
color={theme.primaryColor}
|
|
530
|
+
insets={insets}
|
|
531
|
+
labels={postLabels}
|
|
532
|
+
onShown={handlePostShown}
|
|
533
|
+
onExpand={handlePostExpand}
|
|
534
|
+
onMinimize={handlePostMinimize}
|
|
535
|
+
onCollapse={handlePostCollapse}
|
|
536
|
+
onOpenConversation={handlePostOpenConversation}
|
|
537
|
+
onInteract={handlePostInteract}
|
|
538
|
+
/>
|
|
539
|
+
</WidgetErrorBoundary>
|
|
540
|
+
) : null}
|
|
424
541
|
{state.inAppNotification ? (
|
|
425
542
|
<InAppNotification
|
|
426
543
|
// A new reply remounts the card, so it slides in fresh.
|
|
@@ -444,6 +561,7 @@ export function RepzoWidgetProvider({
|
|
|
444
561
|
animationType="slide"
|
|
445
562
|
presentationStyle={Platform.OS === 'ios' ? presentationStyle : undefined}
|
|
446
563
|
onRequestClose={handleClose}
|
|
564
|
+
onDismiss={handleMessengerDismiss}
|
|
447
565
|
// Edge to edge on every Android version, so the insets below are the
|
|
448
566
|
// only top spacing (a non-translucent modal used to add a second one).
|
|
449
567
|
statusBarTranslucent>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { createContext, useContext, useState } from 'react';
|
|
9
9
|
import { Image, Pressable, StyleSheet, Text, TextInput, View, type ViewStyle } from 'react-native';
|
|
10
10
|
|
|
11
|
+
import { formatLabel } from '../../i18n';
|
|
11
12
|
import type { AgentUiAction, AgentUiBlock } from '../../types';
|
|
12
13
|
import { fontSize, palette, radius, shadows, spacing, withAlpha } from '../styles/theme';
|
|
13
14
|
|
|
@@ -18,6 +19,20 @@ export interface AgentUiBlockLabels {
|
|
|
18
19
|
professional: string;
|
|
19
20
|
priceOffer: string;
|
|
20
21
|
working: string;
|
|
22
|
+
subtotal: string;
|
|
23
|
+
discount: string;
|
|
24
|
+
tax: string;
|
|
25
|
+
shipping: string;
|
|
26
|
+
total: string;
|
|
27
|
+
/** `{count}` = minutes. */
|
|
28
|
+
minutes: string;
|
|
29
|
+
save: string;
|
|
30
|
+
anyAvailable: string;
|
|
31
|
+
/** `{name}` = the professional. */
|
|
32
|
+
withProvider: string;
|
|
33
|
+
bookTime: string;
|
|
34
|
+
/** `{date}` = the formatted date. */
|
|
35
|
+
validUntil: string;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
const DEFAULT_BLOCK_LABELS: AgentUiBlockLabels = {
|
|
@@ -27,6 +42,17 @@ const DEFAULT_BLOCK_LABELS: AgentUiBlockLabels = {
|
|
|
27
42
|
professional: 'Professional',
|
|
28
43
|
priceOffer: 'Price offer',
|
|
29
44
|
working: 'Working...',
|
|
45
|
+
subtotal: 'Subtotal',
|
|
46
|
+
discount: 'Discount',
|
|
47
|
+
tax: 'Tax',
|
|
48
|
+
shipping: 'Shipping',
|
|
49
|
+
total: 'Total',
|
|
50
|
+
minutes: '{count} min',
|
|
51
|
+
save: 'Save',
|
|
52
|
+
anyAvailable: 'Any available',
|
|
53
|
+
withProvider: 'With {name}',
|
|
54
|
+
bookTime: 'Book this time',
|
|
55
|
+
validUntil: 'Valid until {date}',
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
const BlockLabelContext = createContext<AgentUiBlockLabels>(DEFAULT_BLOCK_LABELS);
|
|
@@ -99,7 +125,7 @@ export function AgentUiBlocks({
|
|
|
99
125
|
<View style={styles.row}>
|
|
100
126
|
<Text style={styles.muted}>
|
|
101
127
|
{block.product.durationMinutes
|
|
102
|
-
?
|
|
128
|
+
? formatLabel(labels.minutes, { count: block.product.durationMinutes })
|
|
103
129
|
: block.product.unit || ''}
|
|
104
130
|
</Text>
|
|
105
131
|
<Text style={styles.price}>
|
|
@@ -182,23 +208,23 @@ export function AgentUiBlocks({
|
|
|
182
208
|
))}
|
|
183
209
|
</View>
|
|
184
210
|
<View style={styles.totals}>
|
|
185
|
-
<MoneyRow label=
|
|
211
|
+
<MoneyRow label={labels.subtotal} value={formatMoney(block.subtotal, block.currency)} />
|
|
186
212
|
{block.discount ? (
|
|
187
213
|
<MoneyRow
|
|
188
|
-
label=
|
|
214
|
+
label={labels.discount}
|
|
189
215
|
value={`−${formatMoney(block.discount, block.currency)}`}
|
|
190
216
|
/>
|
|
191
217
|
) : null}
|
|
192
218
|
{block.tax ? (
|
|
193
|
-
<MoneyRow label=
|
|
219
|
+
<MoneyRow label={labels.tax} value={formatMoney(block.tax, block.currency)} />
|
|
194
220
|
) : null}
|
|
195
221
|
{block.shipping ? (
|
|
196
222
|
<MoneyRow
|
|
197
|
-
label=
|
|
223
|
+
label={labels.shipping}
|
|
198
224
|
value={formatMoney(block.shipping, block.currency)}
|
|
199
225
|
/>
|
|
200
226
|
) : null}
|
|
201
|
-
<MoneyRow label=
|
|
227
|
+
<MoneyRow label={labels.total} value={formatMoney(block.total, block.currency)} strong />
|
|
202
228
|
</View>
|
|
203
229
|
{block.action ? (
|
|
204
230
|
<View style={styles.blockAction}>
|
|
@@ -314,7 +340,7 @@ export function AgentUiBlocks({
|
|
|
314
340
|
hitSlop={8}>
|
|
315
341
|
<Text
|
|
316
342
|
style={[styles.linkButton, { color: checked ? color : palette.textSubtle }]}>
|
|
317
|
-
|
|
343
|
+
{labels.save}
|
|
318
344
|
</Text>
|
|
319
345
|
</Pressable>
|
|
320
346
|
</View>
|
|
@@ -339,7 +365,7 @@ export function AgentUiBlocks({
|
|
|
339
365
|
<View key={block.id} style={[styles.card, styles.cardPadded]}>
|
|
340
366
|
<Text style={styles.title}>{labels.chooseTime}</Text>
|
|
341
367
|
<Text style={styles.description}>
|
|
342
|
-
{block.serviceName} · {block.durationMinutes}
|
|
368
|
+
{block.serviceName} · {formatLabel(labels.minutes, { count: block.durationMinutes })}
|
|
343
369
|
{block.timezone ? ` · ${block.timezone}` : ''}
|
|
344
370
|
</Text>
|
|
345
371
|
|
|
@@ -351,7 +377,7 @@ export function AgentUiBlocks({
|
|
|
351
377
|
selected={activeProviderId === 'any'}
|
|
352
378
|
color={color}
|
|
353
379
|
disabled={pending !== null}
|
|
354
|
-
label=
|
|
380
|
+
label={labels.anyAvailable}
|
|
355
381
|
onPress={() =>
|
|
356
382
|
activeProviderId !== 'any' &&
|
|
357
383
|
act({
|
|
@@ -425,7 +451,7 @@ export function AgentUiBlocks({
|
|
|
425
451
|
|
|
426
452
|
{selectedProvider ? (
|
|
427
453
|
<Text style={[styles.muted, styles.blockAction]}>
|
|
428
|
-
|
|
454
|
+
{formatLabel(labels.withProvider, { name: selectedProvider.name })}
|
|
429
455
|
</Text>
|
|
430
456
|
) : null}
|
|
431
457
|
<View style={styles.blockAction}>
|
|
@@ -453,7 +479,7 @@ export function AgentUiBlocks({
|
|
|
453
479
|
},
|
|
454
480
|
})
|
|
455
481
|
}>
|
|
456
|
-
|
|
482
|
+
{labels.bookTime}
|
|
457
483
|
</ActionButton>
|
|
458
484
|
</View>
|
|
459
485
|
</View>
|
|
@@ -477,7 +503,9 @@ export function AgentUiBlocks({
|
|
|
477
503
|
<Text style={styles.amount}>{formatMoney(block.amount, block.currency)}</Text>
|
|
478
504
|
{block.validUntil ? (
|
|
479
505
|
<Text style={styles.muted}>
|
|
480
|
-
|
|
506
|
+
{formatLabel(labels.validUntil, {
|
|
507
|
+
date: formatDate(block.validUntil.slice(0, 10)),
|
|
508
|
+
})}
|
|
481
509
|
</Text>
|
|
482
510
|
) : null}
|
|
483
511
|
{publicActions.map((action) => (
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
|
11
11
|
|
|
12
|
+
import { createTranslator, DEFAULT_LABELS } from '../../i18n';
|
|
12
13
|
import { useWidgetState } from '../../state/use-widget-state';
|
|
13
14
|
import { controller } from '../../controller';
|
|
14
15
|
import { ChatIcon, ChevronIcon } from './icons';
|
|
@@ -47,6 +48,10 @@ export function Launcher({
|
|
|
47
48
|
const inset = Math.max(horizontalPadding ?? config?.launcherHorizontalPadding ?? 20, 20);
|
|
48
49
|
const text = config?.bubbleText;
|
|
49
50
|
const showText = !!text && !state.isOpen;
|
|
51
|
+
const translate = createTranslator(config, state.locale);
|
|
52
|
+
const accessibilityLabel = state.isOpen
|
|
53
|
+
? translate('launcherClose', DEFAULT_LABELS.launcherClose)
|
|
54
|
+
: translate('launcherOpen', DEFAULT_LABELS.launcherOpen);
|
|
50
55
|
|
|
51
56
|
return (
|
|
52
57
|
<View
|
|
@@ -55,7 +60,7 @@ export function Launcher({
|
|
|
55
60
|
<Pressable
|
|
56
61
|
onPress={() => controller.store.toggle()}
|
|
57
62
|
accessibilityRole="button"
|
|
58
|
-
accessibilityLabel={
|
|
63
|
+
accessibilityLabel={accessibilityLabel}
|
|
59
64
|
style={({ pressed }) => [
|
|
60
65
|
styles.bubble,
|
|
61
66
|
showText ? styles.bubbleWithText : styles.bubbleRound,
|