@upstep/react-native 0.6.16 → 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 +6 -0
- package/README.md +17 -2
- package/package.json +8 -3
- package/src/FeedbackContext.tsx +83 -33
- package/src/FeedbackSheet.tsx +452 -124
- package/src/SupportView.tsx +531 -0
- package/src/api.ts +3 -0
- package/src/index.ts +17 -1
- package/.turbo/turbo-type-check.log +0 -4
- package/tsconfig.json +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- Native support chat in the existing feedback sheet, with signed customer identities and anonymous session persistence.
|
|
6
|
+
- Quick replies, issue forms, resolution checks and a required contact card before team handoff.
|
|
7
|
+
- Larger text, softer colors and white/black themes.
|
|
8
|
+
|
|
3
9
|
## 0.6.16
|
|
4
10
|
|
|
5
11
|
- Fix TypeScript compatibility across React Native versions when rendering the feedback-sheet backdrop.
|
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 react-native-safe-area-context
|
|
8
|
+
npm install @upstep/react-native@^0.7.0 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` | `#5B7168` | 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
|
|
@@ -72,3 +72,18 @@ If `expo-sensors` isn't installed, the hook is a no-op — safe to call uncondit
|
|
|
72
72
|
- [Full documentation](https://upstep.dev/#integrate)
|
|
73
73
|
- [Dashboard / sign up](https://upstep.dev/login)
|
|
74
74
|
- [@upstep/js](https://www.npmjs.com/package/@upstep/js) for web apps
|
|
75
|
+
|
|
76
|
+
## Native AI and team support
|
|
77
|
+
|
|
78
|
+
Version 0.7.0 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
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<FeedbackProvider apiKey="YOUR_PROJECT_KEY" chat={true} supportIdentityToken={signedToken}>
|
|
82
|
+
<FeedbackButton />
|
|
83
|
+
<FeedbackSheet />
|
|
84
|
+
</FeedbackProvider>
|
|
85
|
+
```
|
|
86
|
+
|
|
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
|
+
|
|
89
|
+
Chat includes large text, soft message bubbles, quick replies, issue detail cards and resolution confirmations. 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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstep/react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Native UpStep feedback and AI support chat for React Native",
|
|
5
5
|
"homepage": "https://upstep.dev",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@upstep/types": "0.
|
|
22
|
+
"@upstep/types": "0.7.0",
|
|
23
|
+
"@upstep/js": "0.7.0"
|
|
23
24
|
},
|
|
24
25
|
"peerDependencies": {
|
|
25
26
|
"@react-native-async-storage/async-storage": ">=1.18",
|
|
@@ -40,6 +41,10 @@
|
|
|
40
41
|
"upstep"
|
|
41
42
|
],
|
|
42
43
|
"license": "MIT",
|
|
44
|
+
"files": [
|
|
45
|
+
"src",
|
|
46
|
+
"CHANGELOG.md"
|
|
47
|
+
],
|
|
43
48
|
"scripts": {
|
|
44
49
|
"type-check": "tsc --noEmit"
|
|
45
50
|
}
|
package/src/FeedbackContext.tsx
CHANGED
|
@@ -13,23 +13,30 @@ import type {
|
|
|
13
13
|
FeedbackWithComments,
|
|
14
14
|
UpstepConfig,
|
|
15
15
|
VoteValue,
|
|
16
|
+
SupportConfiguration,
|
|
16
17
|
} from "@upstep/types";
|
|
17
18
|
import { UpstepApiClient } from "./api";
|
|
18
19
|
|
|
19
20
|
// Enable LayoutAnimation on Android
|
|
20
21
|
if (Platform.OS === "android") {
|
|
21
|
-
const um = UIManager as unknown as {
|
|
22
|
+
const um = UIManager as unknown as {
|
|
23
|
+
setLayoutAnimationEnabledExperimental?: (v: boolean) => void;
|
|
24
|
+
};
|
|
22
25
|
um.setLayoutAnimationEnabledExperimental?.(true);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
const DEFAULT_ACCENT = "#
|
|
28
|
+
const DEFAULT_ACCENT = "#5B7168";
|
|
26
29
|
|
|
27
30
|
interface UpstepContextValue {
|
|
28
31
|
client: UpstepApiClient;
|
|
29
32
|
feedItems: Feedback[];
|
|
30
33
|
feedLoading: boolean;
|
|
31
34
|
loadFeed: () => Promise<void>;
|
|
32
|
-
submit: (
|
|
35
|
+
submit: (
|
|
36
|
+
title: string,
|
|
37
|
+
content: string,
|
|
38
|
+
type?: FeedbackType,
|
|
39
|
+
) => Promise<void>;
|
|
33
40
|
vote: (feedbackId: string, value: VoteValue) => Promise<void>;
|
|
34
41
|
getItem: (feedbackId: string) => Promise<FeedbackWithComments>;
|
|
35
42
|
isOpen: boolean;
|
|
@@ -39,6 +46,8 @@ interface UpstepContextValue {
|
|
|
39
46
|
accentColor: string;
|
|
40
47
|
theme: "light" | "dark" | "auto";
|
|
41
48
|
showBranding: boolean;
|
|
49
|
+
supportConfig: SupportConfiguration | null;
|
|
50
|
+
supportRevision: number;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
const UpstepContext = createContext<UpstepContextValue | null>(null);
|
|
@@ -51,12 +60,37 @@ export function FeedbackProvider({
|
|
|
51
60
|
const [feedItems, setFeedItems] = useState<Feedback[]>([]);
|
|
52
61
|
const [feedLoading, setFeedLoading] = useState(true);
|
|
53
62
|
const [isOpen, setIsOpen] = useState(false);
|
|
63
|
+
const [supportConfig, setSupportConfig] =
|
|
64
|
+
useState<SupportConfiguration | null>(null);
|
|
65
|
+
const [supportRevision, setSupportRevision] = useState(0);
|
|
54
66
|
const [showBranding, setShowBranding] = useState(true);
|
|
55
67
|
|
|
56
68
|
useEffect(() => {
|
|
57
69
|
client.setUserId(config.userId);
|
|
58
70
|
}, [client, config.userId]);
|
|
59
71
|
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
let active = true;
|
|
74
|
+
void client.support.identify(config.supportIdentityToken).then(() => {
|
|
75
|
+
if (active) setSupportRevision((v) => v + 1);
|
|
76
|
+
});
|
|
77
|
+
if (config.chat === false) {
|
|
78
|
+
setSupportConfig(null);
|
|
79
|
+
return () => {
|
|
80
|
+
active = false;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
void client.support
|
|
84
|
+
.configuration()
|
|
85
|
+
.then((value) => {
|
|
86
|
+
if (active) setSupportConfig(value);
|
|
87
|
+
})
|
|
88
|
+
.catch(() => {});
|
|
89
|
+
return () => {
|
|
90
|
+
active = false;
|
|
91
|
+
};
|
|
92
|
+
}, [client, config.chat, config.supportIdentityToken]);
|
|
93
|
+
|
|
60
94
|
const loadFeed = useCallback(async () => {
|
|
61
95
|
setFeedLoading(true);
|
|
62
96
|
try {
|
|
@@ -70,7 +104,9 @@ export function FeedbackProvider({
|
|
|
70
104
|
}
|
|
71
105
|
}, [client]);
|
|
72
106
|
|
|
73
|
-
useEffect(() => {
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
loadFeed();
|
|
109
|
+
}, [loadFeed]);
|
|
74
110
|
|
|
75
111
|
const submit = useCallback(
|
|
76
112
|
async (title: string, content: string, type?: FeedbackType) => {
|
|
@@ -80,39 +116,42 @@ export function FeedbackProvider({
|
|
|
80
116
|
await client.submitFeedback(payload);
|
|
81
117
|
await loadFeed();
|
|
82
118
|
},
|
|
83
|
-
[client, loadFeed]
|
|
119
|
+
[client, loadFeed],
|
|
84
120
|
);
|
|
85
121
|
|
|
86
|
-
const vote = useCallback(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
const vote = useCallback(
|
|
123
|
+
async (feedbackId: string, value: VoteValue) => {
|
|
124
|
+
// Capture snapshot for rollback, then animate into new sorted position
|
|
125
|
+
let snapshot: Feedback[] = [];
|
|
126
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
127
|
+
setFeedItems((prev) => {
|
|
128
|
+
snapshot = prev;
|
|
129
|
+
const updated = prev.map((f) => {
|
|
130
|
+
if (f.id !== feedbackId) return f;
|
|
131
|
+
const wasVoted = f.userVote === "UP";
|
|
132
|
+
return {
|
|
133
|
+
...f,
|
|
134
|
+
upvotes: wasVoted ? f.upvotes - 1 : f.upvotes + 1,
|
|
135
|
+
userVote: wasVoted ? null : ("UP" as VoteValue),
|
|
136
|
+
};
|
|
137
|
+
});
|
|
138
|
+
return [...updated].sort((a, b) => b.upvotes - a.upvotes);
|
|
100
139
|
});
|
|
101
|
-
return [...updated].sort((a, b) => b.upvotes - a.upvotes);
|
|
102
|
-
});
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
141
|
+
try {
|
|
142
|
+
await client.vote(feedbackId, value);
|
|
143
|
+
} catch {
|
|
144
|
+
// API failed — animate back to original order
|
|
145
|
+
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
|
|
146
|
+
setFeedItems(snapshot);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[client],
|
|
150
|
+
);
|
|
112
151
|
|
|
113
152
|
const getItem = useCallback(
|
|
114
153
|
(feedbackId: string) => client.getItem(feedbackId),
|
|
115
|
-
[client]
|
|
154
|
+
[client],
|
|
116
155
|
);
|
|
117
156
|
|
|
118
157
|
const openSheet = useCallback(() => {
|
|
@@ -125,17 +164,28 @@ export function FeedbackProvider({
|
|
|
125
164
|
|
|
126
165
|
const identify = useCallback(
|
|
127
166
|
(userId: string | undefined) => client.setUserId(userId),
|
|
128
|
-
[client]
|
|
167
|
+
[client],
|
|
129
168
|
);
|
|
130
169
|
|
|
131
170
|
return (
|
|
132
171
|
<UpstepContext.Provider
|
|
133
172
|
value={{
|
|
134
|
-
client,
|
|
135
|
-
|
|
173
|
+
client,
|
|
174
|
+
feedItems,
|
|
175
|
+
feedLoading,
|
|
176
|
+
loadFeed,
|
|
177
|
+
submit,
|
|
178
|
+
vote,
|
|
179
|
+
getItem,
|
|
180
|
+
isOpen,
|
|
181
|
+
openSheet,
|
|
182
|
+
closeSheet,
|
|
183
|
+
identify,
|
|
136
184
|
accentColor: config.accentColor ?? DEFAULT_ACCENT,
|
|
137
185
|
theme: config.theme ?? "auto",
|
|
138
186
|
showBranding,
|
|
187
|
+
supportConfig,
|
|
188
|
+
supportRevision,
|
|
139
189
|
}}
|
|
140
190
|
>
|
|
141
191
|
{children}
|