@janiscommerce/app-tracking-shift 1.0.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.
@@ -0,0 +1,151 @@
1
+ import Request from './utils/request';
2
+
3
+ class StaffApiServices {
4
+ constructor() {
5
+ this.service = 'staff';
6
+ }
7
+
8
+ /**
9
+ * Opens an user's shift
10
+ * @returns {Promise<Object>} - Response from the API
11
+ */
12
+
13
+ async openShift() {
14
+ try {
15
+ return await Request.post({
16
+ service: this.service,
17
+ namespace: 'shift-open',
18
+ });
19
+ } catch (error) {
20
+ return Promise.reject(error);
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Closes current user's shift
26
+ * @returns {Promise<Object>} - Response from the API
27
+ */
28
+
29
+ async closeShift() {
30
+ try {
31
+ return await Request.post({
32
+ service: this.service,
33
+ namespace: 'shift-close',
34
+ });
35
+ } catch (error) {
36
+ return Promise.reject(error);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Posts work logs to the API
42
+ * @param {Object|Array<Object>} workLogs - Work log object or array of work log objects
43
+ * @param {string} workLogs.workLogTypeRefId - Reference ID of the work log type
44
+ * @param {Date} workLogs.startDate - Start date and time of the work log
45
+ * @param {Date} workLogs.endDate - End date and time of the work log
46
+ * @returns {Promise<Object>} - Response from the API
47
+ * @example
48
+ * // Single work log object
49
+ * postWorklog({
50
+ * workLogTypeRefId: "123",
51
+ * startDate: new Date("2024-01-01T09:00:00"),
52
+ * endDate: new Date("2024-01-01T17:00:00")
53
+ * });
54
+ *
55
+ * // Array of work log objects
56
+ * postWorklog([
57
+ * {
58
+ * workLogTypeRefId: "123",
59
+ * startDate: new Date("2024-01-01T09:00:00"),
60
+ * endDate: new Date("2024-01-01T12:00:00")
61
+ * },
62
+ * {
63
+ * workLogTypeRefId: "456",
64
+ * startDate: new Date("2024-01-01T13:00:00"),
65
+ * endDate: new Date("2024-01-01T17:00:00")
66
+ * }
67
+ * ]);
68
+ */
69
+ async postWorklog(workLogs) {
70
+ try {
71
+ if (!Array.isArray(workLogs)) {
72
+ workLogs = [workLogs];
73
+ }
74
+
75
+ return await Request.post({
76
+ service: this.service,
77
+ namespace: 'work-log',
78
+ body: [...workLogs],
79
+ });
80
+ } catch (error) {
81
+ return Promise.reject(error);
82
+ }
83
+ }
84
+
85
+ async getShiftsList(params) {
86
+ const {filters, sort} = params || {};
87
+ try {
88
+ return await Request.list({
89
+ service: this.service,
90
+ namespace: 'shift',
91
+ queryParams: {
92
+ ...(filters && {filters}),
93
+ ...(sort && {sort}),
94
+ },
95
+ });
96
+ } catch (error) {
97
+ return Promise.reject(error);
98
+ }
99
+ }
100
+
101
+ async getWorkLogTypes() {
102
+ try {
103
+ return await Request.list({
104
+ service: this.service,
105
+ namespace: 'work-log-type',
106
+ headers: {
107
+ pageSize: 100,
108
+ },
109
+ queryParams: {
110
+ filters: {
111
+ status: 'active',
112
+ type: ['work', 'pause', 'problem'],
113
+ isInternal: 'false',
114
+ },
115
+ },
116
+ });
117
+ } catch (error) {
118
+ return Promise.reject(error);
119
+ }
120
+ }
121
+
122
+ async getSetting(setting) {
123
+ try {
124
+ return await Request.get({
125
+ service: this.service,
126
+ namespace: 'setting',
127
+ pathParams: [setting],
128
+ });
129
+ } catch (error) {
130
+ return Promise.reject(error);
131
+ }
132
+ }
133
+
134
+ async getWorkLogsList(params) {
135
+ const {filters, sort} = params || {};
136
+ try {
137
+ return await Request.list({
138
+ service: this.service,
139
+ namespace: 'work-log',
140
+ queryParams: {
141
+ ...(filters && {filters}),
142
+ ...(sort && {sort}),
143
+ },
144
+ });
145
+ } catch (error) {
146
+ return Promise.reject(error);
147
+ }
148
+ }
149
+ }
150
+
151
+ export default new StaffApiServices();
@@ -0,0 +1,103 @@
1
+ import TimeTracker from './db/TimeTrackerService';
2
+ import {reverseArray} from './utils/helpers';
3
+
4
+ class TrackerRecords {
5
+ async getWorkLogsFromTimeTracker(filteredId) {
6
+ try {
7
+ if (!filteredId) throw new Error(`Excluding ID is required, but got ${filteredId}`);
8
+
9
+ const events = await this._filterEventsExcludingId(filteredId);
10
+
11
+ return events;
12
+ } catch (error) {
13
+ return Promise.reject(error);
14
+ }
15
+ }
16
+
17
+ async getClientShiftActivities(id) {
18
+ try {
19
+ const query = 'NOT (id CONTAINS[c] "picking" OR id CONTAINS[c] "delivery") AND id != $0';
20
+ const clientActivities = await TimeTracker.searchEventByQuery(query, id);
21
+
22
+ return clientActivities;
23
+ } catch (error) {
24
+ return Promise.reject(error);
25
+ }
26
+ }
27
+
28
+ async getStartDateById(id) {
29
+ try {
30
+ if (!id) throw new Error(`ID is required, but got ${id}`);
31
+
32
+ const startShiftEvent = await this._getStartEventById(id);
33
+
34
+ return startShiftEvent?.time;
35
+ } catch (error) {
36
+ return Promise.reject(error);
37
+ }
38
+ }
39
+
40
+ async getEndDateById(id) {
41
+ try {
42
+ if (!id) throw new Error(`ID is required, but got ${id}`);
43
+
44
+ const endShiftEvent = await this._getFinishEventById(id);
45
+
46
+ return endShiftEvent?.time;
47
+ } catch (error) {
48
+ return Promise.reject(error);
49
+ }
50
+ }
51
+
52
+ async _getStartEventById(id) {
53
+ try {
54
+ const startEvent = await this._filterEventByType(id, 'start');
55
+
56
+ const [firstEvent = {}] = startEvent;
57
+
58
+ return firstEvent;
59
+ } catch (error) {
60
+ return Promise.reject(error);
61
+ }
62
+ }
63
+
64
+ async _getFinishEventById(id) {
65
+ try {
66
+ const finishEvent = await this._filterEventByType(id, 'finish');
67
+
68
+ const [lastEvent = {}] = reverseArray(finishEvent);
69
+
70
+ return lastEvent;
71
+ } catch (error) {
72
+ return Promise.reject(error);
73
+ }
74
+ }
75
+
76
+ async _filterEventByType(id, type) {
77
+ try {
78
+ if (!id) throw new Error('id is required');
79
+ if (!type) throw new Error('type is required');
80
+
81
+ const events = await TimeTracker.searchEventByQuery('id == $0 AND type == $1', id, type);
82
+ console.log('events', events);
83
+
84
+ return events;
85
+ } catch (error) {
86
+ return Promise.reject(error);
87
+ }
88
+ }
89
+
90
+ async _filterEventsExcludingId(id) {
91
+ try {
92
+ if (!id) throw new Error('id is required');
93
+
94
+ const events = await TimeTracker.searchEventByQuery('id!= $0', id);
95
+
96
+ return events;
97
+ } catch (error) {
98
+ return Promise.reject(error);
99
+ }
100
+ }
101
+ }
102
+
103
+ export default new TrackerRecords();
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import {useShiftTracking} from '../../context/ShiftTrackingContext';
3
+
4
+ /**
5
+ * @param {React.Component} WrappedComponent - The component to wrap
6
+ * @param {Object} options - The options for the component
7
+ * @param {React.Component} options.pausedShiftComponent - The component to render when the shift is paused
8
+ * @returns {React.Component} - The wrapped component
9
+ *
10
+ * @example
11
+ * const MyComponent = () => {
12
+ * return <div>My Component</div>;
13
+ * };
14
+ *
15
+ * export default WithShiftTracking(MyComponent, {
16
+ * pausedShiftComponent: <div>Paused Shift</div>,
17
+ * });
18
+ */
19
+
20
+ const WithShiftTracking = (WrappedComponent, options = {}) => {
21
+ const {pausedShiftComponent} = options;
22
+
23
+ return (props) => {
24
+ const shiftTrackingData = useShiftTracking();
25
+ const showPause = shiftTrackingData?.shiftStatus === 'paused' && pausedShiftComponent;
26
+
27
+ return (
28
+ <>
29
+ {showPause && pausedShiftComponent}
30
+ <WrappedComponent {...props} shiftData={shiftTrackingData} />
31
+ </>
32
+ );
33
+ };
34
+ };
35
+
36
+ export default WithShiftTracking;
@@ -0,0 +1,33 @@
1
+ // STORAGE KEYS
2
+ export const SHIFT_ID = 'shift.id';
3
+ export const SHIFT_STATUS = 'shift.status';
4
+ export const SHIFT_DATA = 'shift.data';
5
+ export const WORKLOG_TYPES_DATA = 'worklogTypes.data';
6
+ export const CURRENT_WORKLOG_ID = 'worklog.id';
7
+ export const CURRENT_WORKLOG_DATA = 'worklog.data';
8
+ export const STAFF_AUTH = 'staff.authorization';
9
+ export const OFFLINE_DATA = 'offline.data';
10
+
11
+ // EXPIRATION TIMES
12
+ export const WORKLOG_TYPES_EXPIRATION_TIME = 4 * 60 * 60 * 1000; // 4 hours
13
+ export const STAFF_MS_AUTHORIZATION_EXPIRATION_TIME = 24 * 60 * 60 * 1000; // 24 hour
14
+
15
+ // EXCLUDED WORKLOG TYPES
16
+ export const EXCLUDED_WORKLOG_TYPES = ['default-picking-work', 'default-delivery-work'];
17
+
18
+ // INTERNAL DEFAULT WORKLOGS
19
+ export const INTERNAL_WORKLOGS = {
20
+ PICKING_WORK: {
21
+ referenceId: 'default-picking-work',
22
+ type: 'work',
23
+ name: 'Default picking work',
24
+ },
25
+ DELIVERY_WORK: {
26
+ referenceId: 'default-delivery-work',
27
+ type: 'work',
28
+ name: 'Default delivery work',
29
+ },
30
+ };
31
+
32
+ // SHIFT CLOSE EXTENSION
33
+ export const ONE_HOUR_EXTENSION = 60 * 60 * 1000; // 1 hour
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ const ShiftTrackingContext = React.createContext();
4
+
5
+ export const useShiftTracking = () => React.useContext(ShiftTrackingContext);
6
+
7
+ export default ShiftTrackingContext;
@@ -0,0 +1,5 @@
1
+ import {MMKV} from 'react-native-mmkv';
2
+
3
+ const Storage = new MMKV();
4
+
5
+ export default Storage;
@@ -0,0 +1,5 @@
1
+ import EventTrackingService from '@janiscommerce/app-tracking-time';
2
+
3
+ const TimeTrackerService = new EventTrackingService('shift-tracking');
4
+
5
+ export default TimeTrackerService;
@@ -0,0 +1,19 @@
1
+ import {useMMKVString} from 'react-native-mmkv';
2
+ import {useMemo} from 'react';
3
+ import Crashlytics from '../../utils/crashlytics';
4
+
5
+ export const useMMKVObject = (key, defaultValue = null) => {
6
+ const [raw] = useMMKVString(key);
7
+
8
+ const storageValue = useMemo(() => {
9
+ if (!raw) return defaultValue;
10
+ try {
11
+ return JSON.parse(raw);
12
+ } catch (e) {
13
+ Crashlytics.recordError(e, `Invalid JSON in MMKV key: ${key}`);
14
+ return defaultValue;
15
+ }
16
+ }, [raw]);
17
+
18
+ return [storageValue];
19
+ };
package/lib/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import Shift from './Shift';
2
+ import ShiftTrackingProvider from './provider/ShiftTrackingProvider';
3
+ import {useShiftTracking} from './context/ShiftTrackingContext';
4
+ import WithShiftTracking from './components/WithShiftTracking';
5
+ import {INTERNAL_WORKLOGS} from './constant';
6
+
7
+ export {Shift, ShiftTrackingProvider, useShiftTracking, WithShiftTracking, INTERNAL_WORKLOGS};
@@ -0,0 +1,174 @@
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {useMMKVString} from 'react-native-mmkv';
3
+ import ShiftTrackingContext from '../context/ShiftTrackingContext';
4
+ import {
5
+ openShift,
6
+ downloadWorkLogTypes,
7
+ isAuthorizedToUseStaffMS,
8
+ getShiftWorkLogsFromJanis,
9
+ saveWorkLogTimesInDB,
10
+ } from '../utils/provider';
11
+ import {
12
+ CURRENT_WORKLOG_DATA,
13
+ CURRENT_WORKLOG_ID,
14
+ EXCLUDED_WORKLOG_TYPES,
15
+ SHIFT_DATA,
16
+ SHIFT_ID,
17
+ SHIFT_STATUS,
18
+ STAFF_AUTH,
19
+ WORKLOG_TYPES_DATA,
20
+ } from '../constant';
21
+ import {useMMKVObject} from '../hooks/useMMKVObject';
22
+ import {isValidObject, promiseWrapper} from '../utils/helpers';
23
+ import Crashlytics from '../utils/crashlytics';
24
+ import Storage from '../db/StorageService';
25
+
26
+ const ShiftTrackingProvider = ({children, onError = null}) => {
27
+ const [shiftStatus] = useMMKVString(SHIFT_STATUS);
28
+ const [shiftId] = useMMKVString(SHIFT_ID);
29
+ const [shiftData] = useMMKVObject(SHIFT_DATA, {});
30
+
31
+ const [currentWorkLogId] = useMMKVString(CURRENT_WORKLOG_ID);
32
+ const [workLogData] = useMMKVObject(WORKLOG_TYPES_DATA, {});
33
+ const [currentWorkLogData] = useMMKVObject(CURRENT_WORKLOG_DATA, {});
34
+
35
+ const [staffAuthData] = useMMKVObject(STAFF_AUTH, {});
36
+
37
+ const [error, setError] = useState(null);
38
+ const [openShiftResult, setOpenShiftResult] = useState({
39
+ id: null,
40
+ getWorkLogs: false,
41
+ });
42
+ const [isShiftLoading, setIsShiftLoading] = useState(false);
43
+
44
+ const {workLogTypes = []} = workLogData;
45
+ const {hasStaffAuthorization = false} = staffAuthData;
46
+
47
+ const contextValues = useMemo(() => {
48
+ return {
49
+ shiftId,
50
+ shiftStatus,
51
+ shiftData,
52
+ workLogTypes,
53
+ currentWorkLogData,
54
+ currentWorkLogId,
55
+ hasStaffAuthorization,
56
+ error,
57
+ isShiftLoading,
58
+ };
59
+ }, [
60
+ shiftId,
61
+ shiftStatus,
62
+ shiftData,
63
+ workLogTypes,
64
+ currentWorkLogData,
65
+ currentWorkLogId,
66
+ error,
67
+ hasStaffAuthorization,
68
+ isShiftLoading,
69
+ ]);
70
+
71
+ const handleShiftTrackingInit = async () => {
72
+ setIsShiftLoading(true);
73
+ const [isAuthorized, authError] = await promiseWrapper(isAuthorizedToUseStaffMS());
74
+
75
+ if (authError) {
76
+ setIsShiftLoading(false);
77
+ setError({
78
+ message: authError?.message,
79
+ type: 'staffMSAuthorization',
80
+ });
81
+ return;
82
+ }
83
+
84
+ if (!isAuthorized) {
85
+ setIsShiftLoading(false);
86
+ return;
87
+ }
88
+
89
+ const [shiftResult, openError] = await promiseWrapper(openShift(onError));
90
+
91
+ if (openError) {
92
+ setIsShiftLoading(false);
93
+ setError({
94
+ message: openError?.message,
95
+ type: 'openShift',
96
+ });
97
+ return;
98
+ }
99
+
100
+ const {openShiftId, getWorkLogs} = shiftResult;
101
+
102
+ setOpenShiftResult((prev) => ({
103
+ ...prev,
104
+ id: openShiftId,
105
+ getWorkLogs,
106
+ }));
107
+
108
+ const [, downloadError] = await promiseWrapper(downloadWorkLogTypes(onError));
109
+
110
+ setIsShiftLoading(false);
111
+ if (downloadError) {
112
+ setError({
113
+ message: downloadError?.message,
114
+ type: 'downloadWorkLogTypes',
115
+ });
116
+ }
117
+ };
118
+
119
+ const getShiftWorkLogsHistory = async () => {
120
+ const [workLogs, workLogsError] = await promiseWrapper(
121
+ getShiftWorkLogsFromJanis(openShiftResult.id)
122
+ );
123
+
124
+ if (workLogsError) {
125
+ setError({
126
+ message: workLogsError?.message,
127
+ type: 'getWorkLogsFromJanis',
128
+ });
129
+ return;
130
+ }
131
+
132
+ const {openWorkLogs, closedWorkLogs} = workLogs;
133
+ const [currentWorkLog = {}] = openWorkLogs;
134
+ const isExcludedWork = EXCLUDED_WORKLOG_TYPES.includes(currentWorkLog?.referenceId);
135
+
136
+ if (isValidObject(currentWorkLog)) {
137
+ Storage.set(CURRENT_WORKLOG_ID, currentWorkLog.id);
138
+ Storage.set(CURRENT_WORKLOG_DATA, JSON.stringify(currentWorkLog));
139
+ }
140
+
141
+ if (isValidObject(currentWorkLog) && !isExcludedWork) {
142
+ Storage.set(SHIFT_STATUS, 'paused');
143
+ }
144
+
145
+ const promises = [currentWorkLog, ...closedWorkLogs].map((workLog) =>
146
+ saveWorkLogTimesInDB(workLog).catch((workLogError) => {
147
+ Crashlytics.recordError(workLogError, 'error trying to save work log times in db', workLog);
148
+ })
149
+ );
150
+
151
+ await Promise.all(promises);
152
+
153
+ setOpenShiftResult((prev) => ({
154
+ ...prev,
155
+ getWorkLogs: false,
156
+ }));
157
+ };
158
+
159
+ useEffect(() => {
160
+ handleShiftTrackingInit();
161
+ }, []);
162
+
163
+ useEffect(() => {
164
+ if (openShiftResult?.id && openShiftResult?.getWorkLogs) {
165
+ getShiftWorkLogsHistory();
166
+ }
167
+ }, [openShiftResult]);
168
+
169
+ return (
170
+ <ShiftTrackingContext.Provider value={contextValues}>{children}</ShiftTrackingContext.Provider>
171
+ );
172
+ };
173
+
174
+ export default ShiftTrackingProvider;
@@ -0,0 +1,5 @@
1
+ import CrashlyticsClass from '@janiscommerce/app-crashlytics';
2
+
3
+ const Crashlytics = new CrashlyticsClass();
4
+
5
+ export default Crashlytics;
@@ -0,0 +1,20 @@
1
+ export const generateRandomId = () => Math.random().toString(32).slice(2);
2
+
3
+ export const isFunction = (fn) => !!({}.toString.call(fn) === '[object Function]');
4
+
5
+ export const isObject = (obj) => !!(obj && obj.constructor === Object);
6
+
7
+ export const isEmptyObject = (obj) => isObject(obj) && !Object.keys(obj).length;
8
+
9
+ export const promiseWrapper = (promise) =>
10
+ promise.then((data) => [data, null]).catch((error) => Promise.resolve([null, error]));
11
+
12
+ export const isArray = (arr) => Array.isArray(arr);
13
+
14
+ export const isEmptyArray = (arr) => isArray(arr) && !arr.length;
15
+
16
+ export const reverseArray = (arr) => arr.slice().reverse();
17
+
18
+ export const isNumber = (num) => typeof num === 'number' && !Number.isNaN(Number(num));
19
+
20
+ export const isValidObject = (obj) => isObject(obj) && !!Object.keys(obj).length;
@@ -0,0 +1,30 @@
1
+ import Shift from '../../../Shift';
2
+ import {getWorkLogTypesData} from '../../storage';
3
+ import {WORKLOG_TYPES_DATA, WORKLOG_TYPES_EXPIRATION_TIME} from '../../../constant';
4
+ import Storage from '../../../db/StorageService';
5
+ import Crashlytics from '../../crashlytics';
6
+ import {isFunction} from '../../helpers';
7
+
8
+ const downloadWorkLogTypes = async (onDownloadError) => {
9
+ try {
10
+ const {isExpired} = getWorkLogTypesData();
11
+
12
+ if (!isExpired) return null;
13
+
14
+ const workLogTypes = await Shift.fetchWorklogTypes();
15
+
16
+ const data = {
17
+ workLogTypes,
18
+ expirationTime: Date.now() + WORKLOG_TYPES_EXPIRATION_TIME,
19
+ };
20
+
21
+ Storage.set(WORKLOG_TYPES_DATA, JSON.stringify(data));
22
+ return null;
23
+ } catch (error) {
24
+ Crashlytics.recordError(error, 'Error downloading worklog types');
25
+ if (isFunction(onDownloadError)) onDownloadError(error);
26
+ return Promise.reject(error?.result || error);
27
+ }
28
+ };
29
+
30
+ export default downloadWorkLogTypes;
@@ -0,0 +1,18 @@
1
+ import Formatter from '../../../Formatter';
2
+ import Shift from '../../../Shift';
3
+
4
+ const getShiftWorkLogsFromJanis = async (shiftId) => {
5
+ try {
6
+ const formattedWorkLogs = await Shift.getWorkLogs(shiftId);
7
+ const [openWorkLogs, closedWorkLogs] = Formatter.splitWorkLogsByStatus(formattedWorkLogs);
8
+
9
+ return {
10
+ openWorkLogs,
11
+ closedWorkLogs,
12
+ };
13
+ } catch (error) {
14
+ return Promise.reject(error);
15
+ }
16
+ };
17
+
18
+ export default getShiftWorkLogsFromJanis;
@@ -0,0 +1,5 @@
1
+ export {default as downloadWorkLogTypes} from './downloadWorkLogTypes';
2
+ export {default as openShift} from './openShift';
3
+ export {default as isAuthorizedToUseStaffMS} from './isAuthorizedToUseStaffMS';
4
+ export {default as getShiftWorkLogsFromJanis} from './getShiftWorkLogsFromJanis';
5
+ export {default as saveWorkLogTimesInDB} from './saveWorkLogTimesInDB';
@@ -0,0 +1,39 @@
1
+ import Crashlytics from '../../crashlytics';
2
+ import {STAFF_AUTH, STAFF_MS_AUTHORIZATION_EXPIRATION_TIME} from '../../../constant';
3
+ import Storage from '../../../db/StorageService';
4
+ import Shift from '../../../Shift';
5
+ import {getStaffAuthorizationData} from '../../storage';
6
+
7
+ const isAuthorizedToUseStaffMS = async () => {
8
+ try {
9
+ Crashlytics.log('checking staff MS authorization');
10
+ const {hasStaffAuthorization, isExpired} = getStaffAuthorizationData();
11
+
12
+ if (hasStaffAuthorization && !isExpired) return true;
13
+ if (!hasStaffAuthorization && !isExpired) return false;
14
+
15
+ const isAuthorized = await Shift.checkStaffMSAuthorization();
16
+
17
+ const data = {
18
+ hasStaffAuthorization: isAuthorized,
19
+ expirationTime: Date.now() + STAFF_MS_AUTHORIZATION_EXPIRATION_TIME,
20
+ isExpired: false,
21
+ };
22
+
23
+ Storage.set(STAFF_AUTH, JSON.stringify(data));
24
+
25
+ return isAuthorized;
26
+ } catch (error) {
27
+ Crashlytics.recordError(error, 'Error checking staff MS authorization');
28
+ const data = {
29
+ hasStaffAuthorization: false,
30
+ expirationTime: 0,
31
+ isExpired: true,
32
+ };
33
+ Storage.set(STAFF_AUTH, JSON.stringify(data));
34
+
35
+ return Promise.reject(error);
36
+ }
37
+ };
38
+
39
+ export default isAuthorizedToUseStaffMS;