@upstep/react-native 0.6.15 → 0.7.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 +10 -0
- package/README.md +17 -2
- package/package.json +8 -3
- package/src/FeedbackContext.tsx +83 -33
- package/src/FeedbackSheet.tsx +457 -125
- package/src/SupportView.tsx +531 -0
- package/src/api.ts +3 -0
- package/src/index.ts +17 -1
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
AppState,
|
|
5
|
+
Linking,
|
|
6
|
+
ScrollView,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
Text,
|
|
9
|
+
TextInput,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
View,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { SupportClient, supportMessageId } from "@upstep/js/support";
|
|
14
|
+
import type {
|
|
15
|
+
SupportConfiguration,
|
|
16
|
+
SupportConversation,
|
|
17
|
+
SupportWidget,
|
|
18
|
+
} from "@upstep/types";
|
|
19
|
+
export function SupportView({
|
|
20
|
+
client,
|
|
21
|
+
config,
|
|
22
|
+
dark = false,
|
|
23
|
+
}: {
|
|
24
|
+
client: SupportClient;
|
|
25
|
+
config: SupportConfiguration;
|
|
26
|
+
dark?: boolean;
|
|
27
|
+
}) {
|
|
28
|
+
const [chat, setChat] = useState<SupportConversation | null>(null);
|
|
29
|
+
const [text, setText] = useState("");
|
|
30
|
+
const [busy, setBusy] = useState(false);
|
|
31
|
+
const [error, setError] = useState("");
|
|
32
|
+
const list = useRef<React.ElementRef<typeof ScrollView>>(null);
|
|
33
|
+
const active = useRef(true);
|
|
34
|
+
const pending = useRef<{ text: string; id: string } | null>(null);
|
|
35
|
+
const colors = {
|
|
36
|
+
bg: dark ? "#000000" : "#ffffff",
|
|
37
|
+
soft: dark ? "#191d1b" : "#f4f6f4",
|
|
38
|
+
text: dark ? "#e5e7e5" : "#37413c",
|
|
39
|
+
muted: dark ? "#a2aaa5" : "#69756d",
|
|
40
|
+
line: dark ? "#303833" : "#dde4de",
|
|
41
|
+
};
|
|
42
|
+
async function refresh(id: string) {
|
|
43
|
+
const next = await client.conversation(id);
|
|
44
|
+
if (active.current) setChat(next);
|
|
45
|
+
}
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
active.current = true;
|
|
48
|
+
void client
|
|
49
|
+
.conversations()
|
|
50
|
+
.then((items) => {
|
|
51
|
+
if (items[0]) return refresh(items[0].id);
|
|
52
|
+
})
|
|
53
|
+
.catch((e) => setError(e.message));
|
|
54
|
+
return () => {
|
|
55
|
+
active.current = false;
|
|
56
|
+
};
|
|
57
|
+
}, [client]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!chat) return;
|
|
60
|
+
const id = chat.id;
|
|
61
|
+
const timer = setInterval(() => {
|
|
62
|
+
if (AppState.currentState === "active")
|
|
63
|
+
void refresh(id).catch((e) => {
|
|
64
|
+
if (active.current) setError(e.message);
|
|
65
|
+
});
|
|
66
|
+
}, 2500);
|
|
67
|
+
return () => clearInterval(timer);
|
|
68
|
+
}, [chat?.id, client]);
|
|
69
|
+
async function perform(fn: () => Promise<void>) {
|
|
70
|
+
if (busy) return false;
|
|
71
|
+
setBusy(true);
|
|
72
|
+
setError("");
|
|
73
|
+
try {
|
|
74
|
+
await fn();
|
|
75
|
+
return true;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
setError((e as Error).message);
|
|
78
|
+
return false;
|
|
79
|
+
} finally {
|
|
80
|
+
if (active.current) setBusy(false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function send(value = text) {
|
|
84
|
+
return perform(async () => {
|
|
85
|
+
if (!value.trim()) return;
|
|
86
|
+
const c =
|
|
87
|
+
!chat || chat.state === "RESOLVED"
|
|
88
|
+
? await client.startConversation()
|
|
89
|
+
: chat;
|
|
90
|
+
if (!pending.current || pending.current.text !== value)
|
|
91
|
+
pending.current = { text: value, id: supportMessageId() };
|
|
92
|
+
await client.send(c.id, value, pending.current.id);
|
|
93
|
+
pending.current = null;
|
|
94
|
+
if (active.current) setText("");
|
|
95
|
+
await refresh(c.id);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return (
|
|
99
|
+
<View style={[styles.root, { backgroundColor: colors.bg }]}>
|
|
100
|
+
<View style={[styles.heading, { borderColor: colors.line }]}>
|
|
101
|
+
<Text style={[styles.title, { color: colors.text }]}>
|
|
102
|
+
{config.businessName}
|
|
103
|
+
</Text>
|
|
104
|
+
<Text style={[styles.meta, { color: colors.muted }]}>
|
|
105
|
+
{chat?.state === "HUMAN"
|
|
106
|
+
? "The team is here to help"
|
|
107
|
+
: "AI support for your questions"}
|
|
108
|
+
</Text>
|
|
109
|
+
</View>
|
|
110
|
+
{chat?.ticketReference && (
|
|
111
|
+
<View style={[styles.ticket, { backgroundColor: colors.soft }]}>
|
|
112
|
+
<Text selectable style={[styles.label, { color: colors.text }]}>
|
|
113
|
+
Ticket {chat.ticketReference}
|
|
114
|
+
</Text>
|
|
115
|
+
{chat.supportEmail && (
|
|
116
|
+
<TouchableOpacity
|
|
117
|
+
onPress={() =>
|
|
118
|
+
void Linking.openURL(
|
|
119
|
+
`mailto:${chat.supportEmail}?subject=${encodeURIComponent(chat.ticketReference || "")}`,
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
<Text style={[styles.meta, { color: colors.muted }]}>
|
|
124
|
+
{chat.supportEmail}
|
|
125
|
+
</Text>
|
|
126
|
+
</TouchableOpacity>
|
|
127
|
+
)}
|
|
128
|
+
</View>
|
|
129
|
+
)}
|
|
130
|
+
<ScrollView
|
|
131
|
+
ref={list}
|
|
132
|
+
style={{ flex: 1 }}
|
|
133
|
+
contentContainerStyle={{ padding: 16, gap: 16 }}
|
|
134
|
+
keyboardShouldPersistTaps="handled"
|
|
135
|
+
onContentSizeChange={() => {
|
|
136
|
+
if (chat) list.current?.scrollToEnd({ animated: true });
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
{!chat && (
|
|
140
|
+
<View style={{ gap: 16, paddingVertical: 12 }}>
|
|
141
|
+
<Text style={[styles.hero, { color: colors.text }]}>
|
|
142
|
+
How can we help?
|
|
143
|
+
</Text>
|
|
144
|
+
<Text style={[styles.body, { color: colors.muted }]}>
|
|
145
|
+
{config.welcomeMessage}
|
|
146
|
+
</Text>
|
|
147
|
+
{[
|
|
148
|
+
"What can you help me with?",
|
|
149
|
+
`I need help using ${config.businessName}`,
|
|
150
|
+
].map((q) => (
|
|
151
|
+
<TouchableOpacity
|
|
152
|
+
key={q}
|
|
153
|
+
style={[styles.option, { borderColor: colors.line }]}
|
|
154
|
+
onPress={() => setText(q)}
|
|
155
|
+
>
|
|
156
|
+
<Text style={[styles.body, { color: colors.text }]}>{q}</Text>
|
|
157
|
+
</TouchableOpacity>
|
|
158
|
+
))}
|
|
159
|
+
</View>
|
|
160
|
+
)}
|
|
161
|
+
{chat?.messages.map((m) => (
|
|
162
|
+
<View
|
|
163
|
+
key={m.id}
|
|
164
|
+
style={{
|
|
165
|
+
alignSelf: m.role === "CUSTOMER" ? "flex-end" : "stretch",
|
|
166
|
+
maxWidth: m.role === "CUSTOMER" ? "94%" : "100%",
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<Text
|
|
170
|
+
style={[styles.meta, { color: colors.muted, marginBottom: 6 }]}
|
|
171
|
+
>
|
|
172
|
+
{m.role === "CUSTOMER" ? "You" : m.authorName}
|
|
173
|
+
</Text>
|
|
174
|
+
<View
|
|
175
|
+
style={[
|
|
176
|
+
styles.bubble,
|
|
177
|
+
{
|
|
178
|
+
backgroundColor:
|
|
179
|
+
m.role === "SYSTEM" ? "transparent" : colors.soft,
|
|
180
|
+
},
|
|
181
|
+
]}
|
|
182
|
+
>
|
|
183
|
+
{m.content.split(/\n\s*\n/).map((paragraph, i) => (
|
|
184
|
+
<Text
|
|
185
|
+
key={i}
|
|
186
|
+
selectable
|
|
187
|
+
style={[styles.body, { color: colors.text, marginBottom: 8 }]}
|
|
188
|
+
>
|
|
189
|
+
{paragraph}
|
|
190
|
+
</Text>
|
|
191
|
+
))}
|
|
192
|
+
{m.metadata?.citations?.map((c) => (
|
|
193
|
+
<TouchableOpacity
|
|
194
|
+
key={c.url}
|
|
195
|
+
onPress={() => void Linking.openURL(c.url)}
|
|
196
|
+
>
|
|
197
|
+
<Text
|
|
198
|
+
numberOfLines={1}
|
|
199
|
+
style={[styles.meta, { color: colors.muted, marginTop: 8 }]}
|
|
200
|
+
>
|
|
201
|
+
↗ {c.title}
|
|
202
|
+
</Text>
|
|
203
|
+
</TouchableOpacity>
|
|
204
|
+
))}
|
|
205
|
+
</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
|
+
</View>
|
|
234
|
+
))}
|
|
235
|
+
{chat?.canEscalate && !chat.activeWidgetMessageId && (
|
|
236
|
+
<NativeSupportWidget
|
|
237
|
+
widget={{
|
|
238
|
+
id: `contact:${chat.id}`,
|
|
239
|
+
type: "contact_form",
|
|
240
|
+
title: "Let the team follow up",
|
|
241
|
+
summary: chat.escalationSummary || "",
|
|
242
|
+
}}
|
|
243
|
+
busy={busy}
|
|
244
|
+
color={colors}
|
|
245
|
+
name={chat.customer.name || ""}
|
|
246
|
+
email={chat.customer.email || ""}
|
|
247
|
+
verified={chat.customer.verified}
|
|
248
|
+
onReply={send}
|
|
249
|
+
onContact={(name, email) =>
|
|
250
|
+
perform(async () => {
|
|
251
|
+
await client.handoff(chat.id, name, email);
|
|
252
|
+
await refresh(chat.id);
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
onDetails={(values) =>
|
|
256
|
+
perform(async () => {
|
|
257
|
+
await client.details(chat.id, values);
|
|
258
|
+
await refresh(chat.id);
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
/>
|
|
262
|
+
)}
|
|
263
|
+
{chat?.aiPending && (
|
|
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>
|
|
272
|
+
{error.length > 0 && (
|
|
273
|
+
<Text
|
|
274
|
+
accessibilityRole="alert"
|
|
275
|
+
style={{
|
|
276
|
+
color: dark ? "#e8aaa4" : "#a44e45",
|
|
277
|
+
paddingHorizontal: 16,
|
|
278
|
+
paddingBottom: 8,
|
|
279
|
+
fontSize: 14,
|
|
280
|
+
}}
|
|
281
|
+
>
|
|
282
|
+
{error}
|
|
283
|
+
</Text>
|
|
284
|
+
)}
|
|
285
|
+
{chat?.state === "RESOLVED" ? (
|
|
286
|
+
<TouchableOpacity style={styles.send} onPress={() => setChat(null)}>
|
|
287
|
+
<Text style={styles.sendText}>Start a new conversation</Text>
|
|
288
|
+
</TouchableOpacity>
|
|
289
|
+
) : (
|
|
290
|
+
<View style={[styles.composer, { borderColor: colors.line }]}>
|
|
291
|
+
<TextInput
|
|
292
|
+
accessibilityLabel="Your message"
|
|
293
|
+
multiline
|
|
294
|
+
value={text}
|
|
295
|
+
onChangeText={setText}
|
|
296
|
+
placeholder="Type your message…"
|
|
297
|
+
placeholderTextColor={colors.muted}
|
|
298
|
+
maxLength={4000}
|
|
299
|
+
style={[styles.input, { color: colors.text }]}
|
|
300
|
+
/>
|
|
301
|
+
<TouchableOpacity
|
|
302
|
+
accessibilityRole="button"
|
|
303
|
+
accessibilityLabel="Send message"
|
|
304
|
+
disabled={busy || !text.trim()}
|
|
305
|
+
onPress={() => void send()}
|
|
306
|
+
style={[styles.send, { opacity: busy || !text.trim() ? 0.45 : 1 }]}
|
|
307
|
+
>
|
|
308
|
+
<Text style={styles.sendText}>Send</Text>
|
|
309
|
+
</TouchableOpacity>
|
|
310
|
+
</View>
|
|
311
|
+
)}
|
|
312
|
+
{config.showBranding !== false && (
|
|
313
|
+
<Text
|
|
314
|
+
style={[
|
|
315
|
+
styles.meta,
|
|
316
|
+
{ color: colors.muted, textAlign: "center", padding: 10 },
|
|
317
|
+
]}
|
|
318
|
+
>
|
|
319
|
+
Powered by UpStep
|
|
320
|
+
</Text>
|
|
321
|
+
)}
|
|
322
|
+
</View>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
function NativeSupportWidget({
|
|
326
|
+
widget,
|
|
327
|
+
busy,
|
|
328
|
+
color,
|
|
329
|
+
name,
|
|
330
|
+
email,
|
|
331
|
+
verified,
|
|
332
|
+
onReply,
|
|
333
|
+
onContact,
|
|
334
|
+
onDetails,
|
|
335
|
+
}: {
|
|
336
|
+
widget: SupportWidget;
|
|
337
|
+
busy: boolean;
|
|
338
|
+
color: { soft: string; text: string; muted: string; line: string };
|
|
339
|
+
name: string;
|
|
340
|
+
email: string;
|
|
341
|
+
verified: boolean;
|
|
342
|
+
onReply: (text: string) => Promise<boolean>;
|
|
343
|
+
onContact: (name: string, email: string) => Promise<boolean>;
|
|
344
|
+
onDetails: (data: {
|
|
345
|
+
widgetId: string;
|
|
346
|
+
summary: string;
|
|
347
|
+
details: string;
|
|
348
|
+
reference: string;
|
|
349
|
+
clientMessageId: string;
|
|
350
|
+
}) => Promise<boolean>;
|
|
351
|
+
}) {
|
|
352
|
+
const [open, setOpen] = useState(false);
|
|
353
|
+
const [fullName, setName] = useState(name);
|
|
354
|
+
const [address, setEmail] = useState(email);
|
|
355
|
+
const [summary, setSummary] = useState("");
|
|
356
|
+
const [details, setDetails] = useState("");
|
|
357
|
+
const [reference, setReference] = useState("");
|
|
358
|
+
const id = useRef(supportMessageId());
|
|
359
|
+
if (widget.type === "quick_replies" || widget.type === "confirmation")
|
|
360
|
+
return (
|
|
361
|
+
<View style={{ gap: 8, marginTop: 12 }}>
|
|
362
|
+
<Text style={[styles.label, { color: color.text }]}>
|
|
363
|
+
{widget.title}
|
|
364
|
+
</Text>
|
|
365
|
+
{widget.options.map((option) => (
|
|
366
|
+
<TouchableOpacity
|
|
367
|
+
key={option}
|
|
368
|
+
disabled={busy}
|
|
369
|
+
style={[styles.option, { borderColor: color.line }]}
|
|
370
|
+
onPress={() => void onReply(option)}
|
|
371
|
+
>
|
|
372
|
+
<Text style={[styles.body, { color: color.text }]}>{option}</Text>
|
|
373
|
+
</TouchableOpacity>
|
|
374
|
+
))}
|
|
375
|
+
</View>
|
|
376
|
+
);
|
|
377
|
+
const field = (
|
|
378
|
+
label: string,
|
|
379
|
+
value: string,
|
|
380
|
+
set: (s: string) => void,
|
|
381
|
+
kind: "email-address" | "default" = "default",
|
|
382
|
+
readOnly = false,
|
|
383
|
+
) => (
|
|
384
|
+
<View style={{ gap: 6 }}>
|
|
385
|
+
<Text style={[styles.meta, { color: color.muted }]}>{label}</Text>
|
|
386
|
+
<TextInput
|
|
387
|
+
accessibilityLabel={label}
|
|
388
|
+
value={value}
|
|
389
|
+
onChangeText={set}
|
|
390
|
+
editable={!readOnly && !busy}
|
|
391
|
+
keyboardType={kind}
|
|
392
|
+
autoCapitalize={kind === "email-address" ? "none" : "sentences"}
|
|
393
|
+
style={[
|
|
394
|
+
styles.formInput,
|
|
395
|
+
{ color: color.text, borderColor: color.line },
|
|
396
|
+
]}
|
|
397
|
+
/>
|
|
398
|
+
</View>
|
|
399
|
+
);
|
|
400
|
+
return (
|
|
401
|
+
<View
|
|
402
|
+
style={[
|
|
403
|
+
styles.form,
|
|
404
|
+
{ backgroundColor: color.soft, borderColor: color.line },
|
|
405
|
+
]}
|
|
406
|
+
>
|
|
407
|
+
<Text style={[styles.title, { color: color.text }]}>{widget.title}</Text>
|
|
408
|
+
{widget.type === "contact_form" ? (
|
|
409
|
+
<>
|
|
410
|
+
<Text style={[styles.body, { color: color.muted }]}>
|
|
411
|
+
{widget.summary}
|
|
412
|
+
</Text>
|
|
413
|
+
{!open ? (
|
|
414
|
+
<TouchableOpacity
|
|
415
|
+
disabled={busy}
|
|
416
|
+
style={styles.send}
|
|
417
|
+
onPress={() => setOpen(true)}
|
|
418
|
+
>
|
|
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
|
+
<TouchableOpacity
|
|
435
|
+
disabled={busy || !fullName.trim() || !address.trim()}
|
|
436
|
+
style={[
|
|
437
|
+
styles.send,
|
|
438
|
+
{
|
|
439
|
+
opacity:
|
|
440
|
+
busy || !fullName.trim() || !address.trim() ? 0.5 : 1,
|
|
441
|
+
},
|
|
442
|
+
]}
|
|
443
|
+
onPress={() => void onContact(fullName, address)}
|
|
444
|
+
>
|
|
445
|
+
<Text style={styles.sendText}>Send to the team</Text>
|
|
446
|
+
</TouchableOpacity>
|
|
447
|
+
</>
|
|
448
|
+
)}
|
|
449
|
+
</>
|
|
450
|
+
) : (
|
|
451
|
+
<>
|
|
452
|
+
{field("What is happening?", summary, setSummary)}
|
|
453
|
+
{field("What have you tried? (optional)", details, setDetails)}
|
|
454
|
+
{field(
|
|
455
|
+
"Order or issue reference (optional)",
|
|
456
|
+
reference,
|
|
457
|
+
setReference,
|
|
458
|
+
)}
|
|
459
|
+
<TouchableOpacity
|
|
460
|
+
disabled={busy || !summary.trim()}
|
|
461
|
+
style={styles.send}
|
|
462
|
+
onPress={() =>
|
|
463
|
+
void onDetails({
|
|
464
|
+
widgetId: widget.id,
|
|
465
|
+
summary,
|
|
466
|
+
details,
|
|
467
|
+
reference,
|
|
468
|
+
clientMessageId: id.current,
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
>
|
|
472
|
+
<Text style={styles.sendText}>Send details</Text>
|
|
473
|
+
</TouchableOpacity>
|
|
474
|
+
</>
|
|
475
|
+
)}
|
|
476
|
+
</View>
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const styles = StyleSheet.create({
|
|
480
|
+
root: { flex: 1 },
|
|
481
|
+
heading: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth },
|
|
482
|
+
title: { fontSize: 18, fontWeight: "600" },
|
|
483
|
+
hero: { fontSize: 28, fontWeight: "600" },
|
|
484
|
+
body: { fontSize: 16, lineHeight: 25 },
|
|
485
|
+
meta: { fontSize: 14, lineHeight: 21 },
|
|
486
|
+
label: { fontSize: 15, fontWeight: "600" },
|
|
487
|
+
bubble: { padding: 14, borderRadius: 16 },
|
|
488
|
+
ticket: { padding: 12, gap: 4 },
|
|
489
|
+
composer: {
|
|
490
|
+
margin: 12,
|
|
491
|
+
borderWidth: 1,
|
|
492
|
+
borderRadius: 16,
|
|
493
|
+
padding: 10,
|
|
494
|
+
flexDirection: "row",
|
|
495
|
+
alignItems: "flex-end",
|
|
496
|
+
gap: 8,
|
|
497
|
+
},
|
|
498
|
+
input: {
|
|
499
|
+
flex: 1,
|
|
500
|
+
fontSize: 16,
|
|
501
|
+
lineHeight: 24,
|
|
502
|
+
minHeight: 44,
|
|
503
|
+
maxHeight: 120,
|
|
504
|
+
padding: 8,
|
|
505
|
+
},
|
|
506
|
+
send: {
|
|
507
|
+
minHeight: 44,
|
|
508
|
+
backgroundColor: "#5b7168",
|
|
509
|
+
borderRadius: 12,
|
|
510
|
+
paddingHorizontal: 16,
|
|
511
|
+
paddingVertical: 12,
|
|
512
|
+
alignItems: "center",
|
|
513
|
+
justifyContent: "center",
|
|
514
|
+
},
|
|
515
|
+
sendText: { fontSize: 16, fontWeight: "600", color: "white" },
|
|
516
|
+
option: { minHeight: 44, borderWidth: 1, borderRadius: 12, padding: 12 },
|
|
517
|
+
form: {
|
|
518
|
+
marginTop: 12,
|
|
519
|
+
borderWidth: 1,
|
|
520
|
+
borderRadius: 16,
|
|
521
|
+
padding: 14,
|
|
522
|
+
gap: 12,
|
|
523
|
+
},
|
|
524
|
+
formInput: {
|
|
525
|
+
borderWidth: 1,
|
|
526
|
+
borderRadius: 10,
|
|
527
|
+
padding: 12,
|
|
528
|
+
minHeight: 48,
|
|
529
|
+
fontSize: 16,
|
|
530
|
+
},
|
|
531
|
+
});
|
package/src/api.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {SupportClient} from "@upstep/js/support";
|
|
1
2
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
3
|
import type {
|
|
3
4
|
Feedback,
|
|
@@ -18,6 +19,7 @@ export class UpstepApiClient {
|
|
|
18
19
|
private readonly baseUrl: string;
|
|
19
20
|
private readonly apiKey: string;
|
|
20
21
|
userId: string | undefined;
|
|
22
|
+
readonly support:SupportClient;
|
|
21
23
|
// Set synchronously to a throwaway id so there's always something to send;
|
|
22
24
|
// swapped for the persisted value (or backfilled into storage) once
|
|
23
25
|
// AsyncStorage resolves. Callers await anonymousIdReady first so requests
|
|
@@ -28,6 +30,7 @@ export class UpstepApiClient {
|
|
|
28
30
|
constructor(config: UpstepConfig) {
|
|
29
31
|
this.baseUrl = (config.baseUrl ?? "https://upstep.dev").replace(/\/$/, "");
|
|
30
32
|
this.apiKey = config.apiKey;
|
|
33
|
+
this.support = new SupportClient(config,AsyncStorage);
|
|
31
34
|
if (config.userId !== undefined) this.userId = config.userId;
|
|
32
35
|
this.anonymousId = generateId();
|
|
33
36
|
this.anonymousIdReady = this.loadAnonymousId();
|
package/src/index.ts
CHANGED
|
@@ -2,4 +2,20 @@ export { FeedbackProvider, useUpstep } from "./FeedbackContext";
|
|
|
2
2
|
export { FeedbackSheet } from "./FeedbackSheet";
|
|
3
3
|
export { FeedbackButton } from "./FeedbackButton";
|
|
4
4
|
export { useShakeToFeedback } from "./useShakeToFeedback";
|
|
5
|
-
export type {
|
|
5
|
+
export type {
|
|
6
|
+
UpstepConfig,
|
|
7
|
+
Feedback,
|
|
8
|
+
FeedbackType,
|
|
9
|
+
FeedbackStatus,
|
|
10
|
+
VoteValue,
|
|
11
|
+
} from "@upstep/types";
|
|
12
|
+
|
|
13
|
+
export { SupportView } from "./SupportView";
|
|
14
|
+
|
|
15
|
+
export { SupportClient, SupportApiError } from "@upstep/js/support";
|
|
16
|
+
export type {
|
|
17
|
+
SupportConfiguration,
|
|
18
|
+
SupportConversation,
|
|
19
|
+
SupportMessage,
|
|
20
|
+
SupportWidget,
|
|
21
|
+
} from "@upstep/types";
|