@upstep/react-native 0.1.0 → 0.4.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/package.json +2 -2
- package/src/FeedbackButton.tsx +14 -6
- package/src/FeedbackContext.tsx +23 -1
- package/src/FeedbackSheet.tsx +142 -82
- package/src/api.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upstep/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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.0
|
|
15
|
+
"@upstep/types": "0.4.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"@gorhom/bottom-sheet": ">=4",
|
package/src/FeedbackButton.tsx
CHANGED
|
@@ -8,14 +8,19 @@ interface Props {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function FeedbackButton({ position = "bottom-right", label = "Feedback" }: Props) {
|
|
11
|
-
const { openSheet } = useUpstep();
|
|
11
|
+
const { openSheet, accentColor } = useUpstep();
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<TouchableOpacity
|
|
15
15
|
onPress={openSheet}
|
|
16
|
-
style={[
|
|
16
|
+
style={[
|
|
17
|
+
styles.btn,
|
|
18
|
+
{ backgroundColor: accentColor },
|
|
19
|
+
position === "bottom-left" ? styles.left : styles.right,
|
|
20
|
+
]}
|
|
17
21
|
activeOpacity={0.85}
|
|
18
22
|
>
|
|
23
|
+
<Text style={styles.icon}>💬</Text>
|
|
19
24
|
<Text style={styles.label}>{label}</Text>
|
|
20
25
|
</TouchableOpacity>
|
|
21
26
|
);
|
|
@@ -25,18 +30,21 @@ const styles = StyleSheet.create({
|
|
|
25
30
|
btn: {
|
|
26
31
|
position: "absolute",
|
|
27
32
|
bottom: 24,
|
|
28
|
-
|
|
33
|
+
flexDirection: "row",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
gap: 6,
|
|
29
36
|
borderRadius: 9999,
|
|
30
37
|
paddingVertical: 12,
|
|
31
|
-
paddingHorizontal:
|
|
38
|
+
paddingHorizontal: 18,
|
|
32
39
|
shadowColor: "#000",
|
|
33
|
-
shadowOffset: { width: 0, height:
|
|
40
|
+
shadowOffset: { width: 0, height: 5 },
|
|
34
41
|
shadowOpacity: 0.2,
|
|
35
|
-
shadowRadius:
|
|
42
|
+
shadowRadius: 10,
|
|
36
43
|
elevation: 6,
|
|
37
44
|
zIndex: 100,
|
|
38
45
|
},
|
|
39
46
|
right: { right: 20 },
|
|
40
47
|
left: { left: 20 },
|
|
48
|
+
icon: { fontSize: 14 },
|
|
41
49
|
label: { color: "#fff", fontSize: 14, fontWeight: "600" },
|
|
42
50
|
});
|
package/src/FeedbackContext.tsx
CHANGED
|
@@ -11,14 +11,23 @@ import type { Feedback, FeedbackType, UpstepConfig, VoteValue } from "@upstep/ty
|
|
|
11
11
|
import { UpstepApiClient } from "./api";
|
|
12
12
|
import type BottomSheet from "@gorhom/bottom-sheet";
|
|
13
13
|
|
|
14
|
+
const DEFAULT_ACCENT = "#D97757";
|
|
15
|
+
|
|
14
16
|
interface UpstepContextValue {
|
|
15
17
|
client: UpstepApiClient;
|
|
16
18
|
feedItems: Feedback[];
|
|
17
19
|
loadFeed: () => Promise<void>;
|
|
18
20
|
submit: (content: string, type?: FeedbackType) => Promise<void>;
|
|
19
21
|
vote: (feedbackId: string, value: VoteValue) => Promise<void>;
|
|
22
|
+
/** Open the feedback sheet — call from your own UI (settings, menu, etc.). */
|
|
20
23
|
openSheet: () => void;
|
|
24
|
+
/** Set / update the end-user id at runtime (e.g. after the user logs in). */
|
|
25
|
+
identify: (userId: string | undefined) => void;
|
|
21
26
|
sheetRef: React.RefObject<BottomSheet | null>;
|
|
27
|
+
/** Accent color from provider config (default applied). */
|
|
28
|
+
accentColor: string;
|
|
29
|
+
/** Theme mode from provider config (default "auto"). */
|
|
30
|
+
theme: "light" | "dark" | "auto";
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
const UpstepContext = createContext<UpstepContextValue | null>(null);
|
|
@@ -31,6 +40,11 @@ export function FeedbackProvider({
|
|
|
31
40
|
const [feedItems, setFeedItems] = useState<Feedback[]>([]);
|
|
32
41
|
const sheetRef = useRef<BottomSheet>(null);
|
|
33
42
|
|
|
43
|
+
// Keep the end-user id in sync if `userId` resolves after mount (async login).
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
client.setUserId(config.userId);
|
|
46
|
+
}, [client, config.userId]);
|
|
47
|
+
|
|
34
48
|
const loadFeed = useCallback(async () => {
|
|
35
49
|
try {
|
|
36
50
|
const data = await client.listFeedback({ sort: "votes", limit: 20 });
|
|
@@ -58,8 +72,16 @@ export function FeedbackProvider({
|
|
|
58
72
|
sheetRef.current?.expand();
|
|
59
73
|
}, []);
|
|
60
74
|
|
|
75
|
+
const identify = useCallback((userId: string | undefined) => client.setUserId(userId), [client]);
|
|
76
|
+
|
|
61
77
|
return (
|
|
62
|
-
<UpstepContext.Provider
|
|
78
|
+
<UpstepContext.Provider
|
|
79
|
+
value={{
|
|
80
|
+
client, feedItems, loadFeed, submit, vote, openSheet, identify, sheetRef,
|
|
81
|
+
accentColor: config.accentColor ?? DEFAULT_ACCENT,
|
|
82
|
+
theme: config.theme ?? "auto",
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
63
85
|
{children}
|
|
64
86
|
</UpstepContext.Provider>
|
|
65
87
|
);
|
package/src/FeedbackSheet.tsx
CHANGED
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
TextInput,
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
StyleSheet,
|
|
8
|
-
ScrollView,
|
|
9
8
|
ActivityIndicator,
|
|
9
|
+
useColorScheme,
|
|
10
10
|
} from "react-native";
|
|
11
11
|
import BottomSheet, { BottomSheetView, BottomSheetScrollView } from "@gorhom/bottom-sheet";
|
|
12
12
|
import type { Feedback, FeedbackType } from "@upstep/types";
|
|
@@ -15,10 +15,37 @@ import { useUpstep } from "./FeedbackContext";
|
|
|
15
15
|
type Tab = "submit" | "feed";
|
|
16
16
|
|
|
17
17
|
const TYPES: FeedbackType[] = ["BUG", "FEATURE", "GENERAL"];
|
|
18
|
+
const TYPE_LABELS: Record<FeedbackType, string> = {
|
|
19
|
+
BUG: "🐞 Bug", FEATURE: "✨ Feature", GENERAL: "💬 General",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface Palette {
|
|
23
|
+
bg: string;
|
|
24
|
+
bgSoft: string;
|
|
25
|
+
text: string;
|
|
26
|
+
textSoft: string;
|
|
27
|
+
textFaint: string;
|
|
28
|
+
border: string;
|
|
29
|
+
handle: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const LIGHT: Palette = {
|
|
33
|
+
bg: "#ffffff", bgSoft: "#f6f5f2", text: "#1a1915",
|
|
34
|
+
textSoft: "#56544d", textFaint: "#9b9890", border: "#e8e6df", handle: "#dad7ce",
|
|
35
|
+
};
|
|
36
|
+
const DARK: Palette = {
|
|
37
|
+
bg: "#1c1b19", bgSoft: "#262522", text: "#f5f4f0",
|
|
38
|
+
textSoft: "#b4b1a8", textFaint: "#7d7a72", border: "#33312c", handle: "#48453f",
|
|
39
|
+
};
|
|
18
40
|
|
|
19
41
|
export function FeedbackSheet() {
|
|
20
|
-
const { feedItems, submit, vote, sheetRef } = useUpstep();
|
|
21
|
-
const
|
|
42
|
+
const { feedItems, submit, vote, sheetRef, accentColor, theme } = useUpstep();
|
|
43
|
+
const scheme = useColorScheme();
|
|
44
|
+
const isDark = theme === "dark" || (theme === "auto" && scheme === "dark");
|
|
45
|
+
const p = isDark ? DARK : LIGHT;
|
|
46
|
+
const accent = accentColor;
|
|
47
|
+
|
|
48
|
+
const snapPoints = useMemo(() => ["80%"], []);
|
|
22
49
|
const [tab, setTab] = useState<Tab>("submit");
|
|
23
50
|
const [content, setContent] = useState("");
|
|
24
51
|
const [type, setType] = useState<FeedbackType>("GENERAL");
|
|
@@ -31,7 +58,7 @@ export function FeedbackSheet() {
|
|
|
31
58
|
try {
|
|
32
59
|
await submit(content.trim(), type);
|
|
33
60
|
setContent("");
|
|
34
|
-
setMsg("Thanks
|
|
61
|
+
setMsg("Thanks — we got your feedback! 🎉");
|
|
35
62
|
} catch {
|
|
36
63
|
setMsg("Something went wrong. Please try again.");
|
|
37
64
|
} finally {
|
|
@@ -45,25 +72,33 @@ export function FeedbackSheet() {
|
|
|
45
72
|
index={-1}
|
|
46
73
|
snapPoints={snapPoints}
|
|
47
74
|
enablePanDownToClose
|
|
48
|
-
backgroundStyle={
|
|
49
|
-
handleIndicatorStyle={
|
|
75
|
+
backgroundStyle={{ backgroundColor: p.bg, borderRadius: 24 }}
|
|
76
|
+
handleIndicatorStyle={{ backgroundColor: p.handle, width: 40, height: 4 }}
|
|
50
77
|
>
|
|
51
78
|
<BottomSheetView style={styles.header}>
|
|
52
|
-
<Text style={styles.title}>Share feedback</Text>
|
|
79
|
+
<Text style={[styles.title, { color: p.text }]}>Share your feedback</Text>
|
|
80
|
+
<Text style={[styles.subtitle, { color: p.textFaint }]}>
|
|
81
|
+
Tell us what to build next, or vote on ideas.
|
|
82
|
+
</Text>
|
|
53
83
|
</BottomSheetView>
|
|
54
84
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
{/* Segmented control */}
|
|
86
|
+
<View style={[styles.segment, { backgroundColor: p.bgSoft }]}>
|
|
87
|
+
{(["submit", "feed"] as Tab[]).map((t) => {
|
|
88
|
+
const active = tab === t;
|
|
89
|
+
return (
|
|
90
|
+
<TouchableOpacity
|
|
91
|
+
key={t}
|
|
92
|
+
onPress={() => setTab(t)}
|
|
93
|
+
activeOpacity={0.8}
|
|
94
|
+
style={[styles.segmentBtn, active && { backgroundColor: p.bg, shadowOpacity: 0.08 }]}
|
|
95
|
+
>
|
|
96
|
+
<Text style={[styles.segmentText, { color: active ? p.text : p.textSoft }]}>
|
|
97
|
+
{t === "submit" ? "Give feedback" : "Vote on ideas"}
|
|
98
|
+
</Text>
|
|
99
|
+
</TouchableOpacity>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
67
102
|
</View>
|
|
68
103
|
|
|
69
104
|
{tab === "submit" ? (
|
|
@@ -72,28 +107,36 @@ export function FeedbackSheet() {
|
|
|
72
107
|
value={content}
|
|
73
108
|
onChangeText={setContent}
|
|
74
109
|
placeholder="What's on your mind?"
|
|
75
|
-
placeholderTextColor=
|
|
110
|
+
placeholderTextColor={p.textFaint}
|
|
76
111
|
multiline
|
|
77
112
|
maxLength={2000}
|
|
78
|
-
style={styles.textarea}
|
|
113
|
+
style={[styles.textarea, { borderColor: p.border, backgroundColor: p.bgSoft, color: p.text }]}
|
|
79
114
|
/>
|
|
80
115
|
<View style={styles.typeRow}>
|
|
81
|
-
{TYPES.map((t) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
{TYPES.map((t) => {
|
|
117
|
+
const active = type === t;
|
|
118
|
+
return (
|
|
119
|
+
<TouchableOpacity
|
|
120
|
+
key={t}
|
|
121
|
+
onPress={() => setType(t)}
|
|
122
|
+
activeOpacity={0.8}
|
|
123
|
+
style={[
|
|
124
|
+
styles.typeBtn,
|
|
125
|
+
{ borderColor: active ? accent : p.border, backgroundColor: active ? accent : p.bg },
|
|
126
|
+
]}
|
|
127
|
+
>
|
|
128
|
+
<Text style={[styles.typeBtnText, { color: active ? "#fff" : p.textSoft }]}>
|
|
129
|
+
{TYPE_LABELS[t]}
|
|
130
|
+
</Text>
|
|
131
|
+
</TouchableOpacity>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
92
134
|
</View>
|
|
93
135
|
<TouchableOpacity
|
|
94
136
|
onPress={handleSubmit}
|
|
95
137
|
disabled={loading || !content.trim()}
|
|
96
|
-
|
|
138
|
+
activeOpacity={0.85}
|
|
139
|
+
style={[styles.submitBtn, { backgroundColor: accent }, (loading || !content.trim()) && styles.disabledBtn]}
|
|
97
140
|
>
|
|
98
141
|
{loading ? (
|
|
99
142
|
<ActivityIndicator color="#fff" />
|
|
@@ -101,15 +144,18 @@ export function FeedbackSheet() {
|
|
|
101
144
|
<Text style={styles.submitBtnText}>Send feedback</Text>
|
|
102
145
|
)}
|
|
103
146
|
</TouchableOpacity>
|
|
104
|
-
{msg ? <Text style={styles.msgText}>{msg}</Text> : null}
|
|
147
|
+
{msg ? <Text style={[styles.msgText, { color: accent }]}>{msg}</Text> : null}
|
|
105
148
|
</BottomSheetView>
|
|
106
149
|
) : (
|
|
107
150
|
<BottomSheetScrollView contentContainerStyle={styles.body}>
|
|
108
151
|
{feedItems.length === 0 ? (
|
|
109
|
-
<
|
|
152
|
+
<View style={styles.empty}>
|
|
153
|
+
<Text style={[styles.emptyTitle, { color: p.textSoft }]}>No ideas yet</Text>
|
|
154
|
+
<Text style={[styles.emptyText, { color: p.textFaint }]}>Be the first to share one.</Text>
|
|
155
|
+
</View>
|
|
110
156
|
) : (
|
|
111
157
|
feedItems.map((f) => (
|
|
112
|
-
<FeedItem key={f.id} item={f} onVote={vote} />
|
|
158
|
+
<FeedItem key={f.id} item={f} onVote={vote} accent={accent} p={p} />
|
|
113
159
|
))
|
|
114
160
|
)}
|
|
115
161
|
</BottomSheetScrollView>
|
|
@@ -118,62 +164,76 @@ export function FeedbackSheet() {
|
|
|
118
164
|
);
|
|
119
165
|
}
|
|
120
166
|
|
|
121
|
-
|
|
167
|
+
const BADGE_BG: Record<string, string> = { BUG: "#fdecec", FEATURE: "#e9f0fd" };
|
|
168
|
+
const BADGE_FG: Record<string, string> = { BUG: "#d6453d", FEATURE: "#3b76d6" };
|
|
169
|
+
|
|
170
|
+
function FeedItem({
|
|
171
|
+
item, onVote, accent, p,
|
|
172
|
+
}: {
|
|
173
|
+
item: Feedback;
|
|
174
|
+
onVote: (id: string, v: "UP" | "DOWN") => void;
|
|
175
|
+
accent: string;
|
|
176
|
+
p: Palette;
|
|
177
|
+
}) {
|
|
178
|
+
const voted = item.userVote === "UP";
|
|
122
179
|
return (
|
|
123
|
-
<View style={styles.feedItem}>
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
180
|
+
<View style={[styles.feedItem, { borderColor: p.border, backgroundColor: p.bg }]}>
|
|
181
|
+
<TouchableOpacity
|
|
182
|
+
onPress={() => onVote(item.id, "UP")}
|
|
183
|
+
activeOpacity={0.8}
|
|
184
|
+
style={[
|
|
185
|
+
styles.vote,
|
|
186
|
+
{ borderColor: voted ? accent : p.border, backgroundColor: voted ? `${accent}1a` : p.bgSoft },
|
|
187
|
+
]}
|
|
188
|
+
>
|
|
189
|
+
<Text style={[styles.voteArrow, { color: accent }]}>▲</Text>
|
|
190
|
+
<Text style={[styles.voteCount, { color: p.text }]}>{item.upvotes}</Text>
|
|
191
|
+
</TouchableOpacity>
|
|
192
|
+
<View style={styles.feedMain}>
|
|
193
|
+
<Text style={[styles.feedContent, { color: p.text }]}>{item.content}</Text>
|
|
194
|
+
<View style={styles.feedMeta}>
|
|
195
|
+
<View style={[styles.badge, { backgroundColor: BADGE_BG[item.type] ?? p.bgSoft }]}>
|
|
196
|
+
<Text style={[styles.badgeText, { color: BADGE_FG[item.type] ?? p.textSoft }]}>{item.type}</Text>
|
|
197
|
+
</View>
|
|
198
|
+
<Text style={[styles.feedDate, { color: p.textFaint }]}>
|
|
199
|
+
{new Date(item.createdAt).toLocaleDateString()}
|
|
129
200
|
</Text>
|
|
130
201
|
</View>
|
|
131
|
-
<Text style={styles.feedDate}>{new Date(item.createdAt).toLocaleDateString()}</Text>
|
|
132
|
-
</View>
|
|
133
|
-
<View style={styles.voteRow}>
|
|
134
|
-
<TouchableOpacity style={styles.voteBtn} onPress={() => onVote(item.id, "UP")}>
|
|
135
|
-
<Text style={styles.voteBtnText}>▲ {item.upvotes}</Text>
|
|
136
|
-
</TouchableOpacity>
|
|
137
|
-
<TouchableOpacity style={styles.voteBtn} onPress={() => onVote(item.id, "DOWN")}>
|
|
138
|
-
<Text style={styles.voteBtnText}>▼ {item.downvotes}</Text>
|
|
139
|
-
</TouchableOpacity>
|
|
140
202
|
</View>
|
|
141
203
|
</View>
|
|
142
204
|
);
|
|
143
205
|
}
|
|
144
206
|
|
|
145
207
|
const styles = StyleSheet.create({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
body: { padding:
|
|
156
|
-
textarea: { borderWidth: 1,
|
|
157
|
-
typeRow: { flexDirection: "row", gap:
|
|
158
|
-
typeBtn: { paddingVertical:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
selectedTypeBtnText: { color: "#fff" },
|
|
162
|
-
submitBtn: { backgroundColor: "#6366f1", borderRadius: 8, padding: 12, alignItems: "center", marginTop: 8 },
|
|
208
|
+
header: { paddingHorizontal: 20, paddingTop: 4, paddingBottom: 4 },
|
|
209
|
+
title: { fontSize: 17, fontWeight: "700", letterSpacing: -0.2 },
|
|
210
|
+
subtitle: { fontSize: 12.5, marginTop: 3 },
|
|
211
|
+
segment: { flexDirection: "row", gap: 4, marginHorizontal: 20, marginTop: 16, padding: 4, borderRadius: 12 },
|
|
212
|
+
segmentBtn: {
|
|
213
|
+
flex: 1, paddingVertical: 9, alignItems: "center", borderRadius: 9,
|
|
214
|
+
shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowRadius: 3, shadowOpacity: 0,
|
|
215
|
+
},
|
|
216
|
+
segmentText: { fontSize: 13, fontWeight: "600" },
|
|
217
|
+
body: { padding: 20 },
|
|
218
|
+
textarea: { borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 14, minHeight: 110, textAlignVertical: "top", lineHeight: 21 },
|
|
219
|
+
typeRow: { flexDirection: "row", gap: 7, marginVertical: 12 },
|
|
220
|
+
typeBtn: { flex: 1, paddingVertical: 9, alignItems: "center", borderRadius: 9, borderWidth: 1 },
|
|
221
|
+
typeBtnText: { fontSize: 12.5, fontWeight: "600" },
|
|
222
|
+
submitBtn: { borderRadius: 11, padding: 13, alignItems: "center", marginTop: 4 },
|
|
163
223
|
disabledBtn: { opacity: 0.5 },
|
|
164
224
|
submitBtnText: { color: "#fff", fontSize: 14, fontWeight: "600" },
|
|
165
|
-
msgText: { textAlign: "center", fontSize: 13,
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
225
|
+
msgText: { textAlign: "center", fontSize: 13, fontWeight: "500", marginTop: 12 },
|
|
226
|
+
empty: { alignItems: "center", paddingVertical: 40 },
|
|
227
|
+
emptyTitle: { fontSize: 15, fontWeight: "600", marginBottom: 4 },
|
|
228
|
+
emptyText: { fontSize: 14 },
|
|
229
|
+
feedItem: { flexDirection: "row", gap: 12, padding: 14, borderWidth: 1, borderRadius: 14, marginBottom: 10 },
|
|
230
|
+
vote: { minWidth: 46, paddingVertical: 7, borderRadius: 11, borderWidth: 1, alignItems: "center", justifyContent: "center" },
|
|
231
|
+
voteArrow: { fontSize: 11, lineHeight: 13 },
|
|
232
|
+
voteCount: { fontSize: 14, fontWeight: "700", lineHeight: 17 },
|
|
233
|
+
feedMain: { flex: 1 },
|
|
234
|
+
feedContent: { fontSize: 13.5, lineHeight: 20, marginBottom: 8 },
|
|
235
|
+
feedMeta: { flexDirection: "row", alignItems: "center", gap: 7 },
|
|
170
236
|
badge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 9999 },
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
badgeGeneral: { backgroundColor: "#f3f4f6" },
|
|
174
|
-
badgeText: { fontSize: 11, fontWeight: "500" },
|
|
175
|
-
feedDate: { fontSize: 11, color: "#9ca3af" },
|
|
176
|
-
voteRow: { flexDirection: "row", gap: 8 },
|
|
177
|
-
voteBtn: { flexDirection: "row", alignItems: "center", paddingHorizontal: 10, paddingVertical: 4, borderRadius: 9999, borderWidth: 1, borderColor: "#e5e7eb" },
|
|
178
|
-
voteBtnText: { fontSize: 12, color: "#374151" },
|
|
237
|
+
badgeText: { fontSize: 10.5, fontWeight: "700", letterSpacing: 0.2 },
|
|
238
|
+
feedDate: { fontSize: 11 },
|
|
179
239
|
});
|
package/src/api.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
export class UpstepApiClient {
|
|
10
10
|
private readonly baseUrl: string;
|
|
11
11
|
private readonly apiKey: string;
|
|
12
|
-
|
|
12
|
+
userId: string | undefined;
|
|
13
13
|
|
|
14
14
|
constructor(config: UpstepConfig) {
|
|
15
15
|
this.baseUrl = (config.baseUrl ?? "https://upstep.io").replace(/\/$/, "");
|
|
@@ -17,6 +17,11 @@ export class UpstepApiClient {
|
|
|
17
17
|
if (config.userId !== undefined) this.userId = config.userId;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Update the end-user id at runtime (e.g. after the user logs in). */
|
|
21
|
+
setUserId(userId: string | undefined) {
|
|
22
|
+
this.userId = userId;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
private headers(): Record<string, string> {
|
|
21
26
|
return { "Content-Type": "application/json", "x-api-key": this.apiKey };
|
|
22
27
|
}
|