@planningcenter/chat-react-native 2.2.1 → 2.2.2-rc.1
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/build/contexts/chat_context.d.ts +2 -1
- package/build/contexts/chat_context.d.ts.map +1 -1
- package/build/contexts/chat_context.js +3 -1
- package/build/contexts/chat_context.js.map +1 -1
- package/build/screens/conversation_details_screen.d.ts.map +1 -1
- package/build/screens/conversation_details_screen.js +178 -101
- package/build/screens/conversation_details_screen.js.map +1 -1
- package/build/types/api_primitives.d.ts +15 -0
- package/build/types/api_primitives.d.ts.map +1 -1
- package/build/types/api_primitives.js.map +1 -1
- package/build/types/utils/index.d.ts +1 -0
- package/build/types/utils/index.d.ts.map +1 -1
- package/build/types/utils/index.js +3 -1
- package/build/types/utils/index.js.map +1 -1
- package/build/utils/client/client.d.ts +8 -5
- package/build/utils/client/client.d.ts.map +1 -1
- package/build/utils/client/client.js +22 -7
- package/build/utils/client/client.js.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts +12 -0
- package/build/vendor/tapestry/alias_tokens_color_map.d.ts.map +1 -1
- package/build/vendor/tapestry/alias_tokens_color_map.js +12 -0
- package/build/vendor/tapestry/alias_tokens_color_map.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/client.ts +59 -4
- package/src/__tests__/hooks/useTheme.tsx +1 -1
- package/src/contexts/chat_context.tsx +5 -2
- package/src/screens/conversation_details_screen.tsx +295 -130
- package/src/types/api_primitives.ts +12 -0
- package/src/types/utils/index.ts +4 -0
- package/src/utils/client/client.ts +34 -11
- package/src/vendor/tapestry/alias_tokens_color_map.ts +18 -0
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { StaticScreenProps, useNavigation } from '@react-navigation/native'
|
|
2
|
+
import React, {
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
type SetStateAction,
|
|
7
|
+
type Dispatch,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react'
|
|
1
10
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
StyleSheet,
|
|
12
|
+
TextInput,
|
|
13
|
+
View,
|
|
14
|
+
type TextStyle,
|
|
15
|
+
type ViewStyle,
|
|
16
|
+
type ViewProps,
|
|
17
|
+
} from 'react-native'
|
|
9
18
|
import { Avatar, Badge, Heading, Icon, Switch, Text } from '../components'
|
|
10
19
|
import { useSuspenseGet, useTheme } from '../hooks'
|
|
11
20
|
import {
|
|
@@ -13,19 +22,55 @@ import {
|
|
|
13
22
|
useConversationMute,
|
|
14
23
|
useConversationUpdate,
|
|
15
24
|
} from '../hooks/use_conversation'
|
|
16
|
-
import { MemberResource } from '../types'
|
|
25
|
+
import { MemberBadge, MemberResource, isDefined } from '../types'
|
|
17
26
|
import { HeaderRightButton } from '../navigation/header'
|
|
27
|
+
import { FlashList } from '@shopify/flash-list'
|
|
28
|
+
import { space } from '../utils'
|
|
29
|
+
|
|
30
|
+
// =========================================
|
|
31
|
+
// ====== Factory Constants & Types ========
|
|
32
|
+
// =========================================
|
|
33
|
+
|
|
34
|
+
enum SectionTypes {
|
|
35
|
+
header,
|
|
36
|
+
hidden,
|
|
37
|
+
members,
|
|
38
|
+
setting,
|
|
39
|
+
view,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type SectionListData = Array<
|
|
43
|
+
| DataItem<{ title: string }, SectionTypes.header>
|
|
44
|
+
| DataItem<MemberResource, SectionTypes.members>
|
|
45
|
+
| DataItem<ViewProps, SectionTypes.view>
|
|
46
|
+
| DataItem<{ title: string }, SectionTypes.setting>
|
|
47
|
+
| DataItem<any, SectionTypes.hidden>
|
|
48
|
+
>
|
|
49
|
+
|
|
50
|
+
interface DataItem<T, TName extends SectionTypes> {
|
|
51
|
+
type: TName
|
|
52
|
+
data: T
|
|
53
|
+
sectionStyle?: ViewStyle
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// =================================
|
|
57
|
+
// ====== Components ===============
|
|
58
|
+
// =================================
|
|
18
59
|
|
|
19
60
|
export type ConversationDetailsScreenProps = StaticScreenProps<{
|
|
20
61
|
conversation_id: string
|
|
21
62
|
}>
|
|
22
63
|
|
|
23
64
|
export function ConversationDetailsScreen({ route }: ConversationDetailsScreenProps) {
|
|
24
|
-
const styles = useStyles()
|
|
25
65
|
const navigation = useNavigation()
|
|
66
|
+
const styles = useStyles()
|
|
67
|
+
|
|
26
68
|
const { data: conversation } = useConversation(route.params)
|
|
27
|
-
const
|
|
69
|
+
const [title, setTitle] = useState(conversation.title)
|
|
70
|
+
const { muted, setMuted } = useConversationMute(route.params)
|
|
71
|
+
const { mutate: saveTitle } = useConversationUpdate(route.params)
|
|
28
72
|
|
|
73
|
+
const canUpdate = conversation.memberAbility?.canUpdate || false
|
|
29
74
|
const { data: members } = useSuspenseGet<MemberResource[]>({
|
|
30
75
|
url: `/me/conversations/${route.params.conversation_id}/members`,
|
|
31
76
|
data: {
|
|
@@ -36,9 +81,11 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
36
81
|
},
|
|
37
82
|
})
|
|
38
83
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
const memberItems = members.map(data => ({
|
|
85
|
+
type: SectionTypes.members,
|
|
86
|
+
data,
|
|
87
|
+
}))
|
|
88
|
+
|
|
42
89
|
const HeaderRight = useCallback(() => {
|
|
43
90
|
return (
|
|
44
91
|
<HeaderRightButton
|
|
@@ -58,148 +105,266 @@ export function ConversationDetailsScreen({ route }: ConversationDetailsScreenPr
|
|
|
58
105
|
})
|
|
59
106
|
}, [HeaderRight, navigation])
|
|
60
107
|
|
|
108
|
+
const listData = [
|
|
109
|
+
{
|
|
110
|
+
type: SectionTypes.view,
|
|
111
|
+
data: {
|
|
112
|
+
children: <TitleInput canUpdate={canUpdate} title={title} setTitle={setTitle} />,
|
|
113
|
+
style: styles.titleContainer,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: SectionTypes.header,
|
|
118
|
+
data: { title: 'Settings' },
|
|
119
|
+
sectionStyle: styles.addBottomBorder,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: SectionTypes.setting,
|
|
123
|
+
data: {
|
|
124
|
+
title: 'Mute',
|
|
125
|
+
rightItem: <Switch value={muted} onChange={() => setMuted(!muted)} />,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: SectionTypes.header,
|
|
130
|
+
data: { title: 'Members' },
|
|
131
|
+
},
|
|
132
|
+
...memberItems,
|
|
133
|
+
].filter(item => item.type !== SectionTypes.hidden)
|
|
134
|
+
|
|
135
|
+
const headerIndices = listData
|
|
136
|
+
.map(({ type }, index) => (type === SectionTypes.header ? index : undefined))
|
|
137
|
+
.filter(isDefined)
|
|
138
|
+
|
|
61
139
|
return (
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
<FlashList
|
|
141
|
+
data={listData as SectionListData}
|
|
142
|
+
estimatedItemSize={52}
|
|
143
|
+
contentContainerStyle={styles.contentContainer}
|
|
144
|
+
renderItem={({ item, index }) => {
|
|
145
|
+
const [isStart, isEnd] = [
|
|
146
|
+
index === 0 || headerIndices.includes(index),
|
|
147
|
+
index === listData.length - 1 || headerIndices.includes(index + 1),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
switch (item.type) {
|
|
151
|
+
case SectionTypes.header:
|
|
152
|
+
return (
|
|
153
|
+
<ListSection isStart={isStart} isEnd={isEnd} style={item?.sectionStyle}>
|
|
154
|
+
<SectionHeading title={item.data.title} />
|
|
155
|
+
</ListSection>
|
|
156
|
+
)
|
|
157
|
+
case SectionTypes.members:
|
|
158
|
+
return (
|
|
159
|
+
<ListSection isStart={isStart} isEnd={isEnd} style={item?.sectionStyle}>
|
|
160
|
+
<MemberRow {...item.data} />
|
|
161
|
+
</ListSection>
|
|
162
|
+
)
|
|
163
|
+
case SectionTypes.setting:
|
|
164
|
+
return (
|
|
165
|
+
<ListSection isStart={isStart} isEnd={isEnd} style={item?.sectionStyle}>
|
|
166
|
+
<SettingRow {...item.data} />
|
|
167
|
+
</ListSection>
|
|
168
|
+
)
|
|
169
|
+
case SectionTypes.view:
|
|
170
|
+
return (
|
|
171
|
+
<ListSection isStart={isStart} isEnd={isEnd} style={item?.sectionStyle}>
|
|
172
|
+
<View {...item.data} style={item.data.style} />
|
|
173
|
+
</ListSection>
|
|
174
|
+
)
|
|
175
|
+
default:
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
interface ListSectionProps {
|
|
184
|
+
isStart?: boolean
|
|
185
|
+
isEnd?: boolean
|
|
186
|
+
style?: ViewStyle
|
|
187
|
+
children: ReactNode
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function ListSection({ isStart, isEnd, style, children }: ListSectionProps) {
|
|
191
|
+
const styles = useStyles({ isStart, isEnd })
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<View style={styles.sectionOuter}>
|
|
195
|
+
<View style={[styles.sectionInner, style]}>{children}</View>
|
|
196
|
+
</View>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interface SectionHeadingProps {
|
|
201
|
+
title: string
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function SectionHeading({ title }: SectionHeadingProps) {
|
|
205
|
+
const styles = useStyles()
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<View style={styles.header}>
|
|
209
|
+
<Heading variant="h3">{title}</Heading>
|
|
210
|
+
</View>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
interface InputProps {
|
|
215
|
+
canUpdate: boolean
|
|
216
|
+
title: string
|
|
217
|
+
setTitle: Dispatch<SetStateAction<string>>
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function TitleInput({ canUpdate, title, setTitle }: InputProps) {
|
|
221
|
+
const styles = useStyles()
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<View style={[styles.titleLabelContainer, !canUpdate && styles.titleInputDisabled]}>
|
|
225
|
+
<Text variant="tertiary">Title</Text>
|
|
226
|
+
{!canUpdate && <Icon name="general.lock" />}
|
|
227
|
+
</View>
|
|
228
|
+
<TextInput
|
|
229
|
+
editable={canUpdate}
|
|
230
|
+
onChangeText={setTitle}
|
|
231
|
+
style={[styles.titleInput, !canUpdate && styles.titleInputDisabled]}
|
|
232
|
+
value={title}
|
|
233
|
+
/>
|
|
234
|
+
</>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface SettingRowProps {
|
|
239
|
+
title: string
|
|
240
|
+
style?: ViewStyle
|
|
241
|
+
rightItem?: ReactNode
|
|
242
|
+
titleStyle?: TextStyle
|
|
243
|
+
subtitle?: string
|
|
244
|
+
rightItemStyle?: ViewStyle
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function SettingRow({
|
|
248
|
+
title,
|
|
249
|
+
style,
|
|
250
|
+
titleStyle = {},
|
|
251
|
+
subtitle,
|
|
252
|
+
rightItem,
|
|
253
|
+
rightItemStyle = {},
|
|
254
|
+
}: SettingRowProps) {
|
|
255
|
+
const styles = useStyles()
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<View style={[styles.settingRow, style]}>
|
|
259
|
+
<View style={styles.settingRowContent}>
|
|
260
|
+
<Text variant="plain" style={[styles.settingRowText, titleStyle]}>
|
|
261
|
+
{title}
|
|
262
|
+
</Text>
|
|
263
|
+
{Boolean(subtitle) && <Text variant="footnote">{subtitle}</Text>}
|
|
264
|
+
</View>
|
|
265
|
+
{Boolean(rightItem) && <View style={rightItemStyle}>{rightItem}</View>}
|
|
266
|
+
</View>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
interface MemberRowProps {
|
|
271
|
+
avatar: string
|
|
272
|
+
name: string
|
|
273
|
+
style?: ViewStyle
|
|
274
|
+
badges?: MemberBadge[]
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function MemberRow({ avatar, name, style, badges }: MemberRowProps) {
|
|
278
|
+
const styles = useStyles()
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<View style={[styles.member, style]}>
|
|
282
|
+
<Avatar sourceUri={avatar} />
|
|
283
|
+
<View style={styles.memberBody}>
|
|
284
|
+
<Text style={styles.memberName}>{name}</Text>
|
|
285
|
+
<View style={styles.memberBadges}>
|
|
286
|
+
{badges?.map((badge, index) => (
|
|
287
|
+
<View key={index} style={styles.memberBadge}>
|
|
288
|
+
<Badge label={badge.title} />
|
|
103
289
|
</View>
|
|
104
|
-
)}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/>
|
|
108
|
-
</SectionList>
|
|
290
|
+
))}
|
|
291
|
+
</View>
|
|
292
|
+
</View>
|
|
109
293
|
</View>
|
|
110
294
|
)
|
|
111
295
|
}
|
|
112
296
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
297
|
+
// =================================
|
|
298
|
+
// ====== Styles ===================
|
|
299
|
+
// =================================
|
|
300
|
+
|
|
301
|
+
const useStyles = ({ isStart, isEnd }: { isStart?: boolean; isEnd?: boolean } = {}) => {
|
|
302
|
+
const { colors } = useTheme()
|
|
116
303
|
|
|
117
304
|
return StyleSheet.create({
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
paddingHorizontal: 16,
|
|
122
|
-
backgroundColor: theme.colors.fillColorNeutral080,
|
|
123
|
-
paddingBottom: bottom,
|
|
124
|
-
gap: 16,
|
|
125
|
-
},
|
|
126
|
-
listContainer: {
|
|
127
|
-
gap: 12,
|
|
305
|
+
contentContainer: {
|
|
306
|
+
backgroundColor: colors.fillColorNeutral080,
|
|
307
|
+
padding: space(2),
|
|
128
308
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
309
|
+
sectionOuter: {
|
|
310
|
+
paddingTop: space(1),
|
|
311
|
+
paddingLeft: space(2),
|
|
312
|
+
backgroundColor: colors.fillColorNeutral100Inverted,
|
|
313
|
+
borderTopLeftRadius: isStart ? space(1) : 0,
|
|
314
|
+
borderTopRightRadius: isStart ? space(1) : 0,
|
|
315
|
+
borderBottomLeftRadius: isEnd ? space(1) : 0,
|
|
316
|
+
borderBottomRightRadius: isEnd ? space(1) : 0,
|
|
317
|
+
marginBottom: isEnd ? space(2) : 0,
|
|
132
318
|
},
|
|
133
|
-
|
|
134
|
-
|
|
319
|
+
sectionInner: {
|
|
320
|
+
borderBottomWidth: 0,
|
|
321
|
+
borderBottomColor: colors.borderColorDefaultBase,
|
|
322
|
+
paddingRight: space(2),
|
|
323
|
+
paddingBottom: space(1),
|
|
135
324
|
},
|
|
136
|
-
|
|
137
|
-
|
|
325
|
+
addBottomBorder: {
|
|
326
|
+
borderBottomWidth: isEnd ? 0 : 1,
|
|
138
327
|
},
|
|
139
|
-
|
|
328
|
+
titleContainer: {},
|
|
329
|
+
titleLabel: {},
|
|
330
|
+
titleLabelContainer: {
|
|
140
331
|
flexDirection: 'row',
|
|
332
|
+
alignItems: 'center',
|
|
141
333
|
gap: 8,
|
|
142
334
|
},
|
|
143
|
-
|
|
144
|
-
|
|
335
|
+
titleInput: {
|
|
336
|
+
color: colors.textColorDefaultPrimary,
|
|
337
|
+
},
|
|
338
|
+
titleInputDisabled: { opacity: 0.7 },
|
|
339
|
+
header: {
|
|
340
|
+
paddingVertical: space(1.5),
|
|
341
|
+
},
|
|
342
|
+
settingRow: {
|
|
145
343
|
flexDirection: 'row',
|
|
146
344
|
justifyContent: 'space-between',
|
|
147
345
|
alignItems: 'center',
|
|
346
|
+
gap: space(1),
|
|
148
347
|
},
|
|
149
|
-
|
|
348
|
+
settingRowContent: {
|
|
349
|
+
flex: 1,
|
|
350
|
+
},
|
|
351
|
+
settingRowText: {
|
|
150
352
|
lineHeight: 20,
|
|
151
353
|
},
|
|
152
|
-
|
|
153
|
-
titleLabel: {},
|
|
154
|
-
titleLabelContainer: {
|
|
354
|
+
member: {
|
|
155
355
|
flexDirection: 'row',
|
|
156
|
-
|
|
157
|
-
gap: 8,
|
|
356
|
+
gap: space(1),
|
|
158
357
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
},
|
|
162
|
-
titleInputDisabled: { opacity: 0.7 },
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const SectionList = ({ children }: PropsWithChildren) => {
|
|
167
|
-
const styles = useSectionListStyles()
|
|
168
|
-
|
|
169
|
-
return <View style={styles.container}>{children}</View>
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const SectionListHeader = ({
|
|
173
|
-
children,
|
|
174
|
-
divider = true,
|
|
175
|
-
}: PropsWithChildren & { divider?: boolean }) => {
|
|
176
|
-
const styles = useSectionListStyles()
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<Heading variant="h3" style={[styles.heading, divider && styles.headingDivider]}>
|
|
180
|
-
{children}
|
|
181
|
-
</Heading>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const useSectionListStyles = () => {
|
|
186
|
-
const theme = useTheme()
|
|
187
|
-
const navigationTheme = useNavigationTheme()
|
|
188
|
-
|
|
189
|
-
return StyleSheet.create({
|
|
190
|
-
container: {
|
|
191
|
-
padding: 16,
|
|
192
|
-
backgroundColor: navigationTheme.colors.card,
|
|
193
|
-
borderRadius: 8,
|
|
194
|
-
flexDirection: 'column',
|
|
195
|
-
gap: 8,
|
|
358
|
+
memberBody: {
|
|
359
|
+
gap: space(0.5),
|
|
196
360
|
},
|
|
197
|
-
|
|
198
|
-
|
|
361
|
+
memberName: {
|
|
362
|
+
lineHeight: space(2),
|
|
199
363
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
364
|
+
memberBadges: {
|
|
365
|
+
flexDirection: 'row',
|
|
366
|
+
gap: space(1),
|
|
203
367
|
},
|
|
368
|
+
memberBadge: {},
|
|
204
369
|
})
|
|
205
370
|
}
|
|
@@ -3,6 +3,14 @@ export interface ResourceObject {
|
|
|
3
3
|
type: string
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export interface ErrorObject {
|
|
7
|
+
source?: { parameter: string }
|
|
8
|
+
detail: string
|
|
9
|
+
meta?: { associated_resources: any[]; resource: string }
|
|
10
|
+
title: string
|
|
11
|
+
status: string
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
export type ApiResource<Type = ResourceObject> = {
|
|
7
15
|
data: Type
|
|
8
16
|
links: Record<string, string>
|
|
@@ -15,6 +23,10 @@ export type ApiCollection<Type = ResourceObject> = {
|
|
|
15
23
|
meta: CollectionMeta
|
|
16
24
|
}
|
|
17
25
|
|
|
26
|
+
export interface ApiError {
|
|
27
|
+
errors: ErrorObject[]
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
export interface CollectionMeta {
|
|
19
31
|
count: number
|
|
20
32
|
totalCount: number
|
package/src/types/utils/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiCollection, ApiResource } from '../../types'
|
|
1
|
+
import { ApiCollection, ApiError, ApiResource } from '../../types'
|
|
2
2
|
import { Session } from '../session'
|
|
3
3
|
import { Uri } from '../uri'
|
|
4
4
|
import {
|
|
@@ -15,7 +15,8 @@ import { DeleteRequest, GetRequest, PatchRequest, PostRequest, WalkRequest } fro
|
|
|
15
15
|
type ClientArgs = {
|
|
16
16
|
version: string
|
|
17
17
|
defaultHeaders?: Record<string, string>
|
|
18
|
-
|
|
18
|
+
onForceLogout?: () => void
|
|
19
|
+
onTokenExpired?: () => void
|
|
19
20
|
session: Session
|
|
20
21
|
app: string
|
|
21
22
|
}
|
|
@@ -24,13 +25,22 @@ export class Client {
|
|
|
24
25
|
version: string = ''
|
|
25
26
|
defaultHeaders: Record<string, string> = {}
|
|
26
27
|
uri: Uri
|
|
27
|
-
onTokenExpired
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
onTokenExpired?: () => void
|
|
29
|
+
onForceLogout?: () => void
|
|
30
|
+
|
|
31
|
+
constructor({
|
|
32
|
+
version,
|
|
33
|
+
defaultHeaders = {},
|
|
34
|
+
session,
|
|
35
|
+
app,
|
|
36
|
+
onTokenExpired,
|
|
37
|
+
onForceLogout,
|
|
38
|
+
}: ClientArgs) {
|
|
30
39
|
this.version = version
|
|
31
40
|
this.uri = new Uri({ session, app })
|
|
32
41
|
this.defaultHeaders = defaultHeaders
|
|
33
42
|
this.onTokenExpired = onTokenExpired
|
|
43
|
+
this.onForceLogout = onForceLogout
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
async get<T extends ApiCollection | ApiResource>(args: GetRequest): Promise<T> {
|
|
@@ -69,7 +79,7 @@ export class Client {
|
|
|
69
79
|
|
|
70
80
|
const handler = isWalking ? walkRequest : makeRequest
|
|
71
81
|
|
|
72
|
-
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.
|
|
82
|
+
return throwErrorIfFieldsMissing(handler, requestArgs).catch(this.handleNotOk)
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
async patch(args: PatchRequest) {
|
|
@@ -78,7 +88,7 @@ export class Client {
|
|
|
78
88
|
|
|
79
89
|
const requestArgs: MakeRequestArgs = { data: args.data, url, action: 'PATCH', headers }
|
|
80
90
|
|
|
81
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
91
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
async post(args: PostRequest) {
|
|
@@ -87,7 +97,7 @@ export class Client {
|
|
|
87
97
|
|
|
88
98
|
const requestArgs: MakeRequestArgs = { ...args, data: args.data, url, action: 'POST', headers }
|
|
89
99
|
|
|
90
|
-
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.
|
|
100
|
+
return ensureNoQueryParamsInDev(makeRequest, requestArgs).catch(this.handleNotOk)
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
async delete(args: DeleteRequest) {
|
|
@@ -96,17 +106,30 @@ export class Client {
|
|
|
96
106
|
|
|
97
107
|
const requestArgs: MakeRequestArgs = { url, action: 'DELETE', headers }
|
|
98
108
|
|
|
99
|
-
return makeRequest(requestArgs).catch(this.
|
|
109
|
+
return makeRequest(requestArgs).catch(this.handleNotOk)
|
|
100
110
|
}
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
handleNotOk = async (response: Response) => {
|
|
103
113
|
if (response.status === 401) {
|
|
104
|
-
this.
|
|
114
|
+
const parsed = await this.parseErrorResponse(response)
|
|
115
|
+
const errors = parsed?.errors || []
|
|
116
|
+
const isTokenExpired = errors.some(e => /baboon/i.test(e.detail))
|
|
117
|
+
const isForceLogout = errors.some(e => /capuchin/i.test(e.detail))
|
|
118
|
+
isTokenExpired && this.onTokenExpired?.()
|
|
119
|
+
isForceLogout && this.onForceLogout?.()
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
return Promise.reject(response)
|
|
108
123
|
}
|
|
109
124
|
|
|
125
|
+
parseErrorResponse = async (response: Response): Promise<ApiError | undefined> => {
|
|
126
|
+
try {
|
|
127
|
+
return (await response.json()) as ApiError
|
|
128
|
+
} catch {
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
get headers() {
|
|
111
134
|
return {
|
|
112
135
|
Accept: 'application/vnd.api+json',
|
|
@@ -51,6 +51,12 @@ const neutralsDark: NeutralColors = {
|
|
|
51
51
|
|
|
52
52
|
interface SemanticAliasesColors {
|
|
53
53
|
name: string
|
|
54
|
+
borderColorDefaultBase: string
|
|
55
|
+
borderColorDefaultDark: string
|
|
56
|
+
borderColorDefaultDarker: string
|
|
57
|
+
borderColorDefaultDisabled: string
|
|
58
|
+
borderColorDefaultDim: string
|
|
59
|
+
borderColorDefaultWhite: string
|
|
54
60
|
iconColorDefaultPrimary: string
|
|
55
61
|
iconColorDefaultSecondary: string
|
|
56
62
|
iconColorDefaultDim: string
|
|
@@ -72,6 +78,12 @@ interface SemanticAliasesColors {
|
|
|
72
78
|
|
|
73
79
|
const semanticAliasesLight: SemanticAliasesColors = {
|
|
74
80
|
name: 'light',
|
|
81
|
+
borderColorDefaultBase: neutralsLight.fillColorNeutral050Base,
|
|
82
|
+
borderColorDefaultDark: neutralsLight.fillColorNeutral040,
|
|
83
|
+
borderColorDefaultDarker: neutralsLight.fillColorNeutral020,
|
|
84
|
+
borderColorDefaultDisabled: neutralsLight.fillColorNeutral050Base,
|
|
85
|
+
borderColorDefaultDim: neutralsLight.fillColorNeutral070,
|
|
86
|
+
borderColorDefaultWhite: neutralsLight.fillColorNeutral100Inverted,
|
|
75
87
|
iconColorDefaultPrimary: neutralsLight.fillColorNeutral010,
|
|
76
88
|
iconColorDefaultSecondary: neutralsLight.fillColorNeutral020,
|
|
77
89
|
iconColorDefaultDim: neutralsLight.fillColorNeutral030,
|
|
@@ -93,6 +105,12 @@ const semanticAliasesLight: SemanticAliasesColors = {
|
|
|
93
105
|
|
|
94
106
|
const semanticAliasesDark: SemanticAliasesColors = {
|
|
95
107
|
name: 'dark',
|
|
108
|
+
borderColorDefaultBase: neutralsDark.fillColorNeutral050Base,
|
|
109
|
+
borderColorDefaultDark: neutralsDark.fillColorNeutral040,
|
|
110
|
+
borderColorDefaultDarker: neutralsDark.fillColorNeutral020,
|
|
111
|
+
borderColorDefaultDisabled: neutralsDark.fillColorNeutral050Base,
|
|
112
|
+
borderColorDefaultDim: neutralsDark.fillColorNeutral070,
|
|
113
|
+
borderColorDefaultWhite: neutralsDark.fillColorNeutral100Inverted,
|
|
96
114
|
iconColorDefaultPrimary: neutralsDark.fillColorNeutral010,
|
|
97
115
|
iconColorDefaultSecondary: neutralsDark.fillColorNeutral020,
|
|
98
116
|
iconColorDefaultDim: neutralsDark.fillColorNeutral030,
|