@upstep/react-native 0.7.0 → 0.7.1
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 +5 -0
- package/README.md +4 -4
- package/package.json +3 -3
- package/src/FeedbackContext.tsx +1 -1
- package/src/FeedbackSheet.tsx +3 -2
- package/src/SupportView.tsx +259 -149
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
- Restore the original accent and use the configured accent for all chat actions.
|
|
6
|
+
- Compact text and action cards pinned near the composer, with two-step contact collection and expandable optional details.
|
|
7
|
+
|
|
3
8
|
## 0.7.0
|
|
4
9
|
|
|
5
10
|
- Native support chat in the existing feedback sheet, with signed customer identities and anonymous session persistence.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ The [Upstep](https://upstep.dev) feedback SDK for React Native: a launcher butto
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @upstep/react-native@^0.7.
|
|
8
|
+
npm install @upstep/react-native@^0.7.1 react-native-safe-area-context @react-native-async-storage/async-storage
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
`react-native-safe-area-context` is a required peer dependency (the sheet needs it for safe-area insets). Get an API key by creating a free project at [upstep.dev](https://upstep.dev).
|
|
@@ -56,7 +56,7 @@ If `expo-sensors` isn't installed, the hook is a no-op — safe to call uncondit
|
|
|
56
56
|
| `apiKey` | `string` | — | **Required.** Your project's API key. |
|
|
57
57
|
| `userId` | `string` | — | End-user id from your app, used to deduplicate votes per user. Call `identify(userId)` if it's only known after login. |
|
|
58
58
|
| `baseUrl` | `string` | `https://upstep.dev` | Only needed for self-hosted or staging setups. |
|
|
59
|
-
| `accentColor` | `string` | `#
|
|
59
|
+
| `accentColor` | `string` | `#D97757` | Any CSS color, used for the launcher button and highlights. |
|
|
60
60
|
| `theme` | `"light" \| "dark" \| "auto"` | `"auto"` | `"auto"` follows the device color scheme. |
|
|
61
61
|
|
|
62
62
|
## API
|
|
@@ -75,7 +75,7 @@ If `expo-sensors` isn't installed, the hook is a no-op — safe to call uncondit
|
|
|
75
75
|
|
|
76
76
|
## Native AI and team support
|
|
77
77
|
|
|
78
|
-
Version 0.7.
|
|
78
|
+
Version 0.7.1 adds a native Support tab to the existing feedback sheet. Enable Customer support for a paid project in UpStep, then use the existing provider and launcher. No WebView or separate chat package is needed. Set `chat={false}` on `FeedbackProvider` to hide support.
|
|
79
79
|
|
|
80
80
|
```tsx
|
|
81
81
|
<FeedbackProvider apiKey="YOUR_PROJECT_KEY" chat={true} supportIdentityToken={signedToken}>
|
|
@@ -86,4 +86,4 @@ Version 0.7.0 adds a native Support tab to the existing feedback sheet. Enable C
|
|
|
86
86
|
|
|
87
87
|
`supportIdentityToken` is optional: generate a short-lived project-scoped JWT on your authenticated backend using the signing secret from Support settings. Never put that secret in the app. Pass a new token on login and `undefined` on logout. `userId` is for feedback voting and does not authenticate private chat. Anonymous chat sessions persist using AsyncStorage.
|
|
88
88
|
|
|
89
|
-
Chat includes
|
|
89
|
+
Chat includes compact text, neutral message bubbles, quick replies, issue detail cards and resolution confirmations. Active actions stay near the composer, and contact details use two short steps. The team contact form appears after AI understands an unresolved issue, and requires name/email before handoff. Tickets include a reference and the business contact email.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstep/react-native",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Native UpStep feedback and AI support chat for React Native",
|
|
5
5
|
"homepage": "https://upstep.dev",
|
|
6
6
|
"repository": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@upstep/types": "0.7.
|
|
23
|
-
"@upstep/js": "0.7.
|
|
22
|
+
"@upstep/types": "0.7.1",
|
|
23
|
+
"@upstep/js": "0.7.1"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"@react-native-async-storage/async-storage": ">=1.18",
|
package/src/FeedbackContext.tsx
CHANGED
package/src/FeedbackSheet.tsx
CHANGED
|
@@ -38,7 +38,7 @@ interface Palette {
|
|
|
38
38
|
|
|
39
39
|
const LIGHT: Palette = {
|
|
40
40
|
bg: "#ffffff",
|
|
41
|
-
bgSoft: "#
|
|
41
|
+
bgSoft: "#f5f5f5",
|
|
42
42
|
text: "#1a1915",
|
|
43
43
|
textSoft: "#56544d",
|
|
44
44
|
textFaint: "#9b9890",
|
|
@@ -48,7 +48,7 @@ const LIGHT: Palette = {
|
|
|
48
48
|
|
|
49
49
|
const DARK: Palette = {
|
|
50
50
|
bg: "#000000",
|
|
51
|
-
bgSoft: "#
|
|
51
|
+
bgSoft: "#171717",
|
|
52
52
|
text: "#f5f4f0",
|
|
53
53
|
textSoft: "#b4b1a8",
|
|
54
54
|
textFaint: "#7d7a72",
|
|
@@ -209,6 +209,7 @@ export function FeedbackSheet() {
|
|
|
209
209
|
key={supportRevision}
|
|
210
210
|
client={client.support}
|
|
211
211
|
config={supportConfig}
|
|
212
|
+
accentColor={accentColor}
|
|
212
213
|
dark={isDark}
|
|
213
214
|
/>
|
|
214
215
|
)}
|
package/src/SupportView.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
TextInput,
|
|
10
10
|
TouchableOpacity,
|
|
11
11
|
View,
|
|
12
|
+
useWindowDimensions,
|
|
12
13
|
} from "react-native";
|
|
13
14
|
import { SupportClient, supportMessageId } from "@upstep/js/support";
|
|
14
15
|
import type {
|
|
@@ -20,11 +21,14 @@ export function SupportView({
|
|
|
20
21
|
client,
|
|
21
22
|
config,
|
|
22
23
|
dark = false,
|
|
24
|
+
accentColor = "#D97757",
|
|
23
25
|
}: {
|
|
24
26
|
client: SupportClient;
|
|
25
27
|
config: SupportConfiguration;
|
|
26
28
|
dark?: boolean;
|
|
29
|
+
accentColor?: string;
|
|
27
30
|
}) {
|
|
31
|
+
const { height } = useWindowDimensions();
|
|
28
32
|
const [chat, setChat] = useState<SupportConversation | null>(null);
|
|
29
33
|
const [text, setText] = useState("");
|
|
30
34
|
const [busy, setBusy] = useState(false);
|
|
@@ -34,10 +38,10 @@ export function SupportView({
|
|
|
34
38
|
const pending = useRef<{ text: string; id: string } | null>(null);
|
|
35
39
|
const colors = {
|
|
36
40
|
bg: dark ? "#000000" : "#ffffff",
|
|
37
|
-
soft: dark ? "#
|
|
38
|
-
text: dark ? "#
|
|
39
|
-
muted: dark ? "#
|
|
40
|
-
line: dark ? "#
|
|
41
|
+
soft: dark ? "#171717" : "#f5f5f5",
|
|
42
|
+
text: dark ? "#e5e5e5" : "#404040",
|
|
43
|
+
muted: dark ? "#a3a3a3" : "#737373",
|
|
44
|
+
line: dark ? "#404040" : "#e5e5e5",
|
|
41
45
|
};
|
|
42
46
|
async function refresh(id: string) {
|
|
43
47
|
const next = await client.conversation(id);
|
|
@@ -95,6 +99,21 @@ export function SupportView({
|
|
|
95
99
|
await refresh(c.id);
|
|
96
100
|
});
|
|
97
101
|
}
|
|
102
|
+
const widgets: SupportWidget[] = chat?.activeWidgetMessageId
|
|
103
|
+
? (
|
|
104
|
+
chat.messages.find((m) => m.id === chat.activeWidgetMessageId)?.metadata
|
|
105
|
+
?.widgets || []
|
|
106
|
+
).filter((w) => w.type !== "contact_form" || chat.canEscalate)
|
|
107
|
+
: chat?.canEscalate
|
|
108
|
+
? [
|
|
109
|
+
{
|
|
110
|
+
id: `contact:${chat.id}`,
|
|
111
|
+
type: "contact_form",
|
|
112
|
+
title: "Let the team follow up",
|
|
113
|
+
summary: chat.escalationSummary || "",
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
: [];
|
|
98
117
|
return (
|
|
99
118
|
<View style={[styles.root, { backgroundColor: colors.bg }]}>
|
|
100
119
|
<View style={[styles.heading, { borderColor: colors.line }]}>
|
|
@@ -203,45 +222,33 @@ export function SupportView({
|
|
|
203
222
|
</TouchableOpacity>
|
|
204
223
|
))}
|
|
205
224
|
</View>
|
|
206
|
-
{m.id === chat.activeWidgetMessageId &&
|
|
207
|
-
m.metadata?.widgets
|
|
208
|
-
?.filter((w) => w.type !== "contact_form" || chat.canEscalate)
|
|
209
|
-
.map((w) => (
|
|
210
|
-
<NativeSupportWidget
|
|
211
|
-
key={w.id}
|
|
212
|
-
widget={w}
|
|
213
|
-
busy={busy}
|
|
214
|
-
color={colors}
|
|
215
|
-
name={chat.customer.name || ""}
|
|
216
|
-
email={chat.customer.email || ""}
|
|
217
|
-
verified={chat.customer.verified}
|
|
218
|
-
onReply={send}
|
|
219
|
-
onContact={(name, email) =>
|
|
220
|
-
perform(async () => {
|
|
221
|
-
await client.handoff(chat.id, name, email);
|
|
222
|
-
await refresh(chat.id);
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
onDetails={(values) =>
|
|
226
|
-
perform(async () => {
|
|
227
|
-
await client.details(chat.id, values);
|
|
228
|
-
await refresh(chat.id);
|
|
229
|
-
})
|
|
230
|
-
}
|
|
231
|
-
/>
|
|
232
|
-
))}
|
|
233
225
|
</View>
|
|
234
226
|
))}
|
|
235
|
-
{chat?.
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
227
|
+
{chat?.aiPending && (
|
|
228
|
+
<View style={{ flexDirection: "row", gap: 10, alignItems: "center" }}>
|
|
229
|
+
<ActivityIndicator color={accentColor} />
|
|
230
|
+
<Text style={[styles.meta, { color: colors.muted }]}>
|
|
231
|
+
Thinking…
|
|
232
|
+
</Text>
|
|
233
|
+
</View>
|
|
234
|
+
)}
|
|
235
|
+
</ScrollView>
|
|
236
|
+
{chat && widgets.length > 0 && (
|
|
237
|
+
<ScrollView
|
|
238
|
+
keyboardShouldPersistTaps="handled"
|
|
239
|
+
style={{
|
|
240
|
+
maxHeight: height * 0.38,
|
|
241
|
+
flexGrow: 0,
|
|
242
|
+
marginHorizontal: 12,
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
<NativeSupportActions
|
|
246
|
+
key={chat.activeWidgetMessageId || chat.id}
|
|
247
|
+
widgets={widgets}
|
|
248
|
+
online={config.online.length > 0}
|
|
243
249
|
busy={busy}
|
|
244
250
|
color={colors}
|
|
251
|
+
accentColor={accentColor}
|
|
245
252
|
name={chat.customer.name || ""}
|
|
246
253
|
email={chat.customer.email || ""}
|
|
247
254
|
verified={chat.customer.verified}
|
|
@@ -259,16 +266,8 @@ export function SupportView({
|
|
|
259
266
|
})
|
|
260
267
|
}
|
|
261
268
|
/>
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
<View style={{ flexDirection: "row", gap: 10, alignItems: "center" }}>
|
|
265
|
-
<ActivityIndicator color="#7c9386" />
|
|
266
|
-
<Text style={[styles.meta, { color: colors.muted }]}>
|
|
267
|
-
Thinking…
|
|
268
|
-
</Text>
|
|
269
|
-
</View>
|
|
270
|
-
)}
|
|
271
|
-
</ScrollView>
|
|
269
|
+
</ScrollView>
|
|
270
|
+
)}
|
|
272
271
|
{error.length > 0 && (
|
|
273
272
|
<Text
|
|
274
273
|
accessibilityRole="alert"
|
|
@@ -283,7 +282,10 @@ export function SupportView({
|
|
|
283
282
|
</Text>
|
|
284
283
|
)}
|
|
285
284
|
{chat?.state === "RESOLVED" ? (
|
|
286
|
-
<TouchableOpacity
|
|
285
|
+
<TouchableOpacity
|
|
286
|
+
style={[styles.send, { backgroundColor: accentColor }]}
|
|
287
|
+
onPress={() => setChat(null)}
|
|
288
|
+
>
|
|
287
289
|
<Text style={styles.sendText}>Start a new conversation</Text>
|
|
288
290
|
</TouchableOpacity>
|
|
289
291
|
) : (
|
|
@@ -303,7 +305,13 @@ export function SupportView({
|
|
|
303
305
|
accessibilityLabel="Send message"
|
|
304
306
|
disabled={busy || !text.trim()}
|
|
305
307
|
onPress={() => void send()}
|
|
306
|
-
style={[
|
|
308
|
+
style={[
|
|
309
|
+
styles.send,
|
|
310
|
+
{
|
|
311
|
+
backgroundColor: accentColor,
|
|
312
|
+
opacity: busy || !text.trim() ? 0.45 : 1,
|
|
313
|
+
},
|
|
314
|
+
]}
|
|
307
315
|
>
|
|
308
316
|
<Text style={styles.sendText}>Send</Text>
|
|
309
317
|
</TouchableOpacity>
|
|
@@ -322,20 +330,11 @@ export function SupportView({
|
|
|
322
330
|
</View>
|
|
323
331
|
);
|
|
324
332
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
busy,
|
|
328
|
-
color,
|
|
329
|
-
name,
|
|
330
|
-
email,
|
|
331
|
-
verified,
|
|
332
|
-
onReply,
|
|
333
|
-
onContact,
|
|
334
|
-
onDetails,
|
|
335
|
-
}: {
|
|
336
|
-
widget: SupportWidget;
|
|
333
|
+
type ActionProps = {
|
|
334
|
+
online: boolean;
|
|
337
335
|
busy: boolean;
|
|
338
336
|
color: { soft: string; text: string; muted: string; line: string };
|
|
337
|
+
accentColor: string;
|
|
339
338
|
name: string;
|
|
340
339
|
email: string;
|
|
341
340
|
verified: boolean;
|
|
@@ -348,48 +347,113 @@ function NativeSupportWidget({
|
|
|
348
347
|
reference: string;
|
|
349
348
|
clientMessageId: string;
|
|
350
349
|
}) => Promise<boolean>;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const [
|
|
357
|
-
|
|
350
|
+
};
|
|
351
|
+
function NativeSupportActions({
|
|
352
|
+
widgets,
|
|
353
|
+
...props
|
|
354
|
+
}: ActionProps & { widgets: SupportWidget[] }) {
|
|
355
|
+
const [selected, setSelected] = useState(
|
|
356
|
+
widgets.find((w) => w.type === "quick_replies")?.id || widgets[0]?.id,
|
|
357
|
+
);
|
|
358
|
+
const widget = widgets.find((w) => w.id === selected) || widgets[0];
|
|
359
|
+
return (
|
|
360
|
+
<View style={{ gap: 8, paddingBottom: 4 }}>
|
|
361
|
+
{widgets.length > 1 && (
|
|
362
|
+
<View style={{ flexDirection: "row", gap: 8 }}>
|
|
363
|
+
{widgets.map((w) => (
|
|
364
|
+
<TouchableOpacity
|
|
365
|
+
key={w.id}
|
|
366
|
+
onPress={() => setSelected(w.id)}
|
|
367
|
+
style={[
|
|
368
|
+
styles.option,
|
|
369
|
+
{ borderColor: props.color.line, padding: 8 },
|
|
370
|
+
]}
|
|
371
|
+
>
|
|
372
|
+
<Text style={{ fontSize: 13, color: props.color.text }}>
|
|
373
|
+
{w.type === "quick_replies" ? "Quick replies" : "Issue details"}
|
|
374
|
+
</Text>
|
|
375
|
+
</TouchableOpacity>
|
|
376
|
+
))}
|
|
377
|
+
</View>
|
|
378
|
+
)}
|
|
379
|
+
{widget && (
|
|
380
|
+
<NativeSupportWidget key={widget.id} {...props} widget={widget} />
|
|
381
|
+
)}
|
|
382
|
+
</View>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
function NativeSupportWidget({
|
|
386
|
+
widget,
|
|
387
|
+
busy,
|
|
388
|
+
color,
|
|
389
|
+
accentColor,
|
|
390
|
+
name,
|
|
391
|
+
email,
|
|
392
|
+
verified,
|
|
393
|
+
online,
|
|
394
|
+
onReply,
|
|
395
|
+
onContact,
|
|
396
|
+
onDetails,
|
|
397
|
+
}: ActionProps & { widget: SupportWidget }) {
|
|
398
|
+
const [step, setStep] = useState(0),
|
|
399
|
+
[issueOpen, setIssueOpen] = useState(false),
|
|
400
|
+
[more, setMore] = useState(false);
|
|
401
|
+
const [fullName, setName] = useState(name),
|
|
402
|
+
[address, setEmail] = useState(email),
|
|
403
|
+
[summary, setSummary] = useState(""),
|
|
404
|
+
[details, setDetails] = useState(""),
|
|
405
|
+
[reference, setReference] = useState("");
|
|
358
406
|
const id = useRef(supportMessageId());
|
|
407
|
+
const action = (title: string, press: () => void, disabled = false) => (
|
|
408
|
+
<TouchableOpacity
|
|
409
|
+
disabled={disabled || busy}
|
|
410
|
+
style={[
|
|
411
|
+
styles.send,
|
|
412
|
+
{ backgroundColor: accentColor, opacity: disabled || busy ? 0.5 : 1 },
|
|
413
|
+
]}
|
|
414
|
+
onPress={press}
|
|
415
|
+
>
|
|
416
|
+
<Text style={styles.sendText}>{title}</Text>
|
|
417
|
+
</TouchableOpacity>
|
|
418
|
+
);
|
|
359
419
|
if (widget.type === "quick_replies" || widget.type === "confirmation")
|
|
360
420
|
return (
|
|
361
|
-
<View style={{ gap: 8
|
|
362
|
-
<Text style={[styles.
|
|
421
|
+
<View style={{ gap: 8 }}>
|
|
422
|
+
<Text style={[styles.meta, { color: color.muted }]}>
|
|
363
423
|
{widget.title}
|
|
364
424
|
</Text>
|
|
365
|
-
{
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
425
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
|
426
|
+
{widget.options.map((option) => (
|
|
427
|
+
<TouchableOpacity
|
|
428
|
+
key={option}
|
|
429
|
+
disabled={busy}
|
|
430
|
+
style={[styles.option, { borderColor: color.line }]}
|
|
431
|
+
onPress={() => void onReply(option)}
|
|
432
|
+
>
|
|
433
|
+
<Text style={{ fontSize: 13, color: color.text }}>{option}</Text>
|
|
434
|
+
</TouchableOpacity>
|
|
435
|
+
))}
|
|
436
|
+
</View>
|
|
375
437
|
</View>
|
|
376
438
|
);
|
|
377
439
|
const field = (
|
|
378
440
|
label: string,
|
|
379
441
|
value: string,
|
|
380
|
-
set: (
|
|
381
|
-
|
|
442
|
+
set: (v: string) => void,
|
|
443
|
+
emailField = false,
|
|
382
444
|
readOnly = false,
|
|
445
|
+
maxLength = 160,
|
|
383
446
|
) => (
|
|
384
|
-
<View style={{ gap:
|
|
447
|
+
<View style={{ gap: 4 }}>
|
|
385
448
|
<Text style={[styles.meta, { color: color.muted }]}>{label}</Text>
|
|
386
449
|
<TextInput
|
|
387
450
|
accessibilityLabel={label}
|
|
388
451
|
value={value}
|
|
389
452
|
onChangeText={set}
|
|
390
|
-
editable={!
|
|
391
|
-
|
|
392
|
-
|
|
453
|
+
editable={!busy && !readOnly}
|
|
454
|
+
maxLength={maxLength}
|
|
455
|
+
keyboardType={emailField ? "email-address" : "default"}
|
|
456
|
+
autoCapitalize={emailField ? "none" : "sentences"}
|
|
393
457
|
style={[
|
|
394
458
|
styles.formInput,
|
|
395
459
|
{ color: color.text, borderColor: color.line },
|
|
@@ -404,73 +468,119 @@ function NativeSupportWidget({
|
|
|
404
468
|
{ backgroundColor: color.soft, borderColor: color.line },
|
|
405
469
|
]}
|
|
406
470
|
>
|
|
407
|
-
<
|
|
471
|
+
<View
|
|
472
|
+
style={{
|
|
473
|
+
flexDirection: "row",
|
|
474
|
+
justifyContent: "space-between",
|
|
475
|
+
gap: 8,
|
|
476
|
+
}}
|
|
477
|
+
>
|
|
478
|
+
<Text style={[styles.label, { color: color.text }]}>
|
|
479
|
+
{widget.title}
|
|
480
|
+
</Text>
|
|
481
|
+
{widget.type === "contact_form" && step > 0 && (
|
|
482
|
+
<Text style={[styles.meta, { color: color.muted }]}>{step} of 2</Text>
|
|
483
|
+
)}
|
|
484
|
+
</View>
|
|
408
485
|
{widget.type === "contact_form" ? (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
486
|
+
step === 0 ? (
|
|
487
|
+
<>
|
|
488
|
+
<Text
|
|
489
|
+
numberOfLines={2}
|
|
490
|
+
style={[styles.meta, { color: color.muted }]}
|
|
491
|
+
>
|
|
492
|
+
{widget.summary}
|
|
493
|
+
</Text>
|
|
494
|
+
{action("Ask the team", () => setStep(1))}
|
|
495
|
+
</>
|
|
496
|
+
) : (
|
|
497
|
+
<>
|
|
498
|
+
{step === 1
|
|
499
|
+
? field("Your name", fullName, setName, false, false, 100)
|
|
500
|
+
: field(
|
|
501
|
+
"Email for support follow-up",
|
|
502
|
+
address,
|
|
503
|
+
setEmail,
|
|
504
|
+
true,
|
|
505
|
+
verified && !!email,
|
|
506
|
+
254,
|
|
507
|
+
)}
|
|
508
|
+
<View
|
|
509
|
+
style={{ flexDirection: "row", gap: 8, alignItems: "center" }}
|
|
418
510
|
>
|
|
419
|
-
<Text style={styles.sendText}>Ask the team</Text>
|
|
420
|
-
</TouchableOpacity>
|
|
421
|
-
) : (
|
|
422
|
-
<>
|
|
423
|
-
{field("Your name", fullName, setName)}
|
|
424
|
-
{field(
|
|
425
|
-
"Email address",
|
|
426
|
-
address,
|
|
427
|
-
setEmail,
|
|
428
|
-
"email-address",
|
|
429
|
-
verified && !!email,
|
|
430
|
-
)}
|
|
431
|
-
<Text style={[styles.meta, { color: color.muted }]}>
|
|
432
|
-
The team will use these details to follow up.
|
|
433
|
-
</Text>
|
|
434
511
|
<TouchableOpacity
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
opacity:
|
|
440
|
-
busy || !fullName.trim() || !address.trim() ? 0.5 : 1,
|
|
441
|
-
},
|
|
442
|
-
]}
|
|
443
|
-
onPress={() => void onContact(fullName, address)}
|
|
512
|
+
style={styles.option}
|
|
513
|
+
disabled={busy}
|
|
514
|
+
onPress={() => setStep(step - 1)}
|
|
444
515
|
>
|
|
445
|
-
<Text style={
|
|
516
|
+
<Text style={{ color: color.text, fontSize: 13 }}>Back</Text>
|
|
446
517
|
</TouchableOpacity>
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
518
|
+
<View style={{ flex: 1 }}>
|
|
519
|
+
{action(
|
|
520
|
+
step === 1
|
|
521
|
+
? "Continue"
|
|
522
|
+
: online
|
|
523
|
+
? "Request transfer"
|
|
524
|
+
: "Send to the team",
|
|
525
|
+
() => {
|
|
526
|
+
if (step === 1) setStep(2);
|
|
527
|
+
else void onContact(fullName, address);
|
|
528
|
+
},
|
|
529
|
+
!fullName.trim() || (step === 2 && !address.trim()),
|
|
530
|
+
)}
|
|
531
|
+
</View>
|
|
532
|
+
</View>
|
|
533
|
+
</>
|
|
534
|
+
)
|
|
535
|
+
) : !issueOpen ? (
|
|
536
|
+
action("Add issue details", () => setIssueOpen(true))
|
|
450
537
|
) : (
|
|
451
538
|
<>
|
|
452
539
|
{field("What is happening?", summary, setSummary)}
|
|
453
|
-
{
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
540
|
+
<TouchableOpacity onPress={() => setMore(!more)}>
|
|
541
|
+
<Text
|
|
542
|
+
style={[
|
|
543
|
+
styles.meta,
|
|
544
|
+
{ color: color.muted, textDecorationLine: "underline" },
|
|
545
|
+
]}
|
|
546
|
+
>
|
|
547
|
+
{more
|
|
548
|
+
? "Hide optional details"
|
|
549
|
+
: "Add notes or a reference (optional)"}
|
|
550
|
+
</Text>
|
|
551
|
+
</TouchableOpacity>
|
|
552
|
+
{more && (
|
|
553
|
+
<>
|
|
554
|
+
{field(
|
|
555
|
+
"What have you tried?",
|
|
556
|
+
details,
|
|
557
|
+
setDetails,
|
|
558
|
+
false,
|
|
559
|
+
false,
|
|
560
|
+
2000,
|
|
561
|
+
)}
|
|
562
|
+
{field(
|
|
563
|
+
"Order or issue reference",
|
|
564
|
+
reference,
|
|
565
|
+
setReference,
|
|
566
|
+
false,
|
|
567
|
+
false,
|
|
568
|
+
100,
|
|
569
|
+
)}
|
|
570
|
+
</>
|
|
458
571
|
)}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
onPress={() =>
|
|
572
|
+
{action(
|
|
573
|
+
"Send details",
|
|
574
|
+
() =>
|
|
463
575
|
void onDetails({
|
|
464
576
|
widgetId: widget.id,
|
|
465
577
|
summary,
|
|
466
578
|
details,
|
|
467
579
|
reference,
|
|
468
580
|
clientMessageId: id.current,
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
<Text style={styles.sendText}>Send details</Text>
|
|
473
|
-
</TouchableOpacity>
|
|
581
|
+
}),
|
|
582
|
+
!summary.trim(),
|
|
583
|
+
)}
|
|
474
584
|
</>
|
|
475
585
|
)}
|
|
476
586
|
</View>
|
|
@@ -481,10 +591,10 @@ const styles = StyleSheet.create({
|
|
|
481
591
|
heading: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth },
|
|
482
592
|
title: { fontSize: 18, fontWeight: "600" },
|
|
483
593
|
hero: { fontSize: 28, fontWeight: "600" },
|
|
484
|
-
body: { fontSize:
|
|
485
|
-
meta: { fontSize:
|
|
594
|
+
body: { fontSize: 15, lineHeight: 23 },
|
|
595
|
+
meta: { fontSize: 13, lineHeight: 19 },
|
|
486
596
|
label: { fontSize: 15, fontWeight: "600" },
|
|
487
|
-
bubble: { padding:
|
|
597
|
+
bubble: { padding: 12, borderRadius: 16 },
|
|
488
598
|
ticket: { padding: 12, gap: 4 },
|
|
489
599
|
composer: {
|
|
490
600
|
margin: 12,
|
|
@@ -505,21 +615,21 @@ const styles = StyleSheet.create({
|
|
|
505
615
|
},
|
|
506
616
|
send: {
|
|
507
617
|
minHeight: 44,
|
|
508
|
-
|
|
618
|
+
|
|
509
619
|
borderRadius: 12,
|
|
510
620
|
paddingHorizontal: 16,
|
|
511
|
-
paddingVertical:
|
|
621
|
+
paddingVertical: 10,
|
|
512
622
|
alignItems: "center",
|
|
513
623
|
justifyContent: "center",
|
|
514
624
|
},
|
|
515
|
-
sendText: { fontSize:
|
|
625
|
+
sendText: { fontSize: 14, fontWeight: "600", color: "white" },
|
|
516
626
|
option: { minHeight: 44, borderWidth: 1, borderRadius: 12, padding: 12 },
|
|
517
627
|
form: {
|
|
518
|
-
marginTop:
|
|
628
|
+
marginTop: 0,
|
|
519
629
|
borderWidth: 1,
|
|
520
630
|
borderRadius: 16,
|
|
521
|
-
padding:
|
|
522
|
-
gap:
|
|
631
|
+
padding: 12,
|
|
632
|
+
gap: 8,
|
|
523
633
|
},
|
|
524
634
|
formInput: {
|
|
525
635
|
borderWidth: 1,
|