@upstep/react-native 0.6.7 → 0.6.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upstep/react-native",
3
- "version": "0.6.7",
3
+ "version": "0.6.10",
4
4
  "description": "Upstep feedback SDK for React Native",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -12,7 +12,7 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@upstep/types": "0.5.0"
15
+ "@upstep/types": "0.5.1"
16
16
  },
17
17
  "peerDependencies": {
18
18
  "react": ">=18",
@@ -1,13 +1,14 @@
1
1
  import React from "react";
2
- import { TouchableOpacity, Text, StyleSheet } from "react-native";
2
+ import { TouchableOpacity, Text, View, StyleSheet } from "react-native";
3
3
  import { useUpstep } from "./FeedbackContext";
4
4
 
5
5
  interface Props {
6
6
  position?: "bottom-right" | "bottom-left";
7
7
  label?: string;
8
+ icon?: React.ReactNode;
8
9
  }
9
10
 
10
- export function FeedbackButton({ position = "bottom-right", label = "Feedback" }: Props) {
11
+ export function FeedbackButton({ position = "bottom-right", label = "Feedback", icon }: Props) {
11
12
  const { openSheet, accentColor } = useUpstep();
12
13
 
13
14
  return (
@@ -20,12 +21,21 @@ export function FeedbackButton({ position = "bottom-right", label = "Feedback" }
20
21
  ]}
21
22
  activeOpacity={0.85}
22
23
  >
23
- <Text style={styles.icon}>💬</Text>
24
+ {icon ?? <ChatIcon />}
24
25
  <Text style={styles.label}>{label}</Text>
25
26
  </TouchableOpacity>
26
27
  );
27
28
  }
28
29
 
30
+ function ChatIcon() {
31
+ return (
32
+ <View style={styles.iconWrap}>
33
+ <View style={styles.iconBubble} />
34
+ <View style={styles.iconTail} />
35
+ </View>
36
+ );
37
+ }
38
+
29
39
  const styles = StyleSheet.create({
30
40
  btn: {
31
41
  position: "absolute",
@@ -45,6 +55,27 @@ const styles = StyleSheet.create({
45
55
  },
46
56
  right: { right: 20 },
47
57
  left: { left: 20 },
48
- icon: { fontSize: 14 },
49
58
  label: { color: "#fff", fontSize: 14, fontWeight: "600" },
59
+ iconWrap: { width: 16, height: 16, position: "relative" },
60
+ iconBubble: {
61
+ position: "absolute",
62
+ top: 0,
63
+ left: 0,
64
+ right: 2,
65
+ bottom: 3,
66
+ borderRadius: 4,
67
+ borderWidth: 1.5,
68
+ borderColor: "#fff",
69
+ },
70
+ iconTail: {
71
+ position: "absolute",
72
+ bottom: 0,
73
+ left: 3,
74
+ width: 5,
75
+ height: 5,
76
+ borderRightWidth: 1.5,
77
+ borderBottomWidth: 1.5,
78
+ borderColor: "#fff",
79
+ transform: [{ rotate: "45deg" }],
80
+ },
50
81
  });
@@ -38,6 +38,7 @@ interface UpstepContextValue {
38
38
  identify: (userId: string | undefined) => void;
39
39
  accentColor: string;
40
40
  theme: "light" | "dark" | "auto";
41
+ showBranding: boolean;
41
42
  }
42
43
 
43
44
  const UpstepContext = createContext<UpstepContextValue | null>(null);
@@ -50,6 +51,7 @@ export function FeedbackProvider({
50
51
  const [feedItems, setFeedItems] = useState<Feedback[]>([]);
51
52
  const [feedLoading, setFeedLoading] = useState(true);
52
53
  const [isOpen, setIsOpen] = useState(false);
54
+ const [showBranding, setShowBranding] = useState(true);
53
55
 
54
56
  useEffect(() => {
55
57
  client.setUserId(config.userId);
@@ -60,6 +62,7 @@ export function FeedbackProvider({
60
62
  try {
61
63
  const data = await client.listFeedback({ sort: "votes", limit: 30 });
62
64
  setFeedItems(data.items);
65
+ setShowBranding(data.showBranding ?? true);
63
66
  } catch {
64
67
  // non-critical
65
68
  } finally {
@@ -81,9 +84,11 @@ export function FeedbackProvider({
81
84
  );
82
85
 
83
86
  const vote = useCallback(async (feedbackId: string, value: VoteValue) => {
84
- // Optimistic update: flip vote + re-sort with a smooth animation
87
+ // Capture snapshot for rollback, then animate into new sorted position
88
+ let snapshot: Feedback[] = [];
85
89
  LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
86
90
  setFeedItems((prev) => {
91
+ snapshot = prev;
87
92
  const updated = prev.map((f) => {
88
93
  if (f.id !== feedbackId) return f;
89
94
  const wasVoted = f.userVote === "UP";
@@ -96,10 +101,14 @@ export function FeedbackProvider({
96
101
  return [...updated].sort((a, b) => b.upvotes - a.upvotes);
97
102
  });
98
103
 
99
- // Confirm with server, then silently sync
100
- await client.vote(feedbackId, value);
101
- await loadFeed();
102
- }, [client, loadFeed]);
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]);
103
112
 
104
113
  const getItem = useCallback(
105
114
  (feedbackId: string) => client.getItem(feedbackId),
@@ -126,6 +135,7 @@ export function FeedbackProvider({
126
135
  isOpen, openSheet, closeSheet, identify,
127
136
  accentColor: config.accentColor ?? DEFAULT_ACCENT,
128
137
  theme: config.theme ?? "auto",
138
+ showBranding,
129
139
  }}
130
140
  >
131
141
  {children}
@@ -60,7 +60,7 @@ type Nav =
60
60
  // ─── FeedbackSheet ────────────────────────────────────────────────────────────
61
61
 
62
62
  export function FeedbackSheet() {
63
- const { isOpen, closeSheet, accentColor, theme, loadFeed } = useUpstep();
63
+ const { isOpen, closeSheet, accentColor, theme, loadFeed, showBranding } = useUpstep();
64
64
  const scheme = useColorScheme();
65
65
  const isDark = theme === "dark" || (theme === "auto" && scheme === "dark");
66
66
  const p = isDark ? DARK : LIGHT;
@@ -193,19 +193,21 @@ export function FeedbackSheet() {
193
193
  )}
194
194
  </ScrollView>
195
195
 
196
- {/* Branding + safe-area bottom padding in one strip */}
197
- <TouchableOpacity
198
- onPress={() => Linking.openURL("https://upstep.dev")}
199
- activeOpacity={0.6}
200
- style={[
201
- styles.poweredBy,
202
- { borderTopColor: p.border, paddingBottom: insets.bottom + 10, backgroundColor: p.bg },
203
- ]}
204
- >
205
- <Text style={[styles.poweredByText, { color: p.textFaint }]}>
206
- Powered by <Text style={{ fontWeight: "700" }}>Upstep.dev</Text>
207
- </Text>
208
- </TouchableOpacity>
196
+ {/* Branding — hidden for Business plan */}
197
+ {showBranding && (
198
+ <TouchableOpacity
199
+ onPress={() => Linking.openURL("https://upstep.dev")}
200
+ activeOpacity={0.6}
201
+ style={[
202
+ styles.poweredBy,
203
+ { borderTopColor: p.border, paddingBottom: insets.bottom + 10, backgroundColor: p.bg },
204
+ ]}
205
+ >
206
+ <Text style={[styles.poweredByText, { color: p.textFaint }]}>
207
+ Powered by <Text style={{ fontWeight: "700" }}>Upstep.dev</Text>
208
+ </Text>
209
+ </TouchableOpacity>
210
+ )}
209
211
  </View>
210
212
  </KeyboardAvoidingView>
211
213
  </Modal>