@appsupportkit/support-sdk 0.2.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.
Files changed (242) hide show
  1. package/lib/commonjs/api.js +61 -12
  2. package/lib/commonjs/api.js.map +1 -1
  3. package/lib/commonjs/components/BottomSheet.js +1 -1
  4. package/lib/commonjs/components/BottomSheet.js.map +1 -1
  5. package/lib/commonjs/components/LauncherButton.js +4 -4
  6. package/lib/commonjs/components/LauncherButton.js.map +1 -1
  7. package/lib/commonjs/components/SupportButton.js +192 -66
  8. package/lib/commonjs/components/SupportButton.js.map +1 -1
  9. package/lib/commonjs/components/icons.js +117 -110
  10. package/lib/commonjs/components/icons.js.map +1 -1
  11. package/lib/commonjs/components/primitives.js +3 -154
  12. package/lib/commonjs/components/primitives.js.map +1 -1
  13. package/lib/commonjs/components/screens/ChatScreen.js +487 -0
  14. package/lib/commonjs/components/screens/ChatScreen.js.map +1 -0
  15. package/lib/commonjs/components/screens/LanguageScreen.js +150 -0
  16. package/lib/commonjs/components/screens/LanguageScreen.js.map +1 -0
  17. package/lib/commonjs/i18n/index.js +101 -0
  18. package/lib/commonjs/i18n/index.js.map +1 -0
  19. package/lib/commonjs/i18n/languages.js +95 -0
  20. package/lib/commonjs/i18n/languages.js.map +1 -0
  21. package/lib/commonjs/i18n/strings/de.js +75 -0
  22. package/lib/commonjs/i18n/strings/de.js.map +1 -0
  23. package/lib/commonjs/i18n/strings/en.js +91 -0
  24. package/lib/commonjs/i18n/strings/en.js.map +1 -0
  25. package/lib/commonjs/i18n/strings/es.js +75 -0
  26. package/lib/commonjs/i18n/strings/es.js.map +1 -0
  27. package/lib/commonjs/i18n/strings/fr.js +75 -0
  28. package/lib/commonjs/i18n/strings/fr.js.map +1 -0
  29. package/lib/commonjs/i18n/strings/hi.js +75 -0
  30. package/lib/commonjs/i18n/strings/hi.js.map +1 -0
  31. package/lib/commonjs/i18n/strings/it.js +75 -0
  32. package/lib/commonjs/i18n/strings/it.js.map +1 -0
  33. package/lib/commonjs/i18n/strings/ja.js +75 -0
  34. package/lib/commonjs/i18n/strings/ja.js.map +1 -0
  35. package/lib/commonjs/i18n/strings/ko.js +75 -0
  36. package/lib/commonjs/i18n/strings/ko.js.map +1 -0
  37. package/lib/commonjs/i18n/strings/nl.js +75 -0
  38. package/lib/commonjs/i18n/strings/nl.js.map +1 -0
  39. package/lib/commonjs/i18n/strings/pl.js +75 -0
  40. package/lib/commonjs/i18n/strings/pl.js.map +1 -0
  41. package/lib/commonjs/i18n/strings/pt.js +75 -0
  42. package/lib/commonjs/i18n/strings/pt.js.map +1 -0
  43. package/lib/commonjs/i18n/strings/ru.js +75 -0
  44. package/lib/commonjs/i18n/strings/ru.js.map +1 -0
  45. package/lib/commonjs/i18n/strings/tr.js +75 -0
  46. package/lib/commonjs/i18n/strings/tr.js.map +1 -0
  47. package/lib/commonjs/i18n/strings/zh.js +75 -0
  48. package/lib/commonjs/i18n/strings/zh.js.map +1 -0
  49. package/lib/commonjs/index.js +38 -0
  50. package/lib/commonjs/index.js.map +1 -1
  51. package/lib/commonjs/push.js +78 -3
  52. package/lib/commonjs/push.js.map +1 -1
  53. package/lib/commonjs/review.js +54 -0
  54. package/lib/commonjs/review.js.map +1 -0
  55. package/lib/commonjs/theme.js +142 -23
  56. package/lib/commonjs/theme.js.map +1 -1
  57. package/lib/commonjs/types.js +32 -3
  58. package/lib/commonjs/types.js.map +1 -1
  59. package/lib/commonjs/useSupportThreads.js +41 -24
  60. package/lib/commonjs/useSupportThreads.js.map +1 -1
  61. package/lib/commonjs/utils.js +25 -30
  62. package/lib/commonjs/utils.js.map +1 -1
  63. package/lib/commonjs/version.js +13 -0
  64. package/lib/commonjs/version.js.map +1 -0
  65. package/lib/module/api.js +57 -10
  66. package/lib/module/api.js.map +1 -1
  67. package/lib/module/components/BottomSheet.js +1 -1
  68. package/lib/module/components/BottomSheet.js.map +1 -1
  69. package/lib/module/components/LauncherButton.js +5 -5
  70. package/lib/module/components/LauncherButton.js.map +1 -1
  71. package/lib/module/components/SupportButton.js +199 -73
  72. package/lib/module/components/SupportButton.js.map +1 -1
  73. package/lib/module/components/icons.js +115 -107
  74. package/lib/module/components/icons.js.map +1 -1
  75. package/lib/module/components/primitives.js +5 -147
  76. package/lib/module/components/primitives.js.map +1 -1
  77. package/lib/module/components/screens/ChatScreen.js +485 -0
  78. package/lib/module/components/screens/ChatScreen.js.map +1 -0
  79. package/lib/module/components/screens/LanguageScreen.js +147 -0
  80. package/lib/module/components/screens/LanguageScreen.js.map +1 -0
  81. package/lib/module/i18n/index.js +56 -0
  82. package/lib/module/i18n/index.js.map +1 -0
  83. package/lib/module/i18n/languages.js +88 -0
  84. package/lib/module/i18n/languages.js.map +1 -0
  85. package/lib/module/i18n/strings/de.js +71 -0
  86. package/lib/module/i18n/strings/de.js.map +1 -0
  87. package/lib/module/i18n/strings/en.js +87 -0
  88. package/lib/module/i18n/strings/en.js.map +1 -0
  89. package/lib/module/i18n/strings/es.js +71 -0
  90. package/lib/module/i18n/strings/es.js.map +1 -0
  91. package/lib/module/i18n/strings/fr.js +71 -0
  92. package/lib/module/i18n/strings/fr.js.map +1 -0
  93. package/lib/module/i18n/strings/hi.js +71 -0
  94. package/lib/module/i18n/strings/hi.js.map +1 -0
  95. package/lib/module/i18n/strings/it.js +71 -0
  96. package/lib/module/i18n/strings/it.js.map +1 -0
  97. package/lib/module/i18n/strings/ja.js +71 -0
  98. package/lib/module/i18n/strings/ja.js.map +1 -0
  99. package/lib/module/i18n/strings/ko.js +71 -0
  100. package/lib/module/i18n/strings/ko.js.map +1 -0
  101. package/lib/module/i18n/strings/nl.js +71 -0
  102. package/lib/module/i18n/strings/nl.js.map +1 -0
  103. package/lib/module/i18n/strings/pl.js +71 -0
  104. package/lib/module/i18n/strings/pl.js.map +1 -0
  105. package/lib/module/i18n/strings/pt.js +71 -0
  106. package/lib/module/i18n/strings/pt.js.map +1 -0
  107. package/lib/module/i18n/strings/ru.js +71 -0
  108. package/lib/module/i18n/strings/ru.js.map +1 -0
  109. package/lib/module/i18n/strings/tr.js +71 -0
  110. package/lib/module/i18n/strings/tr.js.map +1 -0
  111. package/lib/module/i18n/strings/zh.js +71 -0
  112. package/lib/module/i18n/strings/zh.js.map +1 -0
  113. package/lib/module/index.js +2 -0
  114. package/lib/module/index.js.map +1 -1
  115. package/lib/module/push.js +77 -3
  116. package/lib/module/push.js.map +1 -1
  117. package/lib/module/review.js +48 -0
  118. package/lib/module/review.js.map +1 -0
  119. package/lib/module/theme.js +141 -23
  120. package/lib/module/theme.js.map +1 -1
  121. package/lib/module/types.js +32 -3
  122. package/lib/module/types.js.map +1 -1
  123. package/lib/module/useSupportThreads.js +41 -24
  124. package/lib/module/useSupportThreads.js.map +1 -1
  125. package/lib/module/utils.js +24 -29
  126. package/lib/module/utils.js.map +1 -1
  127. package/lib/module/version.js +9 -0
  128. package/lib/module/version.js.map +1 -0
  129. package/lib/typescript/api.d.ts +40 -10
  130. package/lib/typescript/api.d.ts.map +1 -1
  131. package/lib/typescript/components/SupportButton.d.ts +2 -2
  132. package/lib/typescript/components/SupportButton.d.ts.map +1 -1
  133. package/lib/typescript/components/icons.d.ts +10 -14
  134. package/lib/typescript/components/icons.d.ts.map +1 -1
  135. package/lib/typescript/components/primitives.d.ts +3 -30
  136. package/lib/typescript/components/primitives.d.ts.map +1 -1
  137. package/lib/typescript/components/screens/ChatScreen.d.ts +21 -0
  138. package/lib/typescript/components/screens/ChatScreen.d.ts.map +1 -0
  139. package/lib/typescript/components/screens/LanguageScreen.d.ts +30 -0
  140. package/lib/typescript/components/screens/LanguageScreen.d.ts.map +1 -0
  141. package/lib/typescript/i18n/index.d.ts +15 -0
  142. package/lib/typescript/i18n/index.d.ts.map +1 -0
  143. package/lib/typescript/i18n/languages.d.ts +36 -0
  144. package/lib/typescript/i18n/languages.d.ts.map +1 -0
  145. package/lib/typescript/i18n/strings/de.d.ts +3 -0
  146. package/lib/typescript/i18n/strings/de.d.ts.map +1 -0
  147. package/lib/typescript/i18n/strings/en.d.ts +86 -0
  148. package/lib/typescript/i18n/strings/en.d.ts.map +1 -0
  149. package/lib/typescript/i18n/strings/es.d.ts +3 -0
  150. package/lib/typescript/i18n/strings/es.d.ts.map +1 -0
  151. package/lib/typescript/i18n/strings/fr.d.ts +3 -0
  152. package/lib/typescript/i18n/strings/fr.d.ts.map +1 -0
  153. package/lib/typescript/i18n/strings/hi.d.ts +3 -0
  154. package/lib/typescript/i18n/strings/hi.d.ts.map +1 -0
  155. package/lib/typescript/i18n/strings/it.d.ts +3 -0
  156. package/lib/typescript/i18n/strings/it.d.ts.map +1 -0
  157. package/lib/typescript/i18n/strings/ja.d.ts +3 -0
  158. package/lib/typescript/i18n/strings/ja.d.ts.map +1 -0
  159. package/lib/typescript/i18n/strings/ko.d.ts +3 -0
  160. package/lib/typescript/i18n/strings/ko.d.ts.map +1 -0
  161. package/lib/typescript/i18n/strings/nl.d.ts +3 -0
  162. package/lib/typescript/i18n/strings/nl.d.ts.map +1 -0
  163. package/lib/typescript/i18n/strings/pl.d.ts +3 -0
  164. package/lib/typescript/i18n/strings/pl.d.ts.map +1 -0
  165. package/lib/typescript/i18n/strings/pt.d.ts +3 -0
  166. package/lib/typescript/i18n/strings/pt.d.ts.map +1 -0
  167. package/lib/typescript/i18n/strings/ru.d.ts +3 -0
  168. package/lib/typescript/i18n/strings/ru.d.ts.map +1 -0
  169. package/lib/typescript/i18n/strings/tr.d.ts +3 -0
  170. package/lib/typescript/i18n/strings/tr.d.ts.map +1 -0
  171. package/lib/typescript/i18n/strings/zh.d.ts +3 -0
  172. package/lib/typescript/i18n/strings/zh.d.ts.map +1 -0
  173. package/lib/typescript/index.d.ts +4 -2
  174. package/lib/typescript/index.d.ts.map +1 -1
  175. package/lib/typescript/push.d.ts +14 -2
  176. package/lib/typescript/push.d.ts.map +1 -1
  177. package/lib/typescript/review.d.ts +15 -0
  178. package/lib/typescript/review.d.ts.map +1 -0
  179. package/lib/typescript/theme.d.ts +23 -5
  180. package/lib/typescript/theme.d.ts.map +1 -1
  181. package/lib/typescript/types.d.ts +86 -13
  182. package/lib/typescript/types.d.ts.map +1 -1
  183. package/lib/typescript/useSupportThreads.d.ts +20 -4
  184. package/lib/typescript/useSupportThreads.d.ts.map +1 -1
  185. package/lib/typescript/utils.d.ts +8 -6
  186. package/lib/typescript/utils.d.ts.map +1 -1
  187. package/lib/typescript/version.d.ts +7 -0
  188. package/lib/typescript/version.d.ts.map +1 -0
  189. package/package.json +2 -2
  190. package/src/api.ts +75 -23
  191. package/src/components/BottomSheet.tsx +1 -1
  192. package/src/components/LauncherButton.tsx +4 -4
  193. package/src/components/SupportButton.tsx +244 -86
  194. package/src/components/icons.tsx +96 -101
  195. package/src/components/primitives.tsx +9 -161
  196. package/src/components/screens/ChatScreen.tsx +541 -0
  197. package/src/components/screens/LanguageScreen.tsx +172 -0
  198. package/src/i18n/index.ts +81 -0
  199. package/src/i18n/languages.ts +106 -0
  200. package/src/i18n/strings/de.ts +81 -0
  201. package/src/i18n/strings/en.ts +97 -0
  202. package/src/i18n/strings/es.ts +80 -0
  203. package/src/i18n/strings/fr.ts +81 -0
  204. package/src/i18n/strings/hi.ts +80 -0
  205. package/src/i18n/strings/it.ts +80 -0
  206. package/src/i18n/strings/ja.ts +80 -0
  207. package/src/i18n/strings/ko.ts +79 -0
  208. package/src/i18n/strings/nl.ts +80 -0
  209. package/src/i18n/strings/pl.ts +80 -0
  210. package/src/i18n/strings/pt.ts +80 -0
  211. package/src/i18n/strings/ru.ts +80 -0
  212. package/src/i18n/strings/tr.ts +80 -0
  213. package/src/i18n/strings/zh.ts +78 -0
  214. package/src/index.tsx +13 -0
  215. package/src/push.ts +100 -2
  216. package/src/review.ts +54 -0
  217. package/src/theme.ts +182 -28
  218. package/src/types.ts +85 -13
  219. package/src/useSupportThreads.ts +61 -28
  220. package/src/utils.ts +27 -29
  221. package/src/version.ts +6 -0
  222. package/lib/commonjs/components/screens/ComposeScreen.js +0 -228
  223. package/lib/commonjs/components/screens/ComposeScreen.js.map +0 -1
  224. package/lib/commonjs/components/screens/HomeScreen.js +0 -286
  225. package/lib/commonjs/components/screens/HomeScreen.js.map +0 -1
  226. package/lib/commonjs/components/screens/ThreadScreen.js +0 -327
  227. package/lib/commonjs/components/screens/ThreadScreen.js.map +0 -1
  228. package/lib/module/components/screens/ComposeScreen.js +0 -224
  229. package/lib/module/components/screens/ComposeScreen.js.map +0 -1
  230. package/lib/module/components/screens/HomeScreen.js +0 -282
  231. package/lib/module/components/screens/HomeScreen.js.map +0 -1
  232. package/lib/module/components/screens/ThreadScreen.js +0 -325
  233. package/lib/module/components/screens/ThreadScreen.js.map +0 -1
  234. package/lib/typescript/components/screens/ComposeScreen.d.ts +0 -10
  235. package/lib/typescript/components/screens/ComposeScreen.d.ts.map +0 -1
  236. package/lib/typescript/components/screens/HomeScreen.d.ts +0 -14
  237. package/lib/typescript/components/screens/HomeScreen.d.ts.map +0 -1
  238. package/lib/typescript/components/screens/ThreadScreen.d.ts +0 -10
  239. package/lib/typescript/components/screens/ThreadScreen.d.ts.map +0 -1
  240. package/src/components/screens/ComposeScreen.tsx +0 -233
  241. package/src/components/screens/HomeScreen.tsx +0 -287
  242. package/src/components/screens/ThreadScreen.tsx +0 -397
@@ -1,14 +0,0 @@
1
- import type { SupportTheme } from '../../theme';
2
- import type { QueryType, Thread } from '../../types';
3
- export declare function HomeScreen({ theme, threads, loading, refreshing, error, onRefresh, onCompose, onOpenThread, onClose, }: {
4
- theme: SupportTheme;
5
- threads: Thread[] | null;
6
- loading: boolean;
7
- refreshing: boolean;
8
- error: string | null;
9
- onRefresh: () => void;
10
- onCompose: (type: QueryType) => void;
11
- onOpenThread: (thread: Thread) => void;
12
- onClose: () => void;
13
- }): import("react").JSX.Element;
14
- //# sourceMappingURL=HomeScreen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HomeScreen.d.ts","sourceRoot":"","sources":["../../../../src/components/screens/HomeScreen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA+IrD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,OAAO,EACP,OAAO,EACP,UAAU,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,YAAY,EACZ,OAAO,GACR,EAAE;IACD,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAyHA"}
@@ -1,10 +0,0 @@
1
- import type { SupportTheme } from '../../theme';
2
- import type { Thread } from '../../types';
3
- export declare function ThreadScreen({ theme, thread, onBack, onSend, }: {
4
- theme: SupportTheme;
5
- thread: Thread;
6
- onBack: () => void;
7
- /** Resolves null on success, or a user-facing error message. */
8
- onSend: (message: string) => Promise<string | null>;
9
- }): import("react").JSX.Element;
10
- //# sourceMappingURL=ThreadScreen.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ThreadScreen.d.ts","sourceRoot":"","sources":["../../../../src/components/screens/ThreadScreen.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAC;AA2KzD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE;IACD,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACrD,+BA4MA"}
@@ -1,233 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import {
3
- Animated,
4
- Easing,
5
- KeyboardAvoidingView,
6
- Platform,
7
- Text,
8
- TextInput,
9
- View,
10
- } from 'react-native';
11
- import type { SupportTheme } from '../../theme';
12
- import type { QueryType } from '../../types';
13
- import { QUERY_TYPE_LABEL, USER_MESSAGE_MAX, USER_MESSAGE_MIN } from '../../types';
14
- import { CheckIcon, Chevron } from '../icons';
15
- import {
16
- IconButton,
17
- PrimaryButton,
18
- textStyles,
19
- TypeGlyph,
20
- } from '../primitives';
21
-
22
- const PROMPT: Record<QueryType, string> = {
23
- feedback: 'What should we know?',
24
- feature_request: 'What would you like to see?',
25
- bug_report: 'What happened, and what did you expect?',
26
- };
27
-
28
- const SUBTITLE: Record<QueryType, string> = {
29
- feedback: 'Good or bad — it goes straight to the team.',
30
- feature_request: 'Tell us what you are trying to do.',
31
- bug_report: 'Steps, screen, and what you expected all help.',
32
- };
33
-
34
- /** Scales in once on success — the only celebratory motion in the SDK. */
35
- function SentMark({ theme }: { theme: SupportTheme }) {
36
- const scale = useRef(new Animated.Value(0.6)).current;
37
- const opacity = useRef(new Animated.Value(0)).current;
38
-
39
- useEffect(() => {
40
- Animated.parallel([
41
- Animated.timing(scale, {
42
- toValue: 1,
43
- duration: 320,
44
- easing: Easing.bezier(0.16, 1, 0.3, 1),
45
- useNativeDriver: true,
46
- }),
47
- Animated.timing(opacity, {
48
- toValue: 1,
49
- duration: 200,
50
- easing: Easing.out(Easing.cubic),
51
- useNativeDriver: true,
52
- }),
53
- ]).start();
54
- }, [scale, opacity]);
55
-
56
- return (
57
- <Animated.View
58
- style={{
59
- width: 56,
60
- height: 56,
61
- borderRadius: 28,
62
- backgroundColor: theme.colors.statusResolvedBg,
63
- borderWidth: 1,
64
- borderColor: theme.colors.statusResolved,
65
- alignItems: 'center',
66
- justifyContent: 'center',
67
- opacity,
68
- transform: [{ scale }],
69
- }}
70
- >
71
- <CheckIcon color={theme.colors.statusResolved} size={20} />
72
- </Animated.View>
73
- );
74
- }
75
-
76
- export function ComposeScreen({
77
- theme,
78
- type,
79
- onBack,
80
- onSubmit,
81
- }: {
82
- theme: SupportTheme;
83
- type: QueryType;
84
- onBack: () => void;
85
- /** Resolves null on success, or a user-facing error message. */
86
- onSubmit: (message: string) => Promise<string | null>;
87
- }) {
88
- const [message, setMessage] = useState('');
89
- const [submitting, setSubmitting] = useState(false);
90
- const [failedMessage, setFailedMessage] = useState<string | null>(null);
91
- const [sent, setSent] = useState(false);
92
-
93
- const trimmed = message.trim();
94
- const valid =
95
- trimmed.length >= USER_MESSAGE_MIN && trimmed.length <= USER_MESSAGE_MAX;
96
- const overLimit = message.length > USER_MESSAGE_MAX;
97
- const tooShort = trimmed.length > 0 && trimmed.length < USER_MESSAGE_MIN;
98
-
99
- async function handleSubmit() {
100
- if (!valid || submitting) return;
101
- setSubmitting(true);
102
- setFailedMessage(null);
103
- const errorMessage = await onSubmit(trimmed);
104
- setSubmitting(false);
105
- if (errorMessage === null) setSent(true);
106
- else setFailedMessage(errorMessage);
107
- }
108
-
109
- if (sent) {
110
- return (
111
- <View
112
- style={{
113
- flex: 1,
114
- alignItems: 'center',
115
- justifyContent: 'center',
116
- gap: 14,
117
- paddingHorizontal: 32,
118
- paddingBottom: 40,
119
- }}
120
- >
121
- <SentMark theme={theme} />
122
- <Text style={textStyles.title(theme)}>Sent</Text>
123
- <Text style={[textStyles.secondary(theme), { textAlign: 'center' }]}>
124
- We&apos;ll reply right here. You&apos;ll find the conversation under
125
- Open, and the button shows a badge when we answer.
126
- </Text>
127
- <View style={{ marginTop: 6 }}>
128
- <PrimaryButton label="Done" onPress={onBack} theme={theme} />
129
- </View>
130
- </View>
131
- );
132
- }
133
-
134
- return (
135
- <KeyboardAvoidingView
136
- style={{ flex: 1 }}
137
- behavior={Platform.OS === 'ios' ? 'padding' : undefined}
138
- >
139
- {/* Header: back + the chosen type */}
140
- <View
141
- style={{
142
- flexDirection: 'row',
143
- alignItems: 'center',
144
- gap: 10,
145
- paddingHorizontal: 12,
146
- paddingBottom: 14,
147
- }}
148
- >
149
- <IconButton
150
- onPress={onBack}
151
- theme={theme}
152
- accessibilityLabel="Back to conversations"
153
- size={32}
154
- >
155
- <Chevron color={theme.colors.textSecondary} direction="left" size={12} />
156
- </IconButton>
157
- <TypeGlyph type={type} theme={theme} size={32} />
158
- <View style={{ flex: 1, gap: 1 }}>
159
- <Text style={textStyles.heading(theme)}>{QUERY_TYPE_LABEL[type]}</Text>
160
- <Text style={textStyles.meta(theme)}>{SUBTITLE[type]}</Text>
161
- </View>
162
- </View>
163
-
164
- <View style={{ flex: 1, paddingHorizontal: 16 }}>
165
- <TextInput
166
- value={message}
167
- onChangeText={(v) => {
168
- setMessage(v);
169
- setFailedMessage(null);
170
- }}
171
- multiline
172
- autoFocus
173
- editable={!submitting}
174
- placeholder={PROMPT[type]}
175
- placeholderTextColor={theme.colors.textTertiary}
176
- style={{
177
- minHeight: 140,
178
- maxHeight: 240,
179
- borderWidth: 1,
180
- borderColor: theme.colors.border,
181
- borderRadius: theme.radii.card,
182
- backgroundColor: theme.colors.surface,
183
- color: theme.colors.textPrimary,
184
- fontSize: 15,
185
- lineHeight: 21,
186
- padding: 14,
187
- textAlignVertical: 'top',
188
- }}
189
- />
190
-
191
- <View
192
- style={{
193
- flexDirection: 'row',
194
- alignItems: 'center',
195
- gap: 10,
196
- paddingTop: 10,
197
- paddingHorizontal: 2,
198
- minHeight: 20,
199
- }}
200
- >
201
- {failedMessage !== null ? (
202
- <Text style={{ fontSize: 12, color: theme.colors.statusDanger }}>
203
- {failedMessage}
204
- </Text>
205
- ) : tooShort ? (
206
- <Text style={textStyles.meta(theme)}>
207
- At least {USER_MESSAGE_MIN} characters
208
- </Text>
209
- ) : null}
210
- <Text
211
- style={[
212
- textStyles.meta(theme),
213
- { marginLeft: 'auto' },
214
- overLimit && { color: theme.colors.statusDanger },
215
- ]}
216
- >
217
- {message.length}/{USER_MESSAGE_MAX}
218
- </Text>
219
- </View>
220
-
221
- <View style={{ paddingTop: 12 }}>
222
- <PrimaryButton
223
- label={submitting ? 'Sending…' : 'Send'}
224
- onPress={handleSubmit}
225
- disabled={!valid || submitting}
226
- theme={theme}
227
- fullWidth
228
- />
229
- </View>
230
- </View>
231
- </KeyboardAvoidingView>
232
- );
233
- }
@@ -1,287 +0,0 @@
1
- import { RefreshControl, ScrollView, Text, View } from 'react-native';
2
- import type { SupportTheme } from '../../theme';
3
- import type { QueryType, Thread } from '../../types';
4
- import { QUERY_TYPE_LABEL } from '../../types';
5
- import { relativeTime } from '../../utils';
6
- import { Chevron, CloseIcon } from '../icons';
7
- import {
8
- EmptyState,
9
- ErrorState,
10
- IconButton,
11
- PressableRow,
12
- SectionLabel,
13
- SkeletonBlock,
14
- textStyles,
15
- TypeGlyph,
16
- UnreadPill,
17
- } from '../primitives';
18
-
19
- const COMPOSE_TYPES: QueryType[] = ['feedback', 'feature_request', 'bug_report'];
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
-
27
- function ThreadRow({
28
- thread,
29
- theme,
30
- onPress,
31
- last,
32
- }: {
33
- thread: Thread;
34
- theme: SupportTheme;
35
- onPress: () => void;
36
- last: boolean;
37
- }) {
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
-
42
- return (
43
- <PressableRow
44
- onPress={onPress}
45
- theme={theme}
46
- accessibilityLabel={
47
- unread
48
- ? `${QUERY_TYPE_LABEL[thread.type]} thread, ${thread.unread_count} unread`
49
- : `${QUERY_TYPE_LABEL[thread.type]} thread`
50
- }
51
- style={{
52
- flexDirection: 'row',
53
- alignItems: 'center',
54
- gap: 12,
55
- paddingHorizontal: 16,
56
- paddingVertical: 13,
57
- borderBottomWidth: last ? 0 : 1,
58
- borderBottomColor: theme.colors.border,
59
- opacity: thread.status === 'resolved' ? 0.75 : 1,
60
- }}
61
- >
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}
68
- style={{
69
- flex: 1,
70
- fontSize: 14,
71
- fontWeight: unread ? '700' : '600',
72
- letterSpacing: -0.1,
73
- color: theme.colors.textPrimary,
74
- }}
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>
100
- </View>
101
- </PressableRow>
102
- );
103
- }
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
-
146
- export function HomeScreen({
147
- theme,
148
- threads,
149
- loading,
150
- refreshing,
151
- error,
152
- onRefresh,
153
- onCompose,
154
- onOpenThread,
155
- onClose,
156
- }: {
157
- theme: SupportTheme;
158
- threads: Thread[] | null;
159
- loading: boolean;
160
- refreshing: boolean;
161
- error: string | null;
162
- onRefresh: () => void;
163
- onCompose: (type: QueryType) => void;
164
- onOpenThread: (thread: Thread) => void;
165
- onClose: () => void;
166
- }) {
167
- const open = (threads ?? []).filter((t) => t.status === 'open');
168
- const resolved = (threads ?? []).filter((t) => t.status === 'resolved');
169
-
170
- return (
171
- <View style={{ flex: 1 }}>
172
- {/* Header */}
173
- <View
174
- style={{
175
- flexDirection: 'row',
176
- alignItems: 'flex-start',
177
- paddingHorizontal: 16,
178
- paddingTop: 4,
179
- paddingBottom: 16,
180
- }}
181
- >
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
189
- onPress={onClose}
190
- theme={theme}
191
- accessibilityLabel="Close support"
192
- size={32}
193
- >
194
- <CloseIcon color={theme.colors.textSecondary} size={12} />
195
- </IconButton>
196
- </View>
197
-
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
- >
212
- {/* Compose actions */}
213
- <View
214
- style={{
215
- marginHorizontal: 16,
216
- borderWidth: 1,
217
- borderColor: theme.colors.border,
218
- borderRadius: theme.radii.card,
219
- backgroundColor: theme.colors.surface,
220
- overflow: 'hidden',
221
- }}
222
- >
223
- {COMPOSE_TYPES.map((type, i) => (
224
- <PressableRow
225
- key={type}
226
- onPress={() => onCompose(type)}
227
- theme={theme}
228
- accessibilityLabel={`New ${QUERY_TYPE_LABEL[type].toLowerCase()}`}
229
- style={{
230
- flexDirection: 'row',
231
- alignItems: 'center',
232
- gap: 12,
233
- paddingHorizontal: 14,
234
- paddingVertical: 12,
235
- borderTopWidth: i === 0 ? 0 : 1,
236
- borderTopColor: theme.colors.border,
237
- }}
238
- >
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>
246
- <Chevron color={theme.colors.textTertiary} />
247
- </PressableRow>
248
- ))}
249
- </View>
250
-
251
- {/* Threads */}
252
- {error !== null && threads === null ? (
253
- // Only take over the screen when there's nothing cached to show —
254
- // a failed background poll shouldn't hide an already-loaded list.
255
- <ErrorState message={error} onRetry={onRefresh} theme={theme} />
256
- ) : loading && threads === null ? (
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} />
261
- </View>
262
- ) : (threads ?? []).length === 0 ? (
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
- />
268
- ) : (
269
- <>
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
- />
282
- </>
283
- )}
284
- </ScrollView>
285
- </View>
286
- );
287
- }