@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 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` | `#D97757` | Any CSS color, used for the launcher button and highlights. |
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.6.16",
4
- "description": "Upstep feedback SDK for React Native",
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.5.6"
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
  }
@@ -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 { setLayoutAnimationEnabledExperimental?: (v: boolean) => void };
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 = "#D97757";
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: (title: string, content: string, type?: FeedbackType) => Promise<void>;
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(() => { loadFeed(); }, [loadFeed]);
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(async (feedbackId: string, value: VoteValue) => {
87
- // Capture snapshot for rollback, then animate into new sorted position
88
- let snapshot: Feedback[] = [];
89
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
90
- setFeedItems((prev) => {
91
- snapshot = prev;
92
- const updated = prev.map((f) => {
93
- if (f.id !== feedbackId) return f;
94
- const wasVoted = f.userVote === "UP";
95
- return {
96
- ...f,
97
- upvotes: wasVoted ? f.upvotes - 1 : f.upvotes + 1,
98
- userVote: wasVoted ? null : ("UP" as VoteValue),
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
- try {
105
- await client.vote(feedbackId, value);
106
- } catch {
107
- // API failed — animate back to original order
108
- LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
109
- setFeedItems(snapshot);
110
- }
111
- }, [client]);
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, feedItems, feedLoading, loadFeed, submit, vote, getItem,
135
- isOpen, openSheet, closeSheet, identify,
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}