@bigbinary/neeto-commons-rn 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/align-deps-preset.js +84 -0
- package/lib/typescript/api/app_version.d.ts.map +1 -1
- package/lib/typescript/components/AppVersion.d.ts.map +1 -1
- package/lib/typescript/components/DevToolsBridge.d.ts +8 -0
- package/lib/typescript/components/DevToolsBridge.d.ts.map +1 -0
- package/lib/typescript/components/Header.d.ts.map +1 -1
- package/lib/typescript/components/ToggleSwitch.d.ts.map +1 -1
- package/lib/typescript/components/constants.d.ts +1 -0
- package/lib/typescript/components/constants.d.ts.map +1 -1
- package/lib/typescript/config/axios/constants.d.ts +1 -0
- package/lib/typescript/config/axios/constants.d.ts.map +1 -1
- package/lib/typescript/config/axios/groupingKey.d.ts +12 -0
- package/lib/typescript/config/axios/groupingKey.d.ts.map +1 -0
- package/lib/typescript/config/axios/index.d.ts.map +1 -1
- package/lib/typescript/config/bugsnag.d.ts +1 -1
- package/lib/typescript/config/bugsnag.d.ts.map +1 -1
- package/lib/typescript/constants/index.d.ts +10 -0
- package/lib/typescript/constants/index.d.ts.map +1 -1
- package/lib/typescript/helpers/notifications/firebaseMessaging.android.d.ts +10 -0
- package/lib/typescript/helpers/notifications/firebaseMessaging.android.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/firebaseMessaging.d.ts +24 -0
- package/lib/typescript/helpers/notifications/firebaseMessaging.d.ts.map +1 -0
- package/lib/typescript/helpers/notifications/getDeviceToken.d.ts.map +1 -1
- package/lib/typescript/helpers/notifications/index.d.ts +1 -0
- package/lib/typescript/helpers/notifications/index.d.ts.map +1 -1
- package/lib/typescript/helpers/scale.d.ts +10 -0
- package/lib/typescript/helpers/scale.d.ts.map +1 -1
- package/lib/typescript/helpers/utils.d.ts.map +1 -1
- package/lib/typescript/hooks/query/useAppVersionApi.d.ts.map +1 -1
- package/lib/typescript/hooks/query/useRolesApi.d.ts.map +1 -1
- package/lib/typescript/providers/NeetoCommonsRNProvider.d.ts +8 -1
- package/lib/typescript/providers/NeetoCommonsRNProvider.d.ts.map +1 -1
- package/lib/typescript/screens/authentication/LoginScreen.d.ts.map +1 -1
- package/lib/typescript/screens/authentication/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/screens/authentication/Organization.d.ts.map +1 -1
- package/lib/typescript/screens/authentication/SubDomainScreen.d.ts.map +1 -1
- package/lib/typescript/screens/profile/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/screens/team_members/ProductSelect.d.ts.map +1 -1
- package/lib/typescript/screens/team_members/index.d.ts.map +1 -1
- package/lib/typescript/screens/team_members/utils.d.ts.map +1 -1
- package/lib/typescript/store/index.d.ts +3 -2
- package/lib/typescript/store/index.d.ts.map +1 -1
- package/lib/typescript/utils/apiHelpers.d.ts.map +1 -1
- package/metro-devtools.js +48 -0
- package/package.json +485 -16
- package/src/api/app_version.ts +42 -2
- package/src/components/AppVersion.ts +46 -30
- package/src/components/DevToolsBridge.ts +40 -0
- package/src/components/Header.tsx +3 -1
- package/src/components/NotificationIcon.tsx +1 -1
- package/src/components/ToggleSwitch.tsx +2 -1
- package/src/components/WorkspaceSwitcherPane.tsx +1 -1
- package/src/components/constants.ts +4 -0
- package/src/config/axios/constants.ts +3 -0
- package/src/config/axios/groupingKey.ts +25 -0
- package/src/config/axios/index.ts +7 -4
- package/src/config/bugsnag.ts +11 -1
- package/src/constants/index.ts +13 -0
- package/src/contexts/localization.tsx +1 -1
- package/src/helpers/notifications/firebaseMessaging.android.ts +21 -0
- package/src/helpers/notifications/firebaseMessaging.ts +37 -0
- package/src/helpers/notifications/getDeviceToken.ts +1 -2
- package/src/helpers/notifications/index.ts +1 -0
- package/src/helpers/notifications/registerBackgroundHandlers.ts +1 -1
- package/src/helpers/notifications/usePushNotification.ts +1 -1
- package/src/helpers/scale.ts +21 -3
- package/src/helpers/utils.ts +2 -1
- package/src/hooks/query/useAppVersionApi.ts +9 -0
- package/src/hooks/query/useRolesApi.ts +3 -1
- package/src/providers/NeetoCommonsRNProvider.tsx +23 -0
- package/src/screens/authentication/IncompleteOnboardingInfo.tsx +2 -2
- package/src/screens/authentication/LoginScreen.tsx +50 -39
- package/src/screens/authentication/OTPVerificationScreen.tsx +17 -41
- package/src/screens/authentication/Organization.tsx +2 -6
- package/src/screens/authentication/SubDomainScreen.tsx +38 -70
- package/src/screens/profile/ProfileScreen.tsx +2 -1
- package/src/screens/team_members/Card.tsx +1 -1
- package/src/screens/team_members/ProductSelect.tsx +6 -7
- package/src/screens/team_members/index.tsx +2 -1
- package/src/screens/team_members/utils.ts +3 -1
- package/src/store/index.ts +68 -1
- package/src/utils/apiHelpers.ts +4 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
Container,
|
|
6
6
|
Input,
|
|
7
7
|
ParentView,
|
|
8
|
+
ScrollView,
|
|
8
9
|
Toast,
|
|
9
10
|
Typography,
|
|
10
11
|
lightTheme,
|
|
@@ -37,7 +38,6 @@ const styles = StyleSheet.create({
|
|
|
37
38
|
flex: 1,
|
|
38
39
|
},
|
|
39
40
|
body: {
|
|
40
|
-
flex: 1,
|
|
41
41
|
marginHorizontal: 16,
|
|
42
42
|
},
|
|
43
43
|
inputWrapper: {
|
|
@@ -45,11 +45,15 @@ const styles = StyleSheet.create({
|
|
|
45
45
|
marginBottom: 32,
|
|
46
46
|
marginTop: 18,
|
|
47
47
|
},
|
|
48
|
+
// `marginTop: auto` inside the grow-to-fill scroll content parks this at
|
|
49
|
+
// the bottom whenever the screen has slack, and lets it flow below the
|
|
50
|
+
// auth buttons when it does not. Absolute positioning used to pin it 50dp
|
|
51
|
+
// from the bottom, which landscape turned into an overlay across the
|
|
52
|
+
// middle of the form.
|
|
48
53
|
versionText: {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
bottom: moderateScale(50),
|
|
54
|
+
marginBottom: moderateScale(24),
|
|
55
|
+
marginTop: "auto",
|
|
56
|
+
paddingTop: moderateScale(24),
|
|
53
57
|
textAlign: "center",
|
|
54
58
|
},
|
|
55
59
|
androidVersionText: {
|
|
@@ -129,42 +133,49 @@ export const LoginScreen = () => {
|
|
|
129
133
|
barStyle="dark-content"
|
|
130
134
|
style={styles.screen}
|
|
131
135
|
>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
{/*
|
|
137
|
+
Landscape (and any small viewport with the keyboard up) leaves the
|
|
138
|
+
auth buttons taller than the screen, so the content scrolls instead
|
|
139
|
+
of being clipped.
|
|
140
|
+
*/}
|
|
141
|
+
<ScrollView>
|
|
142
|
+
<Container style={styles.body}>
|
|
143
|
+
{/*
|
|
144
|
+
The v2 `lineHeight` override has no v3 counterpart: ScreenHeader
|
|
145
|
+
takes only `title` and a container `style`, and sets the title's
|
|
146
|
+
line height itself on Android.
|
|
147
|
+
*/}
|
|
148
|
+
<ScreenHeader title={strings.commons.headers.login} />
|
|
149
|
+
<Container style={styles.inputWrapper}>
|
|
150
|
+
<Input
|
|
151
|
+
autoCapitalize="none"
|
|
152
|
+
error={touched?.email ? errors?.email : ""}
|
|
153
|
+
keyboardType="email-address"
|
|
154
|
+
label={strings.commons.labels.Email}
|
|
155
|
+
value={email}
|
|
156
|
+
onBlur={() => setFieldValue("email", email.toLowerCase())}
|
|
157
|
+
onChangeText={handleChange("email")}
|
|
158
|
+
/>
|
|
159
|
+
</Container>
|
|
160
|
+
<AuthOptions
|
|
161
|
+
buttonLoading={isGeneratingOTP}
|
|
162
|
+
handleSubmit={handleSubmit}
|
|
148
163
|
/>
|
|
149
164
|
</Container>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
>
|
|
164
|
-
{Config.APP_DISPLAY_NAME}
|
|
165
|
-
{strings.commons.common.version}
|
|
166
|
-
{DeviceInfo.getVersion()}
|
|
167
|
-
</Typography>
|
|
165
|
+
<Typography
|
|
166
|
+
color={lightTheme.colors.palette.grey600}
|
|
167
|
+
size="xs"
|
|
168
|
+
weight="regular"
|
|
169
|
+
style={StyleSheet.flatten([
|
|
170
|
+
styles.versionText,
|
|
171
|
+
Platform.OS === "android" && styles.androidVersionText,
|
|
172
|
+
])}
|
|
173
|
+
>
|
|
174
|
+
{Config.APP_DISPLAY_NAME}
|
|
175
|
+
{strings.commons.common.version}
|
|
176
|
+
{DeviceInfo.getVersion()}
|
|
177
|
+
</Typography>
|
|
178
|
+
</ScrollView>
|
|
168
179
|
</ParentView>
|
|
169
180
|
);
|
|
170
181
|
};
|
|
@@ -4,15 +4,14 @@ import { StyleSheet } from "react-native";
|
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Container,
|
|
7
|
+
Header,
|
|
7
8
|
Input,
|
|
8
9
|
ParentView,
|
|
9
10
|
Toast,
|
|
10
|
-
Touchable,
|
|
11
11
|
Typography,
|
|
12
12
|
lightTheme,
|
|
13
13
|
} from "@bigbinary/neetoui-rn";
|
|
14
14
|
import { useFocusEffect } from "@react-navigation/native";
|
|
15
|
-
import Icon from "react-native-remix-icon";
|
|
16
15
|
|
|
17
16
|
import { useLocalization } from "../../contexts/localization";
|
|
18
17
|
import { useAuth } from "../../hooks/query/useAuth";
|
|
@@ -20,7 +19,6 @@ import { type CommonsScreenProps } from "../../navigation/types";
|
|
|
20
19
|
|
|
21
20
|
// eslint-disable-next-line @bigbinary/neeto/no-dangling-constants
|
|
22
21
|
const RESEND_TIMER = 30;
|
|
23
|
-
let interval: ReturnType<typeof setInterval> | null = null;
|
|
24
22
|
|
|
25
23
|
export const OTPVerificationScreen = ({
|
|
26
24
|
navigation,
|
|
@@ -31,6 +29,7 @@ export const OTPVerificationScreen = ({
|
|
|
31
29
|
const [startTimer, setStartTimer] = useState(false);
|
|
32
30
|
|
|
33
31
|
const isSubmittingRef = useRef(false);
|
|
32
|
+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
34
33
|
|
|
35
34
|
const { email, userId } = params;
|
|
36
35
|
const { strings } = useLocalization();
|
|
@@ -80,10 +79,12 @@ export const OTPVerificationScreen = ({
|
|
|
80
79
|
|
|
81
80
|
useEffect(() => {
|
|
82
81
|
if (startTimer) {
|
|
83
|
-
|
|
82
|
+
intervalRef.current = setInterval(() => {
|
|
84
83
|
setResendTimer(prev => {
|
|
85
84
|
if (prev === 1) {
|
|
86
|
-
clearInterval(
|
|
85
|
+
clearInterval(
|
|
86
|
+
intervalRef.current as ReturnType<typeof setInterval>
|
|
87
|
+
);
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
return prev - 1;
|
|
@@ -91,9 +92,10 @@ export const OTPVerificationScreen = ({
|
|
|
91
92
|
}, 1000);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
//
|
|
95
|
-
// handle
|
|
96
|
-
return () =>
|
|
95
|
+
// The ref is null until the first tick is scheduled; clearing a null
|
|
96
|
+
// handle is a no-op.
|
|
97
|
+
return () =>
|
|
98
|
+
clearInterval(intervalRef.current as ReturnType<typeof setInterval>);
|
|
97
99
|
}, [startTimer]);
|
|
98
100
|
|
|
99
101
|
useEffect(() => {
|
|
@@ -118,27 +120,11 @@ export const OTPVerificationScreen = ({
|
|
|
118
120
|
barStyle="dark-content"
|
|
119
121
|
style={styles.screen}
|
|
120
122
|
>
|
|
123
|
+
<Header
|
|
124
|
+
title={strings.commons.headers.otpVerification}
|
|
125
|
+
onBack={() => navigation?.goBack()}
|
|
126
|
+
/>
|
|
121
127
|
<Container style={styles.body}>
|
|
122
|
-
<Container style={styles.backRow}>
|
|
123
|
-
<Touchable
|
|
124
|
-
style={styles.backButton}
|
|
125
|
-
onPress={() => navigation?.goBack()}
|
|
126
|
-
>
|
|
127
|
-
<Icon
|
|
128
|
-
color={lightTheme.colors.palette.grey600}
|
|
129
|
-
name="arrow-left-s-line"
|
|
130
|
-
size={25}
|
|
131
|
-
/>
|
|
132
|
-
</Touchable>
|
|
133
|
-
</Container>
|
|
134
|
-
<Typography
|
|
135
|
-
color={lightTheme.colors.palette.grey800}
|
|
136
|
-
style={styles.heading}
|
|
137
|
-
variant="header"
|
|
138
|
-
weight="semibold"
|
|
139
|
-
>
|
|
140
|
-
{strings.commons.headers.otpVerification}
|
|
141
|
-
</Typography>
|
|
142
128
|
<Typography
|
|
143
129
|
color={lightTheme.colors.palette.grey700}
|
|
144
130
|
size="s"
|
|
@@ -201,19 +187,9 @@ const styles = StyleSheet.create({
|
|
|
201
187
|
body: {
|
|
202
188
|
flex: 1,
|
|
203
189
|
marginHorizontal: 16,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
height: 42,
|
|
208
|
-
marginBottom: 18,
|
|
209
|
-
},
|
|
210
|
-
backButton: {
|
|
211
|
-
marginLeft: -14,
|
|
212
|
-
padding: 2,
|
|
213
|
-
width: 42,
|
|
214
|
-
},
|
|
215
|
-
heading: {
|
|
216
|
-
marginBottom: 2,
|
|
190
|
+
// The title moved into the Header, which carries its own bottom spacing;
|
|
191
|
+
// the description now opens the body.
|
|
192
|
+
marginTop: 16,
|
|
217
193
|
},
|
|
218
194
|
description: {
|
|
219
195
|
marginBottom: 24,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { StyleSheet } from "react-native";
|
|
3
3
|
|
|
4
|
+
import Right from "@bigbinary/neeto-icons-rn/icons/Right";
|
|
4
5
|
import {
|
|
5
6
|
Avatar,
|
|
6
7
|
Container,
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
Typography,
|
|
9
10
|
lightTheme,
|
|
10
11
|
} from "@bigbinary/neetoui-rn";
|
|
11
|
-
import Icon from "react-native-remix-icon";
|
|
12
12
|
|
|
13
13
|
import { moderateScale } from "../../helpers/scale";
|
|
14
14
|
import { type Organization as OrganizationEntity } from "../../slices/auth";
|
|
@@ -64,11 +64,7 @@ export const Organization = ({
|
|
|
64
64
|
) : (
|
|
65
65
|
showOrganizationDetails()
|
|
66
66
|
)}
|
|
67
|
-
<
|
|
68
|
-
color={lightTheme.colors.primary}
|
|
69
|
-
name="arrow-right-s-line"
|
|
70
|
-
size={25}
|
|
71
|
-
/>
|
|
67
|
+
<Right color={lightTheme.colors.primary} size={25} />
|
|
72
68
|
</Touchable>
|
|
73
69
|
);
|
|
74
70
|
};
|
|
@@ -4,17 +4,15 @@ import { StyleSheet } from "react-native";
|
|
|
4
4
|
import {
|
|
5
5
|
Container,
|
|
6
6
|
FlashList,
|
|
7
|
+
Header,
|
|
7
8
|
ParentView,
|
|
8
9
|
SearchBar,
|
|
9
|
-
Touchable,
|
|
10
10
|
Typography,
|
|
11
11
|
lightTheme,
|
|
12
12
|
} from "@bigbinary/neetoui-rn";
|
|
13
|
-
import Icon from "react-native-remix-icon";
|
|
14
13
|
|
|
15
14
|
import { Organization } from "./Organization";
|
|
16
15
|
|
|
17
|
-
import { LineLoader } from "../../components";
|
|
18
16
|
import { useLocalization } from "../../contexts/localization";
|
|
19
17
|
import { moderateScale } from "../../helpers/scale";
|
|
20
18
|
import { useAuth } from "../../hooks/query/useAuth";
|
|
@@ -57,45 +55,35 @@ export const SubDomainScreen = ({
|
|
|
57
55
|
org.subdomain.toLowerCase().includes(searchTermLowerCase)
|
|
58
56
|
);
|
|
59
57
|
|
|
60
|
-
const
|
|
61
|
-
<Container style={styles.
|
|
58
|
+
const renderIntro = () => (
|
|
59
|
+
<Container style={styles.intro}>
|
|
62
60
|
<Typography
|
|
63
|
-
color={lightTheme.colors.palette.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
weight="
|
|
61
|
+
color={lightTheme.colors.palette.grey600}
|
|
62
|
+
size="s"
|
|
63
|
+
style={styles.introDescription}
|
|
64
|
+
weight="regular"
|
|
67
65
|
>
|
|
68
|
-
{strings.commons.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<Typography
|
|
72
|
-
color={lightTheme.colors.palette.grey600}
|
|
73
|
-
size="s"
|
|
74
|
-
style={styles.headerDescription}
|
|
75
|
-
weight="regular"
|
|
76
|
-
>
|
|
77
|
-
{strings.commons.subdomain.description1}
|
|
78
|
-
<Typography color={lightTheme.colors.text.primary} weight="bold">
|
|
79
|
-
{organizations[0].email}
|
|
80
|
-
</Typography>
|
|
81
|
-
{strings.commons.subdomain.description2}
|
|
66
|
+
{strings.commons.subdomain.description1}
|
|
67
|
+
<Typography color={lightTheme.colors.text.primary} weight="bold">
|
|
68
|
+
{organizations[0].email}
|
|
82
69
|
</Typography>
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
70
|
+
{strings.commons.subdomain.description2}
|
|
71
|
+
</Typography>
|
|
72
|
+
{/*
|
|
73
|
+
v3's SearchBar owns its text; it has no `value` prop, so the screen
|
|
74
|
+
only mirrors it through onChangeText.
|
|
75
|
+
*/}
|
|
76
|
+
<SearchBar
|
|
77
|
+
// `formatString` returns a plain string whenever every argument is
|
|
78
|
+
// one, which is the case here.
|
|
79
|
+
placeholder={
|
|
80
|
+
strings.formatString(
|
|
81
|
+
strings.commons.placeholders.search,
|
|
82
|
+
strings.commons.labels.Workspaces.toLowerCase()
|
|
83
|
+
) as string
|
|
84
|
+
}
|
|
85
|
+
onChangeText={setSearchTerm}
|
|
86
|
+
/>
|
|
99
87
|
</Container>
|
|
100
88
|
);
|
|
101
89
|
|
|
@@ -105,22 +93,15 @@ export const SubDomainScreen = ({
|
|
|
105
93
|
barStyle="dark-content"
|
|
106
94
|
style={styles.screen}
|
|
107
95
|
>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
onPress={onBackPress}
|
|
116
|
-
>
|
|
117
|
-
<Icon name="arrow-left-s-line" size={moderateScale(24)} />
|
|
118
|
-
</Touchable>
|
|
119
|
-
</Container>
|
|
120
|
-
<LineLoader isLoading={isSelectingOrganization} />
|
|
121
|
-
</Container>
|
|
96
|
+
{/* Header carries the progress line the screen used to draw with its
|
|
97
|
+
own LineLoader. */}
|
|
98
|
+
<Header
|
|
99
|
+
isLoading={isSelectingOrganization}
|
|
100
|
+
title={strings.commons.headers.subDomain}
|
|
101
|
+
onBack={onBackPress}
|
|
102
|
+
/>
|
|
122
103
|
<Container style={styles.body}>
|
|
123
|
-
{
|
|
104
|
+
{renderIntro()}
|
|
124
105
|
<FlashList
|
|
125
106
|
contentContainerStyle={styles.contentContainerStyle}
|
|
126
107
|
data={filteredOrganizations}
|
|
@@ -142,29 +123,16 @@ const styles = StyleSheet.create({
|
|
|
142
123
|
screen: {
|
|
143
124
|
flex: 1,
|
|
144
125
|
},
|
|
145
|
-
navBar: {
|
|
146
|
-
alignItems: "center",
|
|
147
|
-
flexDirection: "row",
|
|
148
|
-
height: moderateScale(44),
|
|
149
|
-
paddingHorizontal: moderateScale(16),
|
|
150
|
-
},
|
|
151
|
-
backButton: {
|
|
152
|
-
marginLeft: moderateScale(-8),
|
|
153
|
-
},
|
|
154
126
|
body: {
|
|
155
127
|
flex: 1,
|
|
156
128
|
marginHorizontal: moderateScale(16),
|
|
157
|
-
marginTop: moderateScale(
|
|
129
|
+
marginTop: moderateScale(4),
|
|
158
130
|
},
|
|
159
|
-
|
|
131
|
+
intro: {
|
|
160
132
|
justifyContent: "center",
|
|
161
133
|
marginBottom: 18,
|
|
162
134
|
},
|
|
163
|
-
|
|
164
|
-
lineHeight: moderateScale(28),
|
|
165
|
-
marginBottom: moderateScale(8),
|
|
166
|
-
},
|
|
167
|
-
headerDescription: {
|
|
135
|
+
introDescription: {
|
|
168
136
|
marginBottom: moderateScale(8),
|
|
169
137
|
},
|
|
170
138
|
flatListStyle: {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { type ReactNode } from "react";
|
|
2
2
|
import { Linking, ScrollView, StyleSheet } from "react-native";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import Help from "@bigbinary/neeto-icons-rn/icons/Help";
|
|
5
|
+
import LeftArrow from "@bigbinary/neeto-icons-rn/icons/LeftArrow";
|
|
5
6
|
import {
|
|
6
7
|
Typography,
|
|
7
8
|
Container,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { type ComponentType } from "react";
|
|
2
2
|
import { StyleSheet, View } from "react-native";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import MenuVertical from "@bigbinary/neeto-icons-rn/icons/MenuVertical";
|
|
5
5
|
import {
|
|
6
6
|
Avatar as UIAvatar,
|
|
7
7
|
Container,
|
|
@@ -2,7 +2,7 @@ import React, { useState, type ComponentType } from "react";
|
|
|
2
2
|
import { Pressable, StyleSheet } from "react-native";
|
|
3
3
|
|
|
4
4
|
import { findIndexBy } from "@bigbinary/neeto-cist";
|
|
5
|
-
import
|
|
5
|
+
import Down from "@bigbinary/neeto-icons-rn/icons/Down";
|
|
6
6
|
import {
|
|
7
7
|
BottomSheet as UIBottomSheet,
|
|
8
8
|
Container,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type BottomSheetProps,
|
|
14
14
|
} from "@bigbinary/neetoui-rn";
|
|
15
15
|
import { type FormikProps } from "formik";
|
|
16
|
-
import
|
|
16
|
+
import mergeRight from "ramda/src/mergeRight";
|
|
17
17
|
|
|
18
18
|
import Checkbox from "./Checkbox";
|
|
19
19
|
import { APP_NAME } from "./constants";
|
|
@@ -32,11 +32,10 @@ interface RoleSheetItem {
|
|
|
32
32
|
value: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// `
|
|
35
|
+
// Pins the sheet's row type — `data` can be undefined mid-close, which
|
|
36
|
+
// blocks inference at the call site.
|
|
36
37
|
const BottomSheet = UIBottomSheet as ComponentType<
|
|
37
|
-
|
|
38
|
-
isVisible: boolean | null;
|
|
39
|
-
}
|
|
38
|
+
BottomSheetProps<RoleSheetItem>
|
|
40
39
|
>;
|
|
41
40
|
|
|
42
41
|
interface ProductSelectProps {
|
|
@@ -52,7 +51,7 @@ const ProductSelect = ({
|
|
|
52
51
|
setFieldValue,
|
|
53
52
|
}: ProductSelectProps) => {
|
|
54
53
|
const otherProducts = values?.otherProducts ?? true;
|
|
55
|
-
const [openDropdown, setOpenDropdown] = useState
|
|
54
|
+
const [openDropdown, setOpenDropdown] = useState(false);
|
|
56
55
|
const [selectedAppName, setSelectedAppName] = useState<string | null>(null);
|
|
57
56
|
|
|
58
57
|
const updateProduct = (appName: string, object: Partial<ProductRole>) => {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useCallback, useState } from "react";
|
|
2
2
|
import { StyleSheet } from "react-native";
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import Delete from "@bigbinary/neeto-icons-rn/icons/Delete";
|
|
5
|
+
import Edit from "@bigbinary/neeto-icons-rn/icons/Edit";
|
|
5
6
|
import {
|
|
6
7
|
Container,
|
|
7
8
|
Divider,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { _findBy, notEquals } from "@bigbinary/neeto-cist";
|
|
2
|
-
import
|
|
2
|
+
import equals from "ramda/src/equals";
|
|
3
|
+
import includes from "ramda/src/includes";
|
|
4
|
+
import move from "ramda/src/move";
|
|
3
5
|
|
|
4
6
|
import { ADMIN_ROLE_NAME, ADMIN_ROLES, APP_NAME } from "./constants";
|
|
5
7
|
|
package/src/store/index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
configureStore,
|
|
4
|
+
type StoreEnhancer,
|
|
5
|
+
type UnknownAction,
|
|
6
|
+
} from "@reduxjs/toolkit";
|
|
3
7
|
import {
|
|
4
8
|
persistStore,
|
|
5
9
|
persistReducer,
|
|
@@ -27,6 +31,67 @@ const persistConfig: PersistConfig<CommonsState> = {
|
|
|
27
31
|
|
|
28
32
|
const persistedReducer = persistReducer(persistConfig, rootReducer);
|
|
29
33
|
|
|
34
|
+
// The enhancer must exist when configureStore runs — at module-import time —
|
|
35
|
+
// so the store wires it itself; host apps could never inject it early enough.
|
|
36
|
+
// The guarded require keeps hosts without the plugin working (it is a dev
|
|
37
|
+
// tool the host may not install), and metro strips the whole branch from
|
|
38
|
+
// release bundles because __DEV__ is inlined before dependency collection.
|
|
39
|
+
const devToolsEnhancers = () => {
|
|
40
|
+
if (__DEV__) {
|
|
41
|
+
try {
|
|
42
|
+
// The require is untyped on purpose (consistent-type-imports forbids
|
|
43
|
+
// import() annotations); cast just the surface used here.
|
|
44
|
+
const { rozeniteDevToolsEnhancer } =
|
|
45
|
+
require("@rozenite/redux-devtools-plugin") as {
|
|
46
|
+
rozeniteDevToolsEnhancer: (options: {
|
|
47
|
+
name?: string;
|
|
48
|
+
maxAge?: number;
|
|
49
|
+
actionSanitizer?: (action: UnknownAction, id: number) => unknown;
|
|
50
|
+
stateSanitizer?: (state: unknown, index: number) => unknown;
|
|
51
|
+
}) => StoreEnhancer;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// The workspace switcher keeps a per-organization auth token in the
|
|
55
|
+
// store (every other credential lives in the keychain). The DevTools
|
|
56
|
+
// bridge rides metro's websocket, so those tokens must not cross it.
|
|
57
|
+
const redactOrganizationTokens = (state: unknown) => {
|
|
58
|
+
const snapshot = state as CommonsState | undefined;
|
|
59
|
+
if (!snapshot?.auth?.organizations?.length) return state;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
...snapshot,
|
|
63
|
+
auth: {
|
|
64
|
+
...snapshot.auth,
|
|
65
|
+
organizations: snapshot.auth.organizations.map(organization => ({
|
|
66
|
+
...organization,
|
|
67
|
+
auth_token: "<<redacted>>",
|
|
68
|
+
})),
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return [
|
|
74
|
+
rozeniteDevToolsEnhancer({
|
|
75
|
+
name: "neeto-commons-rn store",
|
|
76
|
+
// The whole persisted tree rides through REHYDRATE; big snapshots
|
|
77
|
+
// can OOM the Android DevTools panel, so cap history and elide the
|
|
78
|
+
// payload.
|
|
79
|
+
maxAge: 25,
|
|
80
|
+
actionSanitizer: action =>
|
|
81
|
+
action.type === REHYDRATE
|
|
82
|
+
? { ...action, payload: "<<elided: persisted state>>" }
|
|
83
|
+
: action,
|
|
84
|
+
stateSanitizer: redactOrganizationTokens,
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [];
|
|
93
|
+
};
|
|
94
|
+
|
|
30
95
|
const baseStore = configureStore({
|
|
31
96
|
reducer: persistedReducer,
|
|
32
97
|
middleware: getDefaultMiddleware =>
|
|
@@ -35,6 +100,8 @@ const baseStore = configureStore({
|
|
|
35
100
|
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
|
36
101
|
},
|
|
37
102
|
}),
|
|
103
|
+
enhancers: getDefaultEnhancers =>
|
|
104
|
+
getDefaultEnhancers().concat(devToolsEnhancers()),
|
|
38
105
|
});
|
|
39
106
|
const Persistor = persistStore(baseStore);
|
|
40
107
|
|
package/src/utils/apiHelpers.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
// `appName` doubles as user-facing copy ("Update neetoDesk"), so it keeps its
|
|
2
|
+
// camel case. Hosts are case-insensitive, but the mixed-case spelling shows up
|
|
3
|
+
// verbatim in request logs and Bugsnag error groups, so lowercase it here.
|
|
1
4
|
export const appVersionsUrl = ({
|
|
2
5
|
appName,
|
|
3
6
|
environment,
|
|
4
7
|
}: {
|
|
5
8
|
appName: string;
|
|
6
9
|
environment: string;
|
|
7
|
-
}) => `https://api.${appName}.${environment}/app_versions`;
|
|
10
|
+
}) => `https://api.${appName.toLowerCase()}.${environment}/app_versions`;
|