@appsupportkit/support-sdk 0.3.0 → 0.4.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/lib/commonjs/api.js +49 -15
- package/lib/commonjs/api.js.map +1 -1
- package/lib/commonjs/components/BottomSheet.js +1 -1
- package/lib/commonjs/components/BottomSheet.js.map +1 -1
- package/lib/commonjs/components/LauncherButton.js +4 -4
- package/lib/commonjs/components/LauncherButton.js.map +1 -1
- package/lib/commonjs/components/SupportButton.js +123 -97
- package/lib/commonjs/components/SupportButton.js.map +1 -1
- package/lib/commonjs/components/icons.js +81 -110
- package/lib/commonjs/components/icons.js.map +1 -1
- package/lib/commonjs/components/primitives.js +0 -153
- package/lib/commonjs/components/primitives.js.map +1 -1
- package/lib/commonjs/components/screens/ChatScreen.js +487 -0
- package/lib/commonjs/components/screens/ChatScreen.js.map +1 -0
- package/lib/commonjs/i18n/strings/de.js +3 -0
- package/lib/commonjs/i18n/strings/de.js.map +1 -1
- package/lib/commonjs/i18n/strings/en.js +3 -0
- package/lib/commonjs/i18n/strings/en.js.map +1 -1
- package/lib/commonjs/i18n/strings/es.js +3 -0
- package/lib/commonjs/i18n/strings/es.js.map +1 -1
- package/lib/commonjs/i18n/strings/fr.js +3 -0
- package/lib/commonjs/i18n/strings/fr.js.map +1 -1
- package/lib/commonjs/i18n/strings/hi.js +3 -0
- package/lib/commonjs/i18n/strings/hi.js.map +1 -1
- package/lib/commonjs/i18n/strings/it.js +3 -0
- package/lib/commonjs/i18n/strings/it.js.map +1 -1
- package/lib/commonjs/i18n/strings/ja.js +3 -0
- package/lib/commonjs/i18n/strings/ja.js.map +1 -1
- package/lib/commonjs/i18n/strings/ko.js +3 -0
- package/lib/commonjs/i18n/strings/ko.js.map +1 -1
- package/lib/commonjs/i18n/strings/nl.js +3 -0
- package/lib/commonjs/i18n/strings/nl.js.map +1 -1
- package/lib/commonjs/i18n/strings/pl.js +3 -0
- package/lib/commonjs/i18n/strings/pl.js.map +1 -1
- package/lib/commonjs/i18n/strings/pt.js +3 -0
- package/lib/commonjs/i18n/strings/pt.js.map +1 -1
- package/lib/commonjs/i18n/strings/ru.js +3 -0
- package/lib/commonjs/i18n/strings/ru.js.map +1 -1
- package/lib/commonjs/i18n/strings/tr.js +3 -0
- package/lib/commonjs/i18n/strings/tr.js.map +1 -1
- package/lib/commonjs/i18n/strings/zh.js +3 -0
- package/lib/commonjs/i18n/strings/zh.js.map +1 -1
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/push.js +78 -3
- package/lib/commonjs/push.js.map +1 -1
- package/lib/commonjs/review.js +54 -0
- package/lib/commonjs/review.js.map +1 -0
- package/lib/commonjs/theme.js +142 -23
- package/lib/commonjs/theme.js.map +1 -1
- package/lib/commonjs/types.js +21 -3
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/useSupportThreads.js +22 -15
- package/lib/commonjs/useSupportThreads.js.map +1 -1
- package/lib/commonjs/utils.js +13 -33
- package/lib/commonjs/utils.js.map +1 -1
- package/lib/commonjs/version.js +13 -0
- package/lib/commonjs/version.js.map +1 -0
- package/lib/module/api.js +46 -13
- package/lib/module/api.js.map +1 -1
- package/lib/module/components/BottomSheet.js +1 -1
- package/lib/module/components/BottomSheet.js.map +1 -1
- package/lib/module/components/LauncherButton.js +5 -5
- package/lib/module/components/LauncherButton.js.map +1 -1
- package/lib/module/components/SupportButton.js +128 -102
- package/lib/module/components/SupportButton.js.map +1 -1
- package/lib/module/components/icons.js +80 -107
- package/lib/module/components/icons.js.map +1 -1
- package/lib/module/components/primitives.js +2 -146
- package/lib/module/components/primitives.js.map +1 -1
- package/lib/module/components/screens/ChatScreen.js +485 -0
- package/lib/module/components/screens/ChatScreen.js.map +1 -0
- package/lib/module/i18n/strings/de.js +3 -0
- package/lib/module/i18n/strings/de.js.map +1 -1
- package/lib/module/i18n/strings/en.js +3 -0
- package/lib/module/i18n/strings/en.js.map +1 -1
- package/lib/module/i18n/strings/es.js +3 -0
- package/lib/module/i18n/strings/es.js.map +1 -1
- package/lib/module/i18n/strings/fr.js +3 -0
- package/lib/module/i18n/strings/fr.js.map +1 -1
- package/lib/module/i18n/strings/hi.js +3 -0
- package/lib/module/i18n/strings/hi.js.map +1 -1
- package/lib/module/i18n/strings/it.js +3 -0
- package/lib/module/i18n/strings/it.js.map +1 -1
- package/lib/module/i18n/strings/ja.js +3 -0
- package/lib/module/i18n/strings/ja.js.map +1 -1
- package/lib/module/i18n/strings/ko.js +3 -0
- package/lib/module/i18n/strings/ko.js.map +1 -1
- package/lib/module/i18n/strings/nl.js +3 -0
- package/lib/module/i18n/strings/nl.js.map +1 -1
- package/lib/module/i18n/strings/pl.js +3 -0
- package/lib/module/i18n/strings/pl.js.map +1 -1
- package/lib/module/i18n/strings/pt.js +3 -0
- package/lib/module/i18n/strings/pt.js.map +1 -1
- package/lib/module/i18n/strings/ru.js +3 -0
- package/lib/module/i18n/strings/ru.js.map +1 -1
- package/lib/module/i18n/strings/tr.js +3 -0
- package/lib/module/i18n/strings/tr.js.map +1 -1
- package/lib/module/i18n/strings/zh.js +3 -0
- package/lib/module/i18n/strings/zh.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/push.js +77 -3
- package/lib/module/push.js.map +1 -1
- package/lib/module/review.js +48 -0
- package/lib/module/review.js.map +1 -0
- package/lib/module/theme.js +141 -23
- package/lib/module/theme.js.map +1 -1
- package/lib/module/types.js +21 -3
- package/lib/module/types.js.map +1 -1
- package/lib/module/useSupportThreads.js +22 -15
- package/lib/module/useSupportThreads.js.map +1 -1
- package/lib/module/utils.js +12 -32
- package/lib/module/utils.js.map +1 -1
- package/lib/module/version.js +9 -0
- package/lib/module/version.js.map +1 -0
- package/lib/typescript/api.d.ts +33 -12
- package/lib/typescript/api.d.ts.map +1 -1
- package/lib/typescript/components/SupportButton.d.ts +2 -2
- package/lib/typescript/components/SupportButton.d.ts.map +1 -1
- package/lib/typescript/components/icons.d.ts +5 -14
- package/lib/typescript/components/icons.d.ts.map +1 -1
- package/lib/typescript/components/primitives.d.ts +0 -31
- package/lib/typescript/components/primitives.d.ts.map +1 -1
- package/lib/typescript/components/screens/ChatScreen.d.ts +21 -0
- package/lib/typescript/components/screens/ChatScreen.d.ts.map +1 -0
- package/lib/typescript/i18n/strings/de.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/en.d.ts +3 -0
- package/lib/typescript/i18n/strings/en.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/es.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/fr.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/hi.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/it.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/ja.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/ko.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/nl.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/pl.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/pt.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/ru.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/tr.d.ts.map +1 -1
- package/lib/typescript/i18n/strings/zh.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +3 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/push.d.ts +14 -2
- package/lib/typescript/push.d.ts.map +1 -1
- package/lib/typescript/review.d.ts +15 -0
- package/lib/typescript/review.d.ts.map +1 -0
- package/lib/typescript/theme.d.ts +23 -5
- package/lib/typescript/theme.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +54 -11
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/useSupportThreads.d.ts +9 -3
- package/lib/typescript/useSupportThreads.d.ts.map +1 -1
- package/lib/typescript/utils.d.ts +3 -2
- package/lib/typescript/utils.d.ts.map +1 -1
- package/lib/typescript/version.d.ts +7 -0
- package/lib/typescript/version.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/api.ts +59 -30
- package/src/components/BottomSheet.tsx +1 -1
- package/src/components/LauncherButton.tsx +4 -4
- package/src/components/SupportButton.tsx +140 -111
- package/src/components/icons.tsx +60 -101
- package/src/components/primitives.tsx +1 -163
- package/src/components/screens/ChatScreen.tsx +541 -0
- package/src/i18n/strings/de.ts +3 -0
- package/src/i18n/strings/en.ts +3 -0
- package/src/i18n/strings/es.ts +3 -0
- package/src/i18n/strings/fr.ts +3 -0
- package/src/i18n/strings/hi.ts +3 -0
- package/src/i18n/strings/it.ts +3 -0
- package/src/i18n/strings/ja.ts +3 -0
- package/src/i18n/strings/ko.ts +3 -0
- package/src/i18n/strings/nl.ts +3 -0
- package/src/i18n/strings/pl.ts +3 -0
- package/src/i18n/strings/pt.ts +3 -0
- package/src/i18n/strings/ru.ts +3 -0
- package/src/i18n/strings/tr.ts +3 -0
- package/src/i18n/strings/zh.ts +3 -0
- package/src/index.tsx +5 -0
- package/src/push.ts +100 -2
- package/src/review.ts +54 -0
- package/src/theme.ts +182 -28
- package/src/types.ts +53 -11
- package/src/useSupportThreads.ts +31 -18
- package/src/utils.ts +14 -21
- package/src/version.ts +6 -0
- package/lib/commonjs/components/screens/ComposeScreen.js +0 -219
- package/lib/commonjs/components/screens/ComposeScreen.js.map +0 -1
- package/lib/commonjs/components/screens/HomeScreen.js +0 -334
- package/lib/commonjs/components/screens/HomeScreen.js.map +0 -1
- package/lib/commonjs/components/screens/ThreadScreen.js +0 -339
- package/lib/commonjs/components/screens/ThreadScreen.js.map +0 -1
- package/lib/module/components/screens/ComposeScreen.js +0 -217
- package/lib/module/components/screens/ComposeScreen.js.map +0 -1
- package/lib/module/components/screens/HomeScreen.js +0 -330
- package/lib/module/components/screens/HomeScreen.js.map +0 -1
- package/lib/module/components/screens/ThreadScreen.js +0 -337
- package/lib/module/components/screens/ThreadScreen.js.map +0 -1
- package/lib/typescript/components/screens/ComposeScreen.d.ts +0 -12
- package/lib/typescript/components/screens/ComposeScreen.d.ts.map +0 -1
- package/lib/typescript/components/screens/HomeScreen.d.ts +0 -19
- package/lib/typescript/components/screens/HomeScreen.d.ts.map +0 -1
- package/lib/typescript/components/screens/ThreadScreen.d.ts +0 -12
- package/lib/typescript/components/screens/ThreadScreen.d.ts.map +0 -1
- package/src/components/screens/ComposeScreen.tsx +0 -229
- package/src/components/screens/HomeScreen.tsx +0 -346
- package/src/components/screens/ThreadScreen.tsx +0 -424
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { Text, View } from 'react-native';
|
|
2
|
+
import { Platform, Text, useColorScheme, View } from 'react-native';
|
|
3
3
|
import {
|
|
4
|
-
postMessage,
|
|
5
4
|
registerPush,
|
|
5
|
+
registerUser,
|
|
6
|
+
reviewOpened,
|
|
7
|
+
sendMessage,
|
|
6
8
|
setLanguage as setLanguageRequest,
|
|
7
|
-
submitQuery,
|
|
8
9
|
SupportApiError,
|
|
9
10
|
type ApiConfig,
|
|
10
11
|
} from '../api';
|
|
@@ -15,34 +16,50 @@ import {
|
|
|
15
16
|
deviceLanguage,
|
|
16
17
|
type Language,
|
|
17
18
|
} from '../i18n/languages';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
ensurePushPermission,
|
|
21
|
+
isPushAvailable,
|
|
22
|
+
onSupportNotificationTap,
|
|
23
|
+
} from '../push';
|
|
24
|
+
import { openStoreReview, storeLinks, storePlatform } from '../review';
|
|
25
|
+
import { themeFor } from '../theme';
|
|
26
|
+
import type { SupportButtonProps, ThemeName } from '../types';
|
|
21
27
|
import { IDLE_POLL_MS, useSupportThreads } from '../useSupportThreads';
|
|
22
|
-
import { validateProps } from '../utils';
|
|
28
|
+
import { userPlan, validateProps } from '../utils';
|
|
23
29
|
import { BottomSheet } from './BottomSheet';
|
|
24
30
|
import { LauncherButton } from './LauncherButton';
|
|
25
|
-
import {
|
|
26
|
-
import { HomeScreen } from './screens/HomeScreen';
|
|
31
|
+
import { ChatScreen } from './screens/ChatScreen';
|
|
27
32
|
import { LanguageScreen } from './screens/LanguageScreen';
|
|
28
|
-
import { ThreadScreen } from './screens/ThreadScreen';
|
|
29
33
|
|
|
30
|
-
type Screen =
|
|
31
|
-
| { name: 'home' }
|
|
32
|
-
| { name: 'language' }
|
|
33
|
-
| { name: 'compose'; type: QueryType }
|
|
34
|
-
| { name: 'thread'; threadId: string };
|
|
34
|
+
type Screen = 'chat' | 'language';
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* The single entry point of the SDK: a launcher with an unread badge that
|
|
38
|
-
* opens the support bottom sheet.
|
|
39
|
-
* handled by useSupportThreads.
|
|
38
|
+
* opens the support chat in a bottom sheet. Conversations, polling, and read
|
|
39
|
+
* receipts are handled by useSupportThreads.
|
|
40
40
|
*/
|
|
41
41
|
export function SupportButton(props: SupportButtonProps) {
|
|
42
42
|
const validationError = validateProps(props);
|
|
43
|
+
|
|
44
|
+
// "system" (the default) follows the host app's appearance and re-renders
|
|
45
|
+
// when it changes.
|
|
46
|
+
const colorScheme = useColorScheme();
|
|
47
|
+
const themeName: ThemeName =
|
|
48
|
+
props.theme === 'light' || props.theme === 'dark'
|
|
49
|
+
? props.theme
|
|
50
|
+
: colorScheme === 'dark'
|
|
51
|
+
? 'dark'
|
|
52
|
+
: 'light';
|
|
53
|
+
// Picked down to one string first, so an inline `{ light, dark }` object
|
|
54
|
+
// doesn't rebuild the theme on every render.
|
|
55
|
+
const perModeAccent =
|
|
56
|
+
typeof props.accentColor === 'object' && props.accentColor !== null;
|
|
57
|
+
const accent = perModeAccent
|
|
58
|
+
? (props.accentColor as { light: string; dark: string })[themeName]
|
|
59
|
+
: (props.accentColor as string | undefined);
|
|
43
60
|
const theme = useMemo(
|
|
44
|
-
() =>
|
|
45
|
-
[
|
|
61
|
+
() => themeFor(themeName, accent, !perModeAccent),
|
|
62
|
+
[themeName, accent, perModeAccent],
|
|
46
63
|
);
|
|
47
64
|
|
|
48
65
|
const config: ApiConfig = useMemo(
|
|
@@ -55,16 +72,23 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
55
72
|
);
|
|
56
73
|
|
|
57
74
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
58
|
-
const [screen, setScreen] = useState<Screen>(
|
|
75
|
+
const [screen, setScreen] = useState<Screen>('chat');
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What the user picked, once they have picked. Until then the sheet is
|
|
79
|
+
* rendered in the host app's hint or the device's language, which is a
|
|
80
|
+
* guess — hence `chosen`, which is what decides whether to ask.
|
|
81
|
+
*/
|
|
82
|
+
const [chosen, setChosen] = useState<Language | null>(null);
|
|
59
83
|
|
|
60
84
|
const {
|
|
61
85
|
threads,
|
|
62
86
|
totalUnread,
|
|
63
87
|
loading,
|
|
64
|
-
refreshing,
|
|
65
88
|
errorKey,
|
|
66
89
|
serverLanguage,
|
|
67
90
|
languageLoaded,
|
|
91
|
+
store,
|
|
68
92
|
refresh,
|
|
69
93
|
reload,
|
|
70
94
|
} = useSupportThreads({
|
|
@@ -72,16 +96,12 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
72
96
|
revenuecatId: props.user.revenuecatId,
|
|
73
97
|
enabled: validationError === null,
|
|
74
98
|
sheetOpen,
|
|
75
|
-
|
|
99
|
+
// Not while the first-run language prompt covers it: nothing behind
|
|
100
|
+
// the prompt has been seen, so nothing may be marked read.
|
|
101
|
+
chatVisible: sheetOpen && screen === 'chat' && chosen !== null,
|
|
76
102
|
idlePollMs: props.pollIntervalMs ?? IDLE_POLL_MS,
|
|
77
103
|
});
|
|
78
104
|
|
|
79
|
-
/**
|
|
80
|
-
* What the user picked, once they have picked. Until then the sheet is
|
|
81
|
-
* rendered in the host app's hint or the device's language, which is a
|
|
82
|
-
* guess — hence `chosen`, which is what decides whether to ask.
|
|
83
|
-
*/
|
|
84
|
-
const [chosen, setChosen] = useState<Language | null>(null);
|
|
85
105
|
const [draftLanguage, setDraftLanguage] = useState<Language | null>(null);
|
|
86
106
|
const [savingLanguage, setSavingLanguage] = useState(false);
|
|
87
107
|
const [languageError, setLanguageError] = useState<string | null>(null);
|
|
@@ -129,8 +149,60 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
129
149
|
}
|
|
130
150
|
}, [config, props.user.revenuecatId, props.expoProjectId, validationError]);
|
|
131
151
|
|
|
152
|
+
const { name: userName, revenuecatId, appVersion } = props.user;
|
|
153
|
+
const plan = userPlan(props.user);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Reports the user on load, and whenever who they are changes, so the team
|
|
157
|
+
* can find and message them before they ever open support. Refreshes the
|
|
158
|
+
* push token too when permission was already granted — never prompting:
|
|
159
|
+
* the OS prompt only appears once the user opens support.
|
|
160
|
+
*/
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (validationError !== null) return;
|
|
163
|
+
registerUser(config, {
|
|
164
|
+
revenuecatId,
|
|
165
|
+
user: { name: userName, plan, appVersion, platform: storePlatform() },
|
|
166
|
+
}).catch(() => {
|
|
167
|
+
// Best effort; the next load reports again.
|
|
168
|
+
});
|
|
169
|
+
if (!isPushAvailable) return;
|
|
170
|
+
void (async () => {
|
|
171
|
+
const registration = await ensurePushPermission(props.expoProjectId, {
|
|
172
|
+
prompt: false,
|
|
173
|
+
});
|
|
174
|
+
if (registration.status !== 'granted') return;
|
|
175
|
+
await registerPush(config, {
|
|
176
|
+
revenuecatId,
|
|
177
|
+
permissionStatus: registration.status,
|
|
178
|
+
expoPushToken: registration.token,
|
|
179
|
+
asked: false,
|
|
180
|
+
});
|
|
181
|
+
})().catch(() => {});
|
|
182
|
+
}, [
|
|
183
|
+
config,
|
|
184
|
+
validationError,
|
|
185
|
+
revenuecatId,
|
|
186
|
+
userName,
|
|
187
|
+
plan,
|
|
188
|
+
appVersion,
|
|
189
|
+
props.expoProjectId,
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
// A tapped support-reply notification opens the chat. If the threads
|
|
193
|
+
// haven't loaded yet (cold start), it fills in as soon as they do.
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (validationError !== null) return;
|
|
196
|
+
return onSupportNotificationTap(() => {
|
|
197
|
+
setDraftLanguage(null);
|
|
198
|
+
setLanguageError(null);
|
|
199
|
+
setScreen('chat');
|
|
200
|
+
setSheetOpen(true);
|
|
201
|
+
});
|
|
202
|
+
}, [validationError]);
|
|
203
|
+
|
|
132
204
|
function openSheet() {
|
|
133
|
-
setScreen(
|
|
205
|
+
setScreen('chat');
|
|
134
206
|
setSheetOpen(true);
|
|
135
207
|
// The OS prompt appears right as the sheet opens; if the user declines,
|
|
136
208
|
// everything below carries on exactly as before.
|
|
@@ -139,7 +211,7 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
139
211
|
|
|
140
212
|
function closeSheet() {
|
|
141
213
|
setSheetOpen(false);
|
|
142
|
-
setScreen(
|
|
214
|
+
setScreen('chat');
|
|
143
215
|
setDraftLanguage(null);
|
|
144
216
|
setLanguageError(null);
|
|
145
217
|
}
|
|
@@ -193,7 +265,7 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
193
265
|
});
|
|
194
266
|
setChosen(next);
|
|
195
267
|
setDraftLanguage(null);
|
|
196
|
-
setScreen(
|
|
268
|
+
setScreen('chat');
|
|
197
269
|
// On a change, the server re-queues existing replies for translation
|
|
198
270
|
// into the new language; they arrive over the next poll or two.
|
|
199
271
|
void reload();
|
|
@@ -208,37 +280,13 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
208
280
|
}
|
|
209
281
|
}
|
|
210
282
|
|
|
211
|
-
async function
|
|
212
|
-
type: QueryType,
|
|
213
|
-
message: string,
|
|
214
|
-
): Promise<string | null> {
|
|
215
|
-
try {
|
|
216
|
-
await submitQuery(config, {
|
|
217
|
-
queryType: type,
|
|
218
|
-
message,
|
|
219
|
-
userName: props.user.name,
|
|
220
|
-
isPaid: props.user.isPaid,
|
|
221
|
-
revenuecatId: props.user.revenuecatId,
|
|
222
|
-
appVersion: props.user.appVersion,
|
|
223
|
-
language,
|
|
224
|
-
});
|
|
225
|
-
void reload();
|
|
226
|
-
return null;
|
|
227
|
-
} catch (e) {
|
|
228
|
-
return writeErrorMessage(e);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
async function handleSend(
|
|
233
|
-
threadId: string,
|
|
234
|
-
message: string,
|
|
235
|
-
): Promise<string | null> {
|
|
283
|
+
async function handleSend(message: string): Promise<string | null> {
|
|
236
284
|
try {
|
|
237
|
-
await
|
|
238
|
-
|
|
239
|
-
revenuecatId: props.user.revenuecatId,
|
|
285
|
+
await sendMessage(config, {
|
|
286
|
+
revenuecatId,
|
|
240
287
|
message,
|
|
241
288
|
language,
|
|
289
|
+
user: { name: userName, plan, appVersion, platform: storePlatform() },
|
|
242
290
|
});
|
|
243
291
|
await reload();
|
|
244
292
|
return null;
|
|
@@ -247,33 +295,20 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
247
295
|
}
|
|
248
296
|
}
|
|
249
297
|
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
298
|
+
const reviewLinks = storeLinks(store);
|
|
299
|
+
const reviewLabel =
|
|
300
|
+
reviewLinks === null
|
|
301
|
+
? null
|
|
302
|
+
: strings.t(Platform.OS === 'ios' ? 'review.appStore' : 'review.playStore');
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
error={errorKey === null ? null : strings.t(errorKey)}
|
|
264
|
-
onRefresh={refresh}
|
|
265
|
-
onCompose={(type) => setScreen({ name: 'compose', type })}
|
|
266
|
-
onOpenThread={(thread) =>
|
|
267
|
-
setScreen({ name: 'thread', threadId: thread.id })
|
|
268
|
-
}
|
|
269
|
-
onChangeLanguage={() => {
|
|
270
|
-
setDraftLanguage(language);
|
|
271
|
-
setLanguageError(null);
|
|
272
|
-
setScreen({ name: 'language' });
|
|
273
|
-
}}
|
|
274
|
-
onClose={closeSheet}
|
|
275
|
-
/>
|
|
276
|
-
);
|
|
304
|
+
/** Opens the store's review page and lets the team know it was tapped. */
|
|
305
|
+
function openReview(messageId: string) {
|
|
306
|
+
if (reviewLinks === null) return;
|
|
307
|
+
void openStoreReview(reviewLinks);
|
|
308
|
+
reviewOpened(config, { revenuecatId, messageId }).catch(() => {
|
|
309
|
+
// The receipt is for the dashboard; the user already has the store.
|
|
310
|
+
});
|
|
311
|
+
}
|
|
277
312
|
|
|
278
313
|
const languagePicker = (
|
|
279
314
|
<LanguageScreen
|
|
@@ -290,7 +325,7 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
290
325
|
: () => {
|
|
291
326
|
setDraftLanguage(null);
|
|
292
327
|
setLanguageError(null);
|
|
293
|
-
setScreen(
|
|
328
|
+
setScreen('chat');
|
|
294
329
|
}
|
|
295
330
|
}
|
|
296
331
|
onDismiss={mustChooseLanguage ? closeSheet : undefined}
|
|
@@ -314,33 +349,27 @@ export function SupportButton(props: SupportButtonProps) {
|
|
|
314
349
|
<BottomSheet visible={sheetOpen} onClose={closeSheet} theme={theme}>
|
|
315
350
|
{/* The one-time prompt outranks whatever screen the user is on: it
|
|
316
351
|
only ever fires before they have sent anything. */}
|
|
317
|
-
{mustChooseLanguage || screen
|
|
352
|
+
{mustChooseLanguage || screen === 'language' ? (
|
|
318
353
|
languagePicker
|
|
319
354
|
) : (
|
|
320
|
-
|
|
321
|
-
{
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
onSend={(message) => handleSend(activeThread.id, message)}
|
|
339
|
-
/>
|
|
340
|
-
) : (
|
|
341
|
-
home
|
|
342
|
-
))}
|
|
343
|
-
</>
|
|
355
|
+
<ChatScreen
|
|
356
|
+
theme={theme}
|
|
357
|
+
strings={strings}
|
|
358
|
+
language={language}
|
|
359
|
+
threads={threads}
|
|
360
|
+
loading={loading}
|
|
361
|
+
error={errorKey === null ? null : strings.t(errorKey)}
|
|
362
|
+
onRetry={refresh}
|
|
363
|
+
onSend={handleSend}
|
|
364
|
+
onChangeLanguage={() => {
|
|
365
|
+
setDraftLanguage(language);
|
|
366
|
+
setLanguageError(null);
|
|
367
|
+
setScreen('language');
|
|
368
|
+
}}
|
|
369
|
+
onClose={closeSheet}
|
|
370
|
+
reviewLabel={reviewLabel}
|
|
371
|
+
onOpenReview={openReview}
|
|
372
|
+
/>
|
|
344
373
|
)}
|
|
345
374
|
</BottomSheet>
|
|
346
375
|
</>
|
package/src/components/icons.tsx
CHANGED
|
@@ -33,60 +33,6 @@ export function ChatIcon({ color, size = 16 }: { color: string; size?: number })
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export function PlusCircleIcon({ color, size = 16 }: { color: string; size?: number }) {
|
|
37
|
-
const bar = size * 0.45;
|
|
38
|
-
return (
|
|
39
|
-
<View
|
|
40
|
-
style={{
|
|
41
|
-
width: size,
|
|
42
|
-
height: size,
|
|
43
|
-
borderWidth: STROKE,
|
|
44
|
-
borderColor: color,
|
|
45
|
-
borderRadius: size,
|
|
46
|
-
alignItems: 'center',
|
|
47
|
-
justifyContent: 'center',
|
|
48
|
-
}}
|
|
49
|
-
>
|
|
50
|
-
<View style={{ width: bar, height: STROKE, backgroundColor: color }} />
|
|
51
|
-
<View
|
|
52
|
-
style={{
|
|
53
|
-
width: STROKE,
|
|
54
|
-
height: bar,
|
|
55
|
-
backgroundColor: color,
|
|
56
|
-
position: 'absolute',
|
|
57
|
-
}}
|
|
58
|
-
/>
|
|
59
|
-
</View>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function AlertCircleIcon({ color, size = 16 }: { color: string; size?: number }) {
|
|
64
|
-
return (
|
|
65
|
-
<View
|
|
66
|
-
style={{
|
|
67
|
-
width: size,
|
|
68
|
-
height: size,
|
|
69
|
-
borderWidth: STROKE,
|
|
70
|
-
borderColor: color,
|
|
71
|
-
borderRadius: size,
|
|
72
|
-
alignItems: 'center',
|
|
73
|
-
paddingTop: size * 0.2,
|
|
74
|
-
}}
|
|
75
|
-
>
|
|
76
|
-
<View style={{ width: STROKE, height: size * 0.32, backgroundColor: color }} />
|
|
77
|
-
<View
|
|
78
|
-
style={{
|
|
79
|
-
width: STROKE + 0.5,
|
|
80
|
-
height: STROKE + 0.5,
|
|
81
|
-
borderRadius: 2,
|
|
82
|
-
backgroundColor: color,
|
|
83
|
-
marginTop: size * 0.09,
|
|
84
|
-
}}
|
|
85
|
-
/>
|
|
86
|
-
</View>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
36
|
/** Chevron built from two borders, rotated. direction: right | left | down. */
|
|
91
37
|
export function Chevron({
|
|
92
38
|
color,
|
|
@@ -223,61 +169,74 @@ export function ArrowUpIcon({ color, size = 16 }: { color: string; size?: number
|
|
|
223
169
|
}
|
|
224
170
|
|
|
225
171
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
172
|
+
* Geometry of the product mark on a 24-unit square: two overlapping speech
|
|
173
|
+
* bubbles, the front one cut free of the back by a gap. Kept to rounded
|
|
174
|
+
* rects and right-triangle tails so Views can draw it with no svg; the
|
|
175
|
+
* dashboard's LogoMark (apps/web/components/logo.tsx) draws the same numbers.
|
|
229
176
|
*/
|
|
230
|
-
|
|
177
|
+
const MARK = {
|
|
178
|
+
back: { x: 1, y: 2.5, w: 14.5, h: 11, r: 4.5 },
|
|
179
|
+
/** Right angle at top-left, apex straight down. */
|
|
180
|
+
backTail: { x: 3.5, y: 12.25, w: 4.25, h: 4.25 },
|
|
181
|
+
front: { x: 8.5, y: 8.75, w: 14.5, h: 10.5, r: 4.5 },
|
|
182
|
+
/** Right angle at top-right, apex straight down. */
|
|
183
|
+
frontTail: { x: 16.5, y: 18.25, w: 4.25, h: 4.25 },
|
|
184
|
+
gap: 2,
|
|
185
|
+
} as const;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The mark on the launcher button. Solid shapes read far better than 1.5px
|
|
189
|
+
* strokes at launcher size and on top of a color fill.
|
|
190
|
+
*/
|
|
191
|
+
export function ConversationMark({
|
|
231
192
|
color,
|
|
232
|
-
|
|
193
|
+
holeColor,
|
|
233
194
|
size = 24,
|
|
234
195
|
}: {
|
|
235
196
|
color: string;
|
|
236
|
-
/** The surface behind the mark — the
|
|
237
|
-
|
|
197
|
+
/** The surface behind the mark — the gap between the bubbles is cut in this color. */
|
|
198
|
+
holeColor: string;
|
|
238
199
|
size?: number;
|
|
239
200
|
}) {
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
201
|
+
const u = size / 24;
|
|
202
|
+
const rect = (
|
|
203
|
+
o: { x: number; y: number; w: number; h: number; r: number },
|
|
204
|
+
fill: string,
|
|
205
|
+
grow = 0,
|
|
206
|
+
): ViewStyle => ({
|
|
207
|
+
position: 'absolute',
|
|
208
|
+
left: (o.x - grow) * u,
|
|
209
|
+
top: (o.y - grow) * u,
|
|
210
|
+
width: (o.w + grow * 2) * u,
|
|
211
|
+
height: (o.h + grow * 2) * u,
|
|
212
|
+
borderRadius: (o.r + grow) * u,
|
|
213
|
+
backgroundColor: fill,
|
|
214
|
+
});
|
|
215
|
+
// A zero-size box whose top border is colored and one side border is
|
|
216
|
+
// transparent renders as a right triangle; which side picks the corner.
|
|
217
|
+
const tail = (
|
|
218
|
+
o: { x: number; y: number; w: number; h: number },
|
|
219
|
+
side: 'left' | 'right',
|
|
220
|
+
): ViewStyle => ({
|
|
221
|
+
position: 'absolute',
|
|
222
|
+
left: o.x * u,
|
|
223
|
+
top: o.y * u,
|
|
224
|
+
width: 0,
|
|
225
|
+
height: 0,
|
|
226
|
+
borderTopWidth: o.h * u,
|
|
227
|
+
borderTopColor: color,
|
|
228
|
+
...(side === 'left'
|
|
229
|
+
? { borderRightWidth: o.w * u, borderRightColor: 'transparent' }
|
|
230
|
+
: { borderLeftWidth: o.w * u, borderLeftColor: 'transparent' }),
|
|
231
|
+
});
|
|
232
|
+
|
|
247
233
|
return (
|
|
248
|
-
<View style={{ width: size, height: size
|
|
249
|
-
<View
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
backgroundColor: color,
|
|
255
|
-
flexDirection: 'row',
|
|
256
|
-
alignItems: 'center',
|
|
257
|
-
justifyContent: 'center',
|
|
258
|
-
gap: size * 0.115,
|
|
259
|
-
}}
|
|
260
|
-
>
|
|
261
|
-
<View style={dot} />
|
|
262
|
-
<View style={dot} />
|
|
263
|
-
<View style={dot} />
|
|
264
|
-
</View>
|
|
265
|
-
{/* Tail, drawn as a border triangle so it needs no svg. */}
|
|
266
|
-
<View
|
|
267
|
-
style={{
|
|
268
|
-
position: 'absolute',
|
|
269
|
-
left: size * 0.22,
|
|
270
|
-
top: bubbleHeight - 1,
|
|
271
|
-
width: 0,
|
|
272
|
-
height: 0,
|
|
273
|
-
borderLeftWidth: size * 0.09,
|
|
274
|
-
borderRightWidth: size * 0.16,
|
|
275
|
-
borderTopWidth: size * 0.2,
|
|
276
|
-
borderLeftColor: 'transparent',
|
|
277
|
-
borderRightColor: 'transparent',
|
|
278
|
-
borderTopColor: color,
|
|
279
|
-
}}
|
|
280
|
-
/>
|
|
234
|
+
<View style={{ width: size, height: size }}>
|
|
235
|
+
<View style={rect(MARK.back, color)} />
|
|
236
|
+
<View style={tail(MARK.backTail, 'left')} />
|
|
237
|
+
<View style={rect(MARK.front, holeColor, MARK.gap)} />
|
|
238
|
+
<View style={rect(MARK.front, color)} />
|
|
239
|
+
<View style={tail(MARK.frontTail, 'right')} />
|
|
281
240
|
</View>
|
|
282
241
|
);
|
|
283
242
|
}
|