@appsupportkit/support-sdk 0.1.2 → 0.2.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.
Files changed (94) hide show
  1. package/lib/commonjs/components/BottomSheet.js +67 -47
  2. package/lib/commonjs/components/BottomSheet.js.map +1 -1
  3. package/lib/commonjs/components/LauncherButton.js +177 -0
  4. package/lib/commonjs/components/LauncherButton.js.map +1 -0
  5. package/lib/commonjs/components/SupportButton.js +54 -176
  6. package/lib/commonjs/components/SupportButton.js.map +1 -1
  7. package/lib/commonjs/components/icons.js +74 -23
  8. package/lib/commonjs/components/icons.js.map +1 -1
  9. package/lib/commonjs/components/primitives.js +183 -38
  10. package/lib/commonjs/components/primitives.js.map +1 -1
  11. package/lib/commonjs/components/screens/ComposeScreen.js +115 -58
  12. package/lib/commonjs/components/screens/ComposeScreen.js.map +1 -1
  13. package/lib/commonjs/components/screens/HomeScreen.js +170 -115
  14. package/lib/commonjs/components/screens/HomeScreen.js.map +1 -1
  15. package/lib/commonjs/components/screens/ThreadScreen.js +222 -108
  16. package/lib/commonjs/components/screens/ThreadScreen.js.map +1 -1
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/commonjs/theme.js +136 -17
  19. package/lib/commonjs/theme.js.map +1 -1
  20. package/lib/commonjs/types.js +7 -0
  21. package/lib/commonjs/types.js.map +1 -1
  22. package/lib/commonjs/unread.js +92 -0
  23. package/lib/commonjs/unread.js.map +1 -0
  24. package/lib/commonjs/useSupportThreads.js +286 -0
  25. package/lib/commonjs/useSupportThreads.js.map +1 -0
  26. package/lib/commonjs/utils.js +33 -0
  27. package/lib/commonjs/utils.js.map +1 -1
  28. package/lib/module/components/BottomSheet.js +68 -48
  29. package/lib/module/components/BottomSheet.js.map +1 -1
  30. package/lib/module/components/LauncherButton.js +172 -0
  31. package/lib/module/components/LauncherButton.js.map +1 -0
  32. package/lib/module/components/SupportButton.js +57 -179
  33. package/lib/module/components/SupportButton.js.map +1 -1
  34. package/lib/module/components/icons.js +72 -22
  35. package/lib/module/components/icons.js.map +1 -1
  36. package/lib/module/components/primitives.js +179 -39
  37. package/lib/module/components/primitives.js.map +1 -1
  38. package/lib/module/components/screens/ComposeScreen.js +118 -61
  39. package/lib/module/components/screens/ComposeScreen.js.map +1 -1
  40. package/lib/module/components/screens/HomeScreen.js +172 -117
  41. package/lib/module/components/screens/HomeScreen.js.map +1 -1
  42. package/lib/module/components/screens/ThreadScreen.js +228 -112
  43. package/lib/module/components/screens/ThreadScreen.js.map +1 -1
  44. package/lib/module/index.js.map +1 -1
  45. package/lib/module/theme.js +135 -16
  46. package/lib/module/theme.js.map +1 -1
  47. package/lib/module/types.js +7 -0
  48. package/lib/module/types.js.map +1 -1
  49. package/lib/module/unread.js +83 -0
  50. package/lib/module/unread.js.map +1 -0
  51. package/lib/module/useSupportThreads.js +281 -0
  52. package/lib/module/useSupportThreads.js.map +1 -0
  53. package/lib/module/utils.js +30 -0
  54. package/lib/module/utils.js.map +1 -1
  55. package/lib/typescript/components/BottomSheet.d.ts.map +1 -1
  56. package/lib/typescript/components/LauncherButton.d.ts +14 -0
  57. package/lib/typescript/components/LauncherButton.d.ts.map +1 -0
  58. package/lib/typescript/components/SupportButton.d.ts +3 -3
  59. package/lib/typescript/components/SupportButton.d.ts.map +1 -1
  60. package/lib/typescript/components/icons.d.ts +13 -2
  61. package/lib/typescript/components/icons.d.ts.map +1 -1
  62. package/lib/typescript/components/primitives.d.ts +35 -4
  63. package/lib/typescript/components/primitives.d.ts.map +1 -1
  64. package/lib/typescript/components/screens/ComposeScreen.d.ts.map +1 -1
  65. package/lib/typescript/components/screens/HomeScreen.d.ts +3 -2
  66. package/lib/typescript/components/screens/HomeScreen.d.ts.map +1 -1
  67. package/lib/typescript/components/screens/ThreadScreen.d.ts.map +1 -1
  68. package/lib/typescript/index.d.ts +1 -1
  69. package/lib/typescript/index.d.ts.map +1 -1
  70. package/lib/typescript/theme.d.ts +44 -3
  71. package/lib/typescript/theme.d.ts.map +1 -1
  72. package/lib/typescript/types.d.ts +22 -0
  73. package/lib/typescript/types.d.ts.map +1 -1
  74. package/lib/typescript/unread.d.ts +22 -0
  75. package/lib/typescript/unread.d.ts.map +1 -0
  76. package/lib/typescript/useSupportThreads.d.ts +28 -0
  77. package/lib/typescript/useSupportThreads.d.ts.map +1 -0
  78. package/lib/typescript/utils.d.ts +5 -0
  79. package/lib/typescript/utils.d.ts.map +1 -1
  80. package/package.json +2 -2
  81. package/src/components/BottomSheet.tsx +61 -43
  82. package/src/components/LauncherButton.tsx +217 -0
  83. package/src/components/SupportButton.tsx +52 -196
  84. package/src/components/icons.tsx +77 -19
  85. package/src/components/primitives.tsx +217 -36
  86. package/src/components/screens/ComposeScreen.tsx +131 -61
  87. package/src/components/screens/HomeScreen.tsx +168 -117
  88. package/src/components/screens/ThreadScreen.tsx +289 -119
  89. package/src/index.tsx +1 -0
  90. package/src/theme.ts +165 -18
  91. package/src/types.ts +24 -0
  92. package/src/unread.ts +91 -0
  93. package/src/useSupportThreads.ts +372 -0
  94. package/src/utils.ts +33 -0
@@ -1,4 +1,4 @@
1
- import { ScrollView, Text, View } from 'react-native';
1
+ import { RefreshControl, ScrollView, Text, View } from 'react-native';
2
2
  import type { SupportTheme } from '../../theme';
3
3
  import type { QueryType, Thread } from '../../types';
4
4
  import { QUERY_TYPE_LABEL } from '../../types';
@@ -7,84 +7,149 @@ import { Chevron, CloseIcon } from '../icons';
7
7
  import {
8
8
  EmptyState,
9
9
  ErrorState,
10
+ IconButton,
10
11
  PressableRow,
12
+ SectionLabel,
11
13
  SkeletonBlock,
12
14
  textStyles,
13
- typeColor,
14
- typeIcon,
15
+ TypeGlyph,
16
+ UnreadPill,
15
17
  } from '../primitives';
16
18
 
17
19
  const COMPOSE_TYPES: QueryType[] = ['feedback', 'feature_request', 'bug_report'];
18
20
 
21
+ const COMPOSE_HINT: Record<QueryType, string> = {
22
+ feedback: 'Tell us what you think',
23
+ feature_request: 'Suggest an improvement',
24
+ bug_report: "Something isn't working",
25
+ };
26
+
19
27
  function ThreadRow({
20
28
  thread,
21
29
  theme,
22
30
  onPress,
31
+ last,
23
32
  }: {
24
33
  thread: Thread;
25
34
  theme: SupportTheme;
26
35
  onPress: () => void;
36
+ last: boolean;
27
37
  }) {
28
- const preview = thread.last_message?.body ?? '';
38
+ const unread = thread.unread_count > 0;
39
+ const preview = thread.last_message?.body ?? 'No messages yet';
40
+ const at = thread.last_message?.created_at ?? thread.created_at;
41
+
29
42
  return (
30
43
  <PressableRow
31
44
  onPress={onPress}
32
45
  theme={theme}
33
- accessibilityLabel={`${QUERY_TYPE_LABEL[thread.type]} thread`}
46
+ accessibilityLabel={
47
+ unread
48
+ ? `${QUERY_TYPE_LABEL[thread.type]} thread, ${thread.unread_count} unread`
49
+ : `${QUERY_TYPE_LABEL[thread.type]} thread`
50
+ }
34
51
  style={{
52
+ flexDirection: 'row',
53
+ alignItems: 'center',
54
+ gap: 12,
35
55
  paddingHorizontal: 16,
36
- paddingVertical: 10,
37
- borderBottomWidth: 1,
56
+ paddingVertical: 13,
57
+ borderBottomWidth: last ? 0 : 1,
38
58
  borderBottomColor: theme.colors.border,
59
+ opacity: thread.status === 'resolved' ? 0.75 : 1,
39
60
  }}
40
61
  >
41
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
42
- {thread.unread_count > 0 && (
43
- <View
62
+ <TypeGlyph type={thread.type} theme={theme} size={38} />
63
+
64
+ <View style={{ flex: 1, gap: 3 }}>
65
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
66
+ <Text
67
+ numberOfLines={1}
44
68
  style={{
45
- width: 6,
46
- height: 6,
47
- borderRadius: 3,
48
- backgroundColor: theme.colors.accent,
69
+ flex: 1,
70
+ fontSize: 14,
71
+ fontWeight: unread ? '700' : '600',
72
+ letterSpacing: -0.1,
73
+ color: theme.colors.textPrimary,
49
74
  }}
50
- />
51
- )}
52
- <Text style={{ fontSize: 11, fontWeight: '500', color: typeColor(thread.type, theme) }}>
53
- {QUERY_TYPE_LABEL[thread.type]}
54
- </Text>
55
- <Text style={[textStyles.meta(theme), { marginLeft: 'auto' }]}>
56
- {thread.last_message
57
- ? relativeTime(thread.last_message.created_at)
58
- : relativeTime(thread.created_at)}
59
- </Text>
60
- </View>
61
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 3 }}>
62
- <Text
63
- numberOfLines={1}
64
- style={{
65
- flex: 1,
66
- fontSize: 13,
67
- color:
68
- thread.unread_count > 0
69
- ? theme.colors.textPrimary
70
- : theme.colors.textSecondary,
71
- fontWeight: thread.unread_count > 0 ? '500' : '400',
72
- }}
73
- >
74
- {preview}
75
- </Text>
76
- <Chevron color={theme.colors.textTertiary} />
75
+ >
76
+ {QUERY_TYPE_LABEL[thread.type]}
77
+ </Text>
78
+ <Text style={textStyles.meta(theme)}>{relativeTime(at)}</Text>
79
+ </View>
80
+
81
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
82
+ <Text
83
+ numberOfLines={1}
84
+ style={{
85
+ flex: 1,
86
+ fontSize: 13,
87
+ lineHeight: 18,
88
+ color: unread ? theme.colors.textPrimary : theme.colors.textSecondary,
89
+ }}
90
+ >
91
+ {thread.last_message?.sender === 'user' ? 'You: ' : ''}
92
+ {preview}
93
+ </Text>
94
+ {unread ? (
95
+ <UnreadPill count={thread.unread_count} theme={theme} />
96
+ ) : (
97
+ <Chevron color={theme.colors.textTertiary} />
98
+ )}
99
+ </View>
77
100
  </View>
78
101
  </PressableRow>
79
102
  );
80
103
  }
81
104
 
105
+ function ThreadGroup({
106
+ label,
107
+ threads,
108
+ theme,
109
+ onOpenThread,
110
+ }: {
111
+ label: string;
112
+ threads: Thread[];
113
+ theme: SupportTheme;
114
+ onOpenThread: (thread: Thread) => void;
115
+ }) {
116
+ if (threads.length === 0) return null;
117
+ return (
118
+ <View style={{ marginTop: 24 }}>
119
+ <SectionLabel theme={theme} style={{ paddingHorizontal: 16, paddingBottom: 8 }}>
120
+ {label}
121
+ </SectionLabel>
122
+ <View
123
+ style={{
124
+ marginHorizontal: 16,
125
+ borderWidth: 1,
126
+ borderColor: theme.colors.border,
127
+ borderRadius: theme.radii.card,
128
+ backgroundColor: theme.colors.surface,
129
+ overflow: 'hidden',
130
+ }}
131
+ >
132
+ {threads.map((thread, i) => (
133
+ <ThreadRow
134
+ key={thread.id}
135
+ thread={thread}
136
+ theme={theme}
137
+ onPress={() => onOpenThread(thread)}
138
+ last={i === threads.length - 1}
139
+ />
140
+ ))}
141
+ </View>
142
+ </View>
143
+ );
144
+ }
145
+
82
146
  export function HomeScreen({
83
147
  theme,
84
148
  threads,
85
149
  loading,
150
+ refreshing,
86
151
  error,
87
- onRetry,
152
+ onRefresh,
88
153
  onCompose,
89
154
  onOpenThread,
90
155
  onClose,
@@ -92,8 +157,9 @@ export function HomeScreen({
92
157
  theme: SupportTheme;
93
158
  threads: Thread[] | null;
94
159
  loading: boolean;
160
+ refreshing: boolean;
95
161
  error: string | null;
96
- onRetry: () => void;
162
+ onRefresh: () => void;
97
163
  onCompose: (type: QueryType) => void;
98
164
  onOpenThread: (thread: Thread) => void;
99
165
  onClose: () => void;
@@ -107,23 +173,42 @@ export function HomeScreen({
107
173
  <View
108
174
  style={{
109
175
  flexDirection: 'row',
110
- alignItems: 'center',
176
+ alignItems: 'flex-start',
111
177
  paddingHorizontal: 16,
112
- paddingBottom: 12,
178
+ paddingTop: 4,
179
+ paddingBottom: 16,
113
180
  }}
114
181
  >
115
- <Text style={textStyles.title(theme)}>Support</Text>
116
- <PressableRow
182
+ <View style={{ flex: 1, gap: 2 }}>
183
+ <Text style={textStyles.title(theme)}>Support</Text>
184
+ <Text style={textStyles.secondary(theme)}>
185
+ Start a conversation — we reply right here.
186
+ </Text>
187
+ </View>
188
+ <IconButton
117
189
  onPress={onClose}
118
190
  theme={theme}
119
- accessibilityLabel="Close"
120
- style={{ marginLeft: 'auto', padding: 6, borderRadius: theme.radii.control }}
191
+ accessibilityLabel="Close support"
192
+ size={32}
121
193
  >
122
- <CloseIcon color={theme.colors.textSecondary} size={13} />
123
- </PressableRow>
194
+ <CloseIcon color={theme.colors.textSecondary} size={12} />
195
+ </IconButton>
124
196
  </View>
125
197
 
126
- <ScrollView style={{ flex: 1 }}>
198
+ <ScrollView
199
+ style={{ flex: 1 }}
200
+ contentContainerStyle={{ paddingBottom: 24 }}
201
+ keyboardShouldPersistTaps="handled"
202
+ refreshControl={
203
+ <RefreshControl
204
+ refreshing={refreshing}
205
+ onRefresh={onRefresh}
206
+ tintColor={theme.colors.textTertiary}
207
+ colors={[theme.colors.accent]}
208
+ progressBackgroundColor={theme.colors.surface}
209
+ />
210
+ }
211
+ >
127
212
  {/* Compose actions */}
128
213
  <View
129
214
  style={{
@@ -144,90 +229,56 @@ export function HomeScreen({
144
229
  style={{
145
230
  flexDirection: 'row',
146
231
  alignItems: 'center',
147
- gap: 10,
232
+ gap: 12,
148
233
  paddingHorizontal: 14,
149
234
  paddingVertical: 12,
150
235
  borderTopWidth: i === 0 ? 0 : 1,
151
236
  borderTopColor: theme.colors.border,
152
237
  }}
153
238
  >
154
- {typeIcon(type, typeColor(type, theme))}
155
- <Text style={{ fontSize: 14, color: theme.colors.textPrimary, flex: 1 }}>
156
- {QUERY_TYPE_LABEL[type]}
157
- </Text>
239
+ <TypeGlyph type={type} theme={theme} size={34} />
240
+ <View style={{ flex: 1, gap: 1 }}>
241
+ <Text style={textStyles.heading(theme)}>
242
+ {QUERY_TYPE_LABEL[type]}
243
+ </Text>
244
+ <Text style={textStyles.meta(theme)}>{COMPOSE_HINT[type]}</Text>
245
+ </View>
158
246
  <Chevron color={theme.colors.textTertiary} />
159
247
  </PressableRow>
160
248
  ))}
161
249
  </View>
162
250
 
163
251
  {/* Threads */}
164
- {error && threads === null ? (
252
+ {error !== null && threads === null ? (
165
253
  // Only take over the screen when there's nothing cached to show —
166
254
  // a failed background poll shouldn't hide an already-loaded list.
167
- <ErrorState message={error} onRetry={onRetry} theme={theme} />
255
+ <ErrorState message={error} onRetry={onRefresh} theme={theme} />
168
256
  ) : loading && threads === null ? (
169
- <View style={{ paddingHorizontal: 16, paddingTop: 24, gap: 14 }}>
170
- <SkeletonBlock width={80} height={12} theme={theme} />
171
- <SkeletonBlock width="100%" height={40} theme={theme} />
172
- <SkeletonBlock width="100%" height={40} theme={theme} />
257
+ <View style={{ paddingHorizontal: 16, paddingTop: 28, gap: 12 }}>
258
+ <SkeletonBlock width={110} height={11} theme={theme} radius={4} />
259
+ <SkeletonBlock width="100%" height={64} theme={theme} radius={theme.radii.card} />
260
+ <SkeletonBlock width="100%" height={64} theme={theme} radius={theme.radii.card} />
173
261
  </View>
174
262
  ) : (threads ?? []).length === 0 ? (
175
- <EmptyState message="No queries yet" theme={theme} />
263
+ <EmptyState
264
+ message="No conversations yet"
265
+ hint="Anything you send lands with the team, and their replies come back to this sheet."
266
+ theme={theme}
267
+ />
176
268
  ) : (
177
269
  <>
178
- {open.length > 0 && (
179
- <>
180
- <Text
181
- style={{
182
- fontSize: 11,
183
- fontWeight: '500',
184
- textTransform: 'uppercase',
185
- letterSpacing: 0.4,
186
- color: theme.colors.textTertiary,
187
- paddingHorizontal: 16,
188
- paddingTop: 20,
189
- paddingBottom: 6,
190
- }}
191
- >
192
- Open
193
- </Text>
194
- {open.map((t) => (
195
- <ThreadRow
196
- key={t.id}
197
- thread={t}
198
- theme={theme}
199
- onPress={() => onOpenThread(t)}
200
- />
201
- ))}
202
- </>
203
- )}
204
- {resolved.length > 0 && (
205
- <>
206
- <Text
207
- style={{
208
- fontSize: 11,
209
- fontWeight: '500',
210
- textTransform: 'uppercase',
211
- letterSpacing: 0.4,
212
- color: theme.colors.textTertiary,
213
- paddingHorizontal: 16,
214
- paddingTop: 20,
215
- paddingBottom: 6,
216
- }}
217
- >
218
- Resolved
219
- </Text>
220
- {resolved.map((t) => (
221
- <ThreadRow
222
- key={t.id}
223
- thread={t}
224
- theme={theme}
225
- onPress={() => onOpenThread(t)}
226
- />
227
- ))}
228
- </>
229
- )}
230
- <View style={{ height: 16 }} />
270
+ <ThreadGroup
271
+ label={`Open · ${open.length}`}
272
+ threads={open}
273
+ theme={theme}
274
+ onOpenThread={onOpenThread}
275
+ />
276
+ <ThreadGroup
277
+ label="Resolved"
278
+ threads={resolved}
279
+ theme={theme}
280
+ onOpenThread={onOpenThread}
281
+ />
231
282
  </>
232
283
  )}
233
284
  </ScrollView>