@appsupportkit/support-sdk 0.1.2 → 0.3.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 (224) hide show
  1. package/lib/commonjs/api.js +17 -2
  2. package/lib/commonjs/api.js.map +1 -1
  3. package/lib/commonjs/components/BottomSheet.js +67 -47
  4. package/lib/commonjs/components/BottomSheet.js.map +1 -1
  5. package/lib/commonjs/components/LauncherButton.js +177 -0
  6. package/lib/commonjs/components/LauncherButton.js.map +1 -0
  7. package/lib/commonjs/components/SupportButton.js +168 -190
  8. package/lib/commonjs/components/SupportButton.js.map +1 -1
  9. package/lib/commonjs/components/icons.js +110 -23
  10. package/lib/commonjs/components/icons.js.map +1 -1
  11. package/lib/commonjs/components/primitives.js +191 -44
  12. package/lib/commonjs/components/primitives.js.map +1 -1
  13. package/lib/commonjs/components/screens/ComposeScreen.js +108 -60
  14. package/lib/commonjs/components/screens/ComposeScreen.js.map +1 -1
  15. package/lib/commonjs/components/screens/HomeScreen.js +221 -118
  16. package/lib/commonjs/components/screens/HomeScreen.js.map +1 -1
  17. package/lib/commonjs/components/screens/LanguageScreen.js +150 -0
  18. package/lib/commonjs/components/screens/LanguageScreen.js.map +1 -0
  19. package/lib/commonjs/components/screens/ThreadScreen.js +237 -111
  20. package/lib/commonjs/components/screens/ThreadScreen.js.map +1 -1
  21. package/lib/commonjs/i18n/index.js +101 -0
  22. package/lib/commonjs/i18n/index.js.map +1 -0
  23. package/lib/commonjs/i18n/languages.js +95 -0
  24. package/lib/commonjs/i18n/languages.js.map +1 -0
  25. package/lib/commonjs/i18n/strings/de.js +72 -0
  26. package/lib/commonjs/i18n/strings/de.js.map +1 -0
  27. package/lib/commonjs/i18n/strings/en.js +88 -0
  28. package/lib/commonjs/i18n/strings/en.js.map +1 -0
  29. package/lib/commonjs/i18n/strings/es.js +72 -0
  30. package/lib/commonjs/i18n/strings/es.js.map +1 -0
  31. package/lib/commonjs/i18n/strings/fr.js +72 -0
  32. package/lib/commonjs/i18n/strings/fr.js.map +1 -0
  33. package/lib/commonjs/i18n/strings/hi.js +72 -0
  34. package/lib/commonjs/i18n/strings/hi.js.map +1 -0
  35. package/lib/commonjs/i18n/strings/it.js +72 -0
  36. package/lib/commonjs/i18n/strings/it.js.map +1 -0
  37. package/lib/commonjs/i18n/strings/ja.js +72 -0
  38. package/lib/commonjs/i18n/strings/ja.js.map +1 -0
  39. package/lib/commonjs/i18n/strings/ko.js +72 -0
  40. package/lib/commonjs/i18n/strings/ko.js.map +1 -0
  41. package/lib/commonjs/i18n/strings/nl.js +72 -0
  42. package/lib/commonjs/i18n/strings/nl.js.map +1 -0
  43. package/lib/commonjs/i18n/strings/pl.js +72 -0
  44. package/lib/commonjs/i18n/strings/pl.js.map +1 -0
  45. package/lib/commonjs/i18n/strings/pt.js +72 -0
  46. package/lib/commonjs/i18n/strings/pt.js.map +1 -0
  47. package/lib/commonjs/i18n/strings/ru.js +72 -0
  48. package/lib/commonjs/i18n/strings/ru.js.map +1 -0
  49. package/lib/commonjs/i18n/strings/tr.js +72 -0
  50. package/lib/commonjs/i18n/strings/tr.js.map +1 -0
  51. package/lib/commonjs/i18n/strings/zh.js +72 -0
  52. package/lib/commonjs/i18n/strings/zh.js.map +1 -0
  53. package/lib/commonjs/index.js +31 -0
  54. package/lib/commonjs/index.js.map +1 -1
  55. package/lib/commonjs/theme.js +136 -17
  56. package/lib/commonjs/theme.js.map +1 -1
  57. package/lib/commonjs/types.js +18 -0
  58. package/lib/commonjs/types.js.map +1 -1
  59. package/lib/commonjs/unread.js +92 -0
  60. package/lib/commonjs/unread.js.map +1 -0
  61. package/lib/commonjs/useSupportThreads.js +296 -0
  62. package/lib/commonjs/useSupportThreads.js.map +1 -0
  63. package/lib/commonjs/utils.js +58 -10
  64. package/lib/commonjs/utils.js.map +1 -1
  65. package/lib/module/api.js +16 -2
  66. package/lib/module/api.js.map +1 -1
  67. package/lib/module/components/BottomSheet.js +68 -48
  68. package/lib/module/components/BottomSheet.js.map +1 -1
  69. package/lib/module/components/LauncherButton.js +172 -0
  70. package/lib/module/components/LauncherButton.js.map +1 -0
  71. package/lib/module/components/SupportButton.js +172 -194
  72. package/lib/module/components/SupportButton.js.map +1 -1
  73. package/lib/module/components/icons.js +107 -22
  74. package/lib/module/components/icons.js.map +1 -1
  75. package/lib/module/components/primitives.js +187 -45
  76. package/lib/module/components/primitives.js.map +1 -1
  77. package/lib/module/components/screens/ComposeScreen.js +114 -64
  78. package/lib/module/components/screens/ComposeScreen.js.map +1 -1
  79. package/lib/module/components/screens/HomeScreen.js +224 -121
  80. package/lib/module/components/screens/HomeScreen.js.map +1 -1
  81. package/lib/module/components/screens/LanguageScreen.js +147 -0
  82. package/lib/module/components/screens/LanguageScreen.js.map +1 -0
  83. package/lib/module/components/screens/ThreadScreen.js +243 -115
  84. package/lib/module/components/screens/ThreadScreen.js.map +1 -1
  85. package/lib/module/i18n/index.js +56 -0
  86. package/lib/module/i18n/index.js.map +1 -0
  87. package/lib/module/i18n/languages.js +88 -0
  88. package/lib/module/i18n/languages.js.map +1 -0
  89. package/lib/module/i18n/strings/de.js +68 -0
  90. package/lib/module/i18n/strings/de.js.map +1 -0
  91. package/lib/module/i18n/strings/en.js +84 -0
  92. package/lib/module/i18n/strings/en.js.map +1 -0
  93. package/lib/module/i18n/strings/es.js +68 -0
  94. package/lib/module/i18n/strings/es.js.map +1 -0
  95. package/lib/module/i18n/strings/fr.js +68 -0
  96. package/lib/module/i18n/strings/fr.js.map +1 -0
  97. package/lib/module/i18n/strings/hi.js +68 -0
  98. package/lib/module/i18n/strings/hi.js.map +1 -0
  99. package/lib/module/i18n/strings/it.js +68 -0
  100. package/lib/module/i18n/strings/it.js.map +1 -0
  101. package/lib/module/i18n/strings/ja.js +68 -0
  102. package/lib/module/i18n/strings/ja.js.map +1 -0
  103. package/lib/module/i18n/strings/ko.js +68 -0
  104. package/lib/module/i18n/strings/ko.js.map +1 -0
  105. package/lib/module/i18n/strings/nl.js +68 -0
  106. package/lib/module/i18n/strings/nl.js.map +1 -0
  107. package/lib/module/i18n/strings/pl.js +68 -0
  108. package/lib/module/i18n/strings/pl.js.map +1 -0
  109. package/lib/module/i18n/strings/pt.js +68 -0
  110. package/lib/module/i18n/strings/pt.js.map +1 -0
  111. package/lib/module/i18n/strings/ru.js +68 -0
  112. package/lib/module/i18n/strings/ru.js.map +1 -0
  113. package/lib/module/i18n/strings/tr.js +68 -0
  114. package/lib/module/i18n/strings/tr.js.map +1 -0
  115. package/lib/module/i18n/strings/zh.js +68 -0
  116. package/lib/module/i18n/strings/zh.js.map +1 -0
  117. package/lib/module/index.js +1 -0
  118. package/lib/module/index.js.map +1 -1
  119. package/lib/module/theme.js +135 -16
  120. package/lib/module/theme.js.map +1 -1
  121. package/lib/module/types.js +18 -0
  122. package/lib/module/types.js.map +1 -1
  123. package/lib/module/unread.js +83 -0
  124. package/lib/module/unread.js.map +1 -0
  125. package/lib/module/useSupportThreads.js +291 -0
  126. package/lib/module/useSupportThreads.js.map +1 -0
  127. package/lib/module/utils.js +55 -10
  128. package/lib/module/utils.js.map +1 -1
  129. package/lib/typescript/api.d.ts +9 -0
  130. package/lib/typescript/api.d.ts.map +1 -1
  131. package/lib/typescript/components/BottomSheet.d.ts.map +1 -1
  132. package/lib/typescript/components/LauncherButton.d.ts +14 -0
  133. package/lib/typescript/components/LauncherButton.d.ts.map +1 -0
  134. package/lib/typescript/components/SupportButton.d.ts +3 -3
  135. package/lib/typescript/components/SupportButton.d.ts.map +1 -1
  136. package/lib/typescript/components/icons.d.ts +18 -2
  137. package/lib/typescript/components/icons.d.ts.map +1 -1
  138. package/lib/typescript/components/primitives.d.ts +42 -7
  139. package/lib/typescript/components/primitives.d.ts.map +1 -1
  140. package/lib/typescript/components/screens/ComposeScreen.d.ts +3 -1
  141. package/lib/typescript/components/screens/ComposeScreen.d.ts.map +1 -1
  142. package/lib/typescript/components/screens/HomeScreen.d.ts +8 -2
  143. package/lib/typescript/components/screens/HomeScreen.d.ts.map +1 -1
  144. package/lib/typescript/components/screens/LanguageScreen.d.ts +30 -0
  145. package/lib/typescript/components/screens/LanguageScreen.d.ts.map +1 -0
  146. package/lib/typescript/components/screens/ThreadScreen.d.ts +3 -1
  147. package/lib/typescript/components/screens/ThreadScreen.d.ts.map +1 -1
  148. package/lib/typescript/i18n/index.d.ts +15 -0
  149. package/lib/typescript/i18n/index.d.ts.map +1 -0
  150. package/lib/typescript/i18n/languages.d.ts +36 -0
  151. package/lib/typescript/i18n/languages.d.ts.map +1 -0
  152. package/lib/typescript/i18n/strings/de.d.ts +3 -0
  153. package/lib/typescript/i18n/strings/de.d.ts.map +1 -0
  154. package/lib/typescript/i18n/strings/en.d.ts +83 -0
  155. package/lib/typescript/i18n/strings/en.d.ts.map +1 -0
  156. package/lib/typescript/i18n/strings/es.d.ts +3 -0
  157. package/lib/typescript/i18n/strings/es.d.ts.map +1 -0
  158. package/lib/typescript/i18n/strings/fr.d.ts +3 -0
  159. package/lib/typescript/i18n/strings/fr.d.ts.map +1 -0
  160. package/lib/typescript/i18n/strings/hi.d.ts +3 -0
  161. package/lib/typescript/i18n/strings/hi.d.ts.map +1 -0
  162. package/lib/typescript/i18n/strings/it.d.ts +3 -0
  163. package/lib/typescript/i18n/strings/it.d.ts.map +1 -0
  164. package/lib/typescript/i18n/strings/ja.d.ts +3 -0
  165. package/lib/typescript/i18n/strings/ja.d.ts.map +1 -0
  166. package/lib/typescript/i18n/strings/ko.d.ts +3 -0
  167. package/lib/typescript/i18n/strings/ko.d.ts.map +1 -0
  168. package/lib/typescript/i18n/strings/nl.d.ts +3 -0
  169. package/lib/typescript/i18n/strings/nl.d.ts.map +1 -0
  170. package/lib/typescript/i18n/strings/pl.d.ts +3 -0
  171. package/lib/typescript/i18n/strings/pl.d.ts.map +1 -0
  172. package/lib/typescript/i18n/strings/pt.d.ts +3 -0
  173. package/lib/typescript/i18n/strings/pt.d.ts.map +1 -0
  174. package/lib/typescript/i18n/strings/ru.d.ts +3 -0
  175. package/lib/typescript/i18n/strings/ru.d.ts.map +1 -0
  176. package/lib/typescript/i18n/strings/tr.d.ts +3 -0
  177. package/lib/typescript/i18n/strings/tr.d.ts.map +1 -0
  178. package/lib/typescript/i18n/strings/zh.d.ts +3 -0
  179. package/lib/typescript/i18n/strings/zh.d.ts.map +1 -0
  180. package/lib/typescript/index.d.ts +2 -1
  181. package/lib/typescript/index.d.ts.map +1 -1
  182. package/lib/typescript/theme.d.ts +44 -3
  183. package/lib/typescript/theme.d.ts.map +1 -1
  184. package/lib/typescript/types.d.ts +54 -2
  185. package/lib/typescript/types.d.ts.map +1 -1
  186. package/lib/typescript/unread.d.ts +22 -0
  187. package/lib/typescript/unread.d.ts.map +1 -0
  188. package/lib/typescript/useSupportThreads.d.ts +38 -0
  189. package/lib/typescript/useSupportThreads.d.ts.map +1 -0
  190. package/lib/typescript/utils.d.ts +8 -2
  191. package/lib/typescript/utils.d.ts.map +1 -1
  192. package/package.json +3 -3
  193. package/src/api.ts +24 -1
  194. package/src/components/BottomSheet.tsx +61 -43
  195. package/src/components/LauncherButton.tsx +217 -0
  196. package/src/components/SupportButton.tsx +195 -210
  197. package/src/components/icons.tsx +113 -19
  198. package/src/components/primitives.tsx +230 -39
  199. package/src/components/screens/ComposeScreen.tsx +129 -63
  200. package/src/components/screens/HomeScreen.tsx +230 -120
  201. package/src/components/screens/LanguageScreen.tsx +172 -0
  202. package/src/components/screens/ThreadScreen.tsx +319 -122
  203. package/src/i18n/index.ts +81 -0
  204. package/src/i18n/languages.ts +106 -0
  205. package/src/i18n/strings/de.ts +78 -0
  206. package/src/i18n/strings/en.ts +94 -0
  207. package/src/i18n/strings/es.ts +77 -0
  208. package/src/i18n/strings/fr.ts +78 -0
  209. package/src/i18n/strings/hi.ts +77 -0
  210. package/src/i18n/strings/it.ts +77 -0
  211. package/src/i18n/strings/ja.ts +77 -0
  212. package/src/i18n/strings/ko.ts +76 -0
  213. package/src/i18n/strings/nl.ts +77 -0
  214. package/src/i18n/strings/pl.ts +77 -0
  215. package/src/i18n/strings/pt.ts +77 -0
  216. package/src/i18n/strings/ru.ts +77 -0
  217. package/src/i18n/strings/tr.ts +77 -0
  218. package/src/i18n/strings/zh.ts +75 -0
  219. package/src/index.tsx +9 -0
  220. package/src/theme.ts +165 -18
  221. package/src/types.ts +56 -2
  222. package/src/unread.ts +91 -0
  223. package/src/useSupportThreads.ts +392 -0
  224. package/src/utils.ts +48 -10
@@ -1,8 +1,17 @@
1
+ import type { StyleProp, ViewStyle } from 'react-native';
2
+ import type { Language } from './i18n/languages';
1
3
  export type QueryType = 'feedback' | 'feature_request' | 'bug_report';
2
4
  export type QueryStatus = 'open' | 'resolved';
3
5
  export type MessageSender = 'user' | 'admin';
4
6
  export type ThemeName = 'dark' | 'light';
5
7
  export type PushPermissionStatus = 'undetermined' | 'granted' | 'denied';
8
+ /**
9
+ * How the launcher renders:
10
+ * solid — accent fill, elevated. The floating action button.
11
+ * soft — app surface + hairline border. Sits in a header or toolbar.
12
+ * minimal — bare mark, no fill or border. Sits in a nav bar.
13
+ */
14
+ export type LauncherVariant = 'solid' | 'soft' | 'minimal';
6
15
  export interface SupportUser {
7
16
  /** Display name shown to the support team. */
8
17
  name: string;
@@ -28,6 +37,20 @@ export interface SupportButtonProps {
28
37
  supabaseUrl: string;
29
38
  /** Visual theme. Matches the admin dashboard tokens. Default "dark". */
30
39
  theme?: ThemeName;
40
+ /**
41
+ * Brand color (hex, e.g. "#FF5A1F") replacing the default accent across
42
+ * the launcher, buttons, and the user's own message bubbles. The pressed
43
+ * shade, the 12% wash, and a legible foreground are derived from it.
44
+ */
45
+ accentColor?: string;
46
+ /** Launcher style. Default "solid" — the floating action button. */
47
+ variant?: LauncherVariant;
48
+ /** Launcher diameter in px (height when `label` is set). Default 52. */
49
+ size?: number;
50
+ /** Renders the launcher as a pill with this text beside the mark. */
51
+ label?: string;
52
+ /** Style applied to the launcher — position it, add margins, etc. */
53
+ style?: StyleProp<ViewStyle>;
31
54
  /**
32
55
  * Idle poll interval (ms) — how often the unread badge is refreshed while
33
56
  * the sheet is CLOSED, using a lightweight count-only endpoint. Default
@@ -45,21 +68,43 @@ export interface SupportButtonProps {
45
68
  * installed — it is an optional peer dependency.
46
69
  */
47
70
  expoProjectId?: string;
71
+ /**
72
+ * Pre-selects a language in the picker the first time a user opens support.
73
+ * Pass it when the host app already knows what the user reads — an in-app
74
+ * language setting beats the device locale, which is what the SDK falls
75
+ * back to. It is a default, not an override: the user still chooses, and a
76
+ * choice they have already made always wins.
77
+ */
78
+ defaultLanguage?: Language;
48
79
  }
49
80
  export interface ThreadMessage {
50
81
  id: string;
51
82
  sender: MessageSender;
83
+ /**
84
+ * Already in the user's language: their own messages as they wrote them,
85
+ * support replies translated. The server does the choosing — see the
86
+ * get-threads function.
87
+ */
52
88
  body: string;
53
89
  created_at: string;
54
- admin_name: string | null;
90
+ /**
91
+ * A support reply whose translation hasn't landed yet. `body` currently
92
+ * holds the English original; it will change under the reader shortly, so
93
+ * the UI says so rather than letting it flicker.
94
+ */
95
+ translation_pending: boolean;
55
96
  }
97
+ /**
98
+ * Note the absence of any admin identity: end users see one support team,
99
+ * not the people staffing it, so the names never leave the server. The
100
+ * dashboard still shows who wrote and who resolved what.
101
+ */
56
102
  export interface Thread {
57
103
  id: string;
58
104
  type: QueryType;
59
105
  status: QueryStatus;
60
106
  created_at: string;
61
107
  resolved_at: string | null;
62
- resolved_by_name: string | null;
63
108
  app_version: string;
64
109
  unread_count: number;
65
110
  last_message: {
@@ -72,7 +117,14 @@ export interface Thread {
72
117
  export interface ThreadsResponse {
73
118
  threads: Thread[];
74
119
  total_unread: number;
120
+ /** Null when this user has never picked one — the SDK then asks. */
121
+ language: string | null;
75
122
  }
123
+ /**
124
+ * English labels, kept for host apps that render their own entry points.
125
+ * Inside the sheet the labels come from the string table instead, so they
126
+ * follow the user's language.
127
+ */
76
128
  export declare const QUERY_TYPE_LABEL: Record<QueryType, string>;
77
129
  export declare const USER_MESSAGE_MIN = 10;
78
130
  export declare const USER_MESSAGE_MAX = 300;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,YAAY,CAAC;AACtE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AAC9C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAC7C,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AACzC,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzE,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,MAAM,EAAE,OAAO,CAAC;IAChB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,WAAW,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,aAAa,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI,CAAC;IACT,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAItD,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,gBAAgB,MAAM,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Thread } from './types';
2
+ /** Watermark map: thread id → `created_at` (ms) of the newest seen reply. */
3
+ export type ReadWatermarks = Map<string, number>;
4
+ /** `created_at` (ms) of the newest admin message, or 0 if there are none. */
5
+ export declare function latestAdminMessageAt(thread: Thread): number;
6
+ /** Admin messages in `thread` newer than `watermark`. */
7
+ export declare function unreadSince(thread: Thread, watermark: number): number;
8
+ /**
9
+ * Returns `thread` with `unread_count` narrowed by what this device knows
10
+ * the user has read. Identity is preserved when nothing changes so lists
11
+ * stay referentially stable between polls.
12
+ */
13
+ export declare function reconcileThread(thread: Thread, watermarks: ReadWatermarks): Thread;
14
+ export declare function reconcileThreads(threads: Thread[], watermarks: ReadWatermarks): Thread[];
15
+ export declare function sumUnread(threads: Thread[]): number;
16
+ /**
17
+ * Drops watermarks the server has caught up on (its own count is already 0)
18
+ * and ones for threads that no longer exist, so the map can't grow without
19
+ * bound over a long session.
20
+ */
21
+ export declare function pruneWatermarks(watermarks: ReadWatermarks, serverThreads: Thread[]): ReadWatermarks;
22
+ //# sourceMappingURL=unread.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unread.d.ts","sourceRoot":"","sources":["../../src/unread.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ3D;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQrE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,cAAc,GACzB,MAAM,CAKR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EAAE,EACjB,UAAU,EAAE,cAAc,GACzB,MAAM,EAAE,CAEV;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,cAAc,EAC1B,aAAa,EAAE,MAAM,EAAE,GACtB,cAAc,CAShB"}
@@ -0,0 +1,38 @@
1
+ import { type ApiConfig } from './api';
2
+ import type { StringKey } from './i18n';
3
+ import type { Thread } from './types';
4
+ export declare const IDLE_POLL_MS = 60000;
5
+ export interface SupportThreadsState {
6
+ /** Server threads with unread counts narrowed by local reads. */
7
+ threads: Thread[] | null;
8
+ totalUnread: number;
9
+ /** First load only — there is nothing cached to show yet. */
10
+ loading: boolean;
11
+ /** User-pulled refresh with content already on screen. */
12
+ refreshing: boolean;
13
+ /** Set only when there is nothing cached; background failures stay quiet. */
14
+ errorKey: StringKey | null;
15
+ /** The language this user picked, or null if they never have. */
16
+ serverLanguage: string | null;
17
+ /**
18
+ * False until the first successful load. `serverLanguage === null` only
19
+ * means "never picked" once this is true — before that it means "don't
20
+ * know yet", and showing the picker on a guess would nag a user who
21
+ * answered on another device.
22
+ */
23
+ languageLoaded: boolean;
24
+ /** User-initiated refresh (pull-to-refresh, retry button). */
25
+ refresh: () => void;
26
+ /** Awaited by send/submit handlers so the list reflects the write. */
27
+ reload: () => Promise<void>;
28
+ }
29
+ export declare function useSupportThreads({ config, revenuecatId, enabled, sheetOpen, activeThreadId, idlePollMs, }: {
30
+ config: ApiConfig;
31
+ revenuecatId: string;
32
+ /** False when props failed validation — no requests are made. */
33
+ enabled: boolean;
34
+ sheetOpen: boolean;
35
+ activeThreadId: string | null;
36
+ idlePollMs: number;
37
+ }): SupportThreadsState;
38
+ //# sourceMappingURL=useSupportThreads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSupportThreads.d.ts","sourceRoot":"","sources":["../../src/useSupportThreads.ts"],"names":[],"mappings":"AAgBA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAetC,eAAO,MAAM,YAAY,QAAS,CAAC;AA8CnC,MAAM,WAAW,mBAAmB;IAClC,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;IAC3B,iEAAiE;IACjE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,sEAAsE;IACtE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,YAAY,EACZ,OAAO,EACP,SAAS,EACT,cAAc,EACd,UAAU,GACX,EAAE;IACD,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,mBAAmB,CA0QtB"}
@@ -1,6 +1,12 @@
1
+ import type { Translator } from './i18n';
1
2
  /** "2h ago" relative time — mirrors the web dashboard's formatting. */
2
- export declare function relativeTime(iso: string): string;
3
- export declare function formatDate(iso: string): string;
3
+ export declare function relativeTime(iso: string, strings: Translator): string;
4
+ export declare function formatDate(iso: string, strings: Translator): string;
5
+ /** "14:32" in the user's locale — shown under a group of messages. */
6
+ export declare function formatTime(iso: string, strings: Translator): string;
7
+ export declare function isSameDay(a: string, b: string): boolean;
8
+ /** Date separator label: Today / Yesterday / "Mon, 5 Jan". */
9
+ export declare function dayLabel(iso: string, strings: Translator): string;
4
10
  /** Dev-mode prop validation with actionable messages. */
5
11
  export declare function validateProps(props: {
6
12
  appName?: unknown;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAehD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM9C;AAED,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,MAAM,GAAG,IAAI,CA8BhB"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAKzC,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAerE;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAMnE;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAKnE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAQvD;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,MAAM,CAYjE;AAED,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,GAAG,MAAM,GAAG,IAAI,CA8BhB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@appsupportkit/support-sdk",
3
- "version": "0.1.2",
4
- "description": "In-app customer support for React Native: feedback, feature requests, and bug reports with two-way threads.",
3
+ "version": "0.3.0",
4
+ "description": "In-app customer support for React Native: feedback, feature requests, and bug reports with two-way threads, in the user's own language.",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",
7
7
  "types": "./lib/typescript/index.d.ts",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@appsupportkit/design-tokens": "^0.1.0"
22
+ "@appsupportkit/design-tokens": "^0.2.0"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "expo-notifications": ">=0.27.0",
package/src/api.ts CHANGED
@@ -112,6 +112,20 @@ export function registerPush(
112
112
  });
113
113
  }
114
114
 
115
+ /** Records the language the user reads and writes support messages in. */
116
+ export function setLanguage(
117
+ config: ApiConfig,
118
+ input: { revenuecatId: string; language: string },
119
+ ): Promise<{ language: string }> {
120
+ return request(config, 'set-language', {
121
+ method: 'POST',
122
+ body: JSON.stringify({
123
+ revenuecat_id: input.revenuecatId,
124
+ language: input.language,
125
+ }),
126
+ });
127
+ }
128
+
115
129
  export function submitQuery(
116
130
  config: ApiConfig,
117
131
  input: {
@@ -121,6 +135,7 @@ export function submitQuery(
121
135
  isPaid: boolean;
122
136
  revenuecatId: string;
123
137
  appVersion: string;
138
+ language: string;
124
139
  },
125
140
  ): Promise<{ query_id: string }> {
126
141
  return request(config, 'submit-query', {
@@ -133,13 +148,20 @@ export function submitQuery(
133
148
  is_paid: input.isPaid,
134
149
  revenuecat_id: input.revenuecatId,
135
150
  app_version: input.appVersion,
151
+ // Sent on every write so a lost set-language call self-heals.
152
+ language: input.language,
136
153
  }),
137
154
  });
138
155
  }
139
156
 
140
157
  export function postMessage(
141
158
  config: ApiConfig,
142
- input: { queryId: string; revenuecatId: string; message: string },
159
+ input: {
160
+ queryId: string;
161
+ revenuecatId: string;
162
+ message: string;
163
+ language: string;
164
+ },
143
165
  ): Promise<{ message_id: string }> {
144
166
  return request(config, 'post-message', {
145
167
  method: 'POST',
@@ -147,6 +169,7 @@ export function postMessage(
147
169
  query_id: input.queryId,
148
170
  revenuecat_id: input.revenuecatId,
149
171
  message: input.message,
172
+ language: input.language,
150
173
  }),
151
174
  });
152
175
  }
@@ -1,10 +1,15 @@
1
1
  // Bottom sheet built on the built-in Modal + Animated APIs.
2
2
  //
3
3
  // Why no sheet library: the SDK needs exactly one detent, a drag handle, a
4
- // backdrop fade, and a 180ms ease-out slide — all achievable with core APIs.
4
+ // backdrop fade, and a short ease-out slide — all achievable with core APIs.
5
5
  // Skipping reanimated/gesture-handler keeps the package free of native
6
6
  // dependencies and install friction for consumers.
7
- import { useCallback, useEffect, useRef, type ReactNode } from 'react';
7
+ //
8
+ // `visible` drives a single animation path: the sheet stays mounted until
9
+ // its exit finishes, so closing from the header button, the backdrop, the
10
+ // hardware back button, or a drag all animate identically — and a reopen
11
+ // always starts from off-screen instead of appearing already in place.
12
+ import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
8
13
  import {
9
14
  Animated,
10
15
  Easing,
@@ -17,7 +22,8 @@ import {
17
22
  import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
18
23
  import type { SupportTheme } from '../theme';
19
24
 
20
- const SLIDE_MS = 180; // motion.slow — ease-out, no springs, no bounce.
25
+ const SLIDE_MS = 260;
26
+ const FADE_MS = 200;
21
27
 
22
28
  export function BottomSheet({
23
29
  visible,
@@ -31,57 +37,67 @@ export function BottomSheet({
31
37
  children: ReactNode;
32
38
  }) {
33
39
  const { height: windowHeight } = useWindowDimensions();
34
- const sheetHeight = Math.min(windowHeight * 0.82, 640);
40
+ const sheetHeight = Math.min(windowHeight * 0.88, 720);
35
41
 
42
+ const [rendered, setRendered] = useState(false);
36
43
  const translateY = useRef(new Animated.Value(sheetHeight)).current;
37
44
  const backdrop = useRef(new Animated.Value(0)).current;
45
+ /** Whether the entrance has already played for the current open. */
46
+ const shown = useRef(false);
38
47
 
39
- const animateIn = useCallback(() => {
40
- Animated.parallel([
41
- Animated.timing(translateY, {
42
- toValue: 0,
43
- duration: SLIDE_MS,
44
- easing: Easing.out(Easing.cubic),
45
- useNativeDriver: true,
46
- }),
47
- Animated.timing(backdrop, {
48
- toValue: 1,
49
- duration: SLIDE_MS,
50
- easing: Easing.out(Easing.cubic),
51
- useNativeDriver: true,
52
- }),
53
- ]).start();
54
- }, [translateY, backdrop]);
48
+ useEffect(() => {
49
+ if (visible) setRendered(true);
50
+ }, [visible]);
55
51
 
56
- const animateOut = useCallback(
57
- (done: () => void) => {
52
+ useEffect(() => {
53
+ if (!rendered) return;
54
+
55
+ if (visible && !shown.current) {
56
+ shown.current = true;
57
+ translateY.setValue(sheetHeight);
58
58
  Animated.parallel([
59
59
  Animated.timing(translateY, {
60
- toValue: sheetHeight,
60
+ toValue: 0,
61
61
  duration: SLIDE_MS,
62
+ easing: Easing.bezier(0.16, 1, 0.3, 1),
63
+ useNativeDriver: true,
64
+ }),
65
+ Animated.timing(backdrop, {
66
+ toValue: 1,
67
+ duration: FADE_MS,
62
68
  easing: Easing.out(Easing.cubic),
63
69
  useNativeDriver: true,
64
70
  }),
71
+ ]).start();
72
+ return;
73
+ }
74
+
75
+ if (!visible && shown.current) {
76
+ shown.current = false;
77
+ // Starts from wherever the sheet is now, so a drag that crosses the
78
+ // dismiss threshold continues rather than jumping.
79
+ Animated.parallel([
80
+ Animated.timing(translateY, {
81
+ toValue: sheetHeight,
82
+ duration: theme.motion.slow,
83
+ easing: Easing.in(Easing.cubic),
84
+ useNativeDriver: true,
85
+ }),
65
86
  Animated.timing(backdrop, {
66
87
  toValue: 0,
67
- duration: SLIDE_MS,
68
- easing: Easing.out(Easing.cubic),
88
+ duration: theme.motion.slow,
89
+ easing: Easing.in(Easing.cubic),
69
90
  useNativeDriver: true,
70
91
  }),
71
92
  ]).start(({ finished }) => {
72
- if (finished) done();
93
+ if (finished) setRendered(false);
73
94
  });
74
- },
75
- [translateY, backdrop, sheetHeight],
76
- );
77
-
78
- useEffect(() => {
79
- if (visible) animateIn();
80
- }, [visible, animateIn]);
95
+ }
96
+ }, [rendered, visible, sheetHeight, translateY, backdrop, theme.motion.slow]);
81
97
 
82
98
  const requestClose = useCallback(() => {
83
- animateOut(onClose);
84
- }, [animateOut, onClose]);
99
+ onClose();
100
+ }, [onClose]);
85
101
 
86
102
  // Drag handle: follow the finger downward; past 25% (or a flick), dismiss.
87
103
  const panResponder = useRef(
@@ -97,7 +113,7 @@ export function BottomSheet({
97
113
  } else {
98
114
  Animated.timing(translateY, {
99
115
  toValue: 0,
100
- duration: SLIDE_MS,
116
+ duration: theme.motion.slow,
101
117
  easing: Easing.out(Easing.cubic),
102
118
  useNativeDriver: true,
103
119
  }).start();
@@ -106,7 +122,7 @@ export function BottomSheet({
106
122
  }),
107
123
  ).current;
108
124
 
109
- if (!visible) return null;
125
+ if (!rendered) return null;
110
126
 
111
127
  return (
112
128
  <Modal
@@ -125,7 +141,7 @@ export function BottomSheet({
125
141
  right: 0,
126
142
  bottom: 0,
127
143
  left: 0,
128
- backgroundColor: 'rgba(0,0,0,0.5)',
144
+ backgroundColor: 'rgba(0,0,0,0.55)',
129
145
  opacity: backdrop,
130
146
  }}
131
147
  >
@@ -139,25 +155,27 @@ export function BottomSheet({
139
155
  <Animated.View
140
156
  style={{
141
157
  height: sheetHeight,
142
- backgroundColor: theme.colors.overlay,
158
+ backgroundColor: theme.colors.bg,
143
159
  borderTopLeftRadius: theme.radii.sheet,
144
160
  borderTopRightRadius: theme.radii.sheet,
145
161
  borderWidth: 1,
146
162
  borderBottomWidth: 0,
147
163
  borderColor: theme.colors.border,
164
+ overflow: 'hidden',
148
165
  transform: [{ translateY }],
166
+ ...theme.shadows.sheet,
149
167
  }}
150
168
  >
151
169
  {/* Drag handle */}
152
170
  <View
153
171
  {...panResponder.panHandlers}
154
- style={{ alignItems: 'center', paddingVertical: 10 }}
172
+ style={{ alignItems: 'center', paddingTop: 10, paddingBottom: 6 }}
155
173
  >
156
174
  <View
157
175
  style={{
158
- width: 32,
159
- height: 4,
160
- borderRadius: 2,
176
+ width: 40,
177
+ height: 5,
178
+ borderRadius: 3,
161
179
  backgroundColor: theme.colors.borderStrong,
162
180
  }}
163
181
  />