@eohjsc/react-native-smart-city 0.3.97 → 0.3.99

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 (42) hide show
  1. package/package.json +1 -3
  2. package/src/commons/Dashboard/MyUnit/index.js +8 -2
  3. package/src/commons/Device/WindSpeed/Anemometer/index.js +19 -20
  4. package/src/commons/Device/WindSpeed/__test__/Anemometer.test.js +21 -2
  5. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +3 -4
  6. package/src/commons/SubUnit/OneTap/index.js +3 -1
  7. package/src/configs/AccessibilityLabel.js +1 -0
  8. package/src/configs/Constants.js +1 -0
  9. package/src/context/actionType.ts +1 -0
  10. package/src/context/mockStore.ts +3 -0
  11. package/src/context/reducer.ts +7 -1
  12. package/src/screens/ActivityLog/hooks/index.js +5 -1
  13. package/src/screens/AddCommon/SelectUnit.js +1 -0
  14. package/src/screens/AddNewGateway/ScanGatewayQR.js +0 -5
  15. package/src/screens/AddNewGateway/SelectDeviceType.js +7 -3
  16. package/src/screens/AddNewGateway/__test__/ScanGatewayQR.test.js +0 -1
  17. package/src/screens/AddNewGateway/__test__/SelectDeviceType.test.js +19 -12
  18. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +7 -16
  19. package/src/screens/Automate/AddNewAction/__test__/SetupSensor.test.js +5 -19
  20. package/src/screens/Automate/MultiUnits.js +8 -2
  21. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +3 -4
  22. package/src/screens/Automate/ScriptDetail/index.js +3 -1
  23. package/src/screens/Automate/__test__/MultiUnits.test.js +6 -5
  24. package/src/screens/Automate/__test__/index.test.js +6 -5
  25. package/src/screens/Automate/index.js +8 -2
  26. package/src/screens/Device/detail.js +4 -3
  27. package/src/screens/Device/utils/index.js +2 -1
  28. package/src/screens/Notification/__test__/NotificationItem.test.js +1 -0
  29. package/src/screens/Notification/components/NotificationItem.js +8 -0
  30. package/src/screens/Sharing/MemberList.js +3 -1
  31. package/src/screens/Sharing/__test__/MemberList.test.js +3 -4
  32. package/src/screens/SmartAccount/Connecting/index.js +2 -2
  33. package/src/screens/SmartAccount/Connecting/style.js +1 -1
  34. package/src/screens/SmartAccount/SuccessfullyConnected/index.js +4 -2
  35. package/src/screens/SmartAccount/SuccessfullyConnected/styles.js +3 -0
  36. package/src/screens/SmartAccount/index.js +1 -0
  37. package/src/screens/SubUnit/AddSubUnit.js +16 -0
  38. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +55 -3
  39. package/src/screens/Unit/AddMenu.js +3 -1
  40. package/src/utils/I18n/translations/en.js +16 -16
  41. package/src/utils/I18n/translations/vi.js +16 -13
  42. package/src/utils/Utils.js +6 -6
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.3.97",
4
+ "version": "0.3.99",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -178,7 +178,6 @@
178
178
  "react-native-input-credit-card": "^0.5.5",
179
179
  "react-native-iphone-x-helper": "^1.2.1",
180
180
  "react-native-linear-gradient": "^2.5.6",
181
- "react-native-loading-dots": "^1.3.2",
182
181
  "react-native-localize": "^1.4.1",
183
182
  "react-native-maps": "0.27.1",
184
183
  "react-native-maps-directions": "^1.8.0",
@@ -200,7 +199,6 @@
200
199
  "react-native-svg": "^12.1.0",
201
200
  "react-native-toast-message": "^2.1.1",
202
201
  "react-native-udp": "^4.1.3",
203
- "react-native-unimodules": "^0.11.0",
204
202
  "react-native-version-check": "^3.4.2",
205
203
  "react-native-vlc-media-player": "^1.0.41",
206
204
  "react-native-webview": "11.17.2",
@@ -67,11 +67,17 @@ const MyUnit = ({ refreshing }) => {
67
67
  if (isNeedUpdateCache) {
68
68
  setAction(Action.IS_CHECK_CLEAR_CACHE_UNITS, false);
69
69
  const { success, data } = await axiosGet(API.UNIT.MY_UNITS(), {}, true);
70
- success && setMyUnits(data);
70
+ if (success) {
71
+ setMyUnits(data);
72
+ setAction(Action.SET_MY_UNITS, data);
73
+ }
71
74
  } else {
72
75
  await fetchWithCache(API.UNIT.MY_UNITS(), {}, (response) => {
73
76
  const { success, data } = response;
74
- success && setMyUnits(data);
77
+ if (success) {
78
+ setMyUnits(data);
79
+ setAction(Action.SET_MY_UNITS, data);
80
+ }
75
81
  });
76
82
  }
77
83
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -34,7 +34,7 @@ const Anemometer = memo(
34
34
  }, [item?.configuration?.max_value]);
35
35
 
36
36
  const value = data.length ? data[0].value : 0;
37
- const measure = data.length ? data[0].measure : 'm/s';
37
+ const measure = data.length ? data[0].unit || data[0].measure : 'm/s';
38
38
 
39
39
  const radius = (size - strokeWidth) / 2;
40
40
  const viewBox = `0 0 ${width} ${width}`;
@@ -178,25 +178,24 @@ const Anemometer = memo(
178
178
  </View>
179
179
 
180
180
  <View style={styles.textValue}>
181
- {!!value && (
182
- <Svg width={width} height={width} {...viewBox}>
183
- <Text
184
- fill={Colors.Lime6}
185
- fontSize={valueSize[0]}
186
- fontWeight="bold"
187
- x={width / 2}
188
- y={width / 2 + valueSize[1]}
189
- textAnchor="middle"
190
- fontFamily={Fonts.Regular}
191
- onPressIn={handleShowToolTip(true)}
192
- onPressOut={handleShowToolTip(false)}
193
- >
194
- {value.toString().length > 10
195
- ? value.toString().substring(0, 10) + '...'
196
- : value}
197
- </Text>
198
- </Svg>
199
- )}
181
+ <Svg width={width} height={width} {...viewBox}>
182
+ <Text
183
+ fill={Colors.Lime6}
184
+ fontSize={valueSize[0]}
185
+ fontWeight="bold"
186
+ x={width / 2}
187
+ y={width / 2 + valueSize[1]}
188
+ textAnchor="middle"
189
+ fontFamily={Fonts.Regular}
190
+ onPressIn={handleShowToolTip(true)}
191
+ onPressOut={handleShowToolTip(false)}
192
+ accessibilityLabel={AccessibilityLabel.GAUGE_VALUE}
193
+ >
194
+ {value.toString().length > 10
195
+ ? value.toString().substring(0, 10) + '...'
196
+ : value}
197
+ </Text>
198
+ </Svg>
200
199
  {isShowToolTip && (
201
200
  <View
202
201
  style={{
@@ -19,14 +19,17 @@ describe('Test Anemometer', () => {
19
19
  const text = instance.findAllByType(Text);
20
20
  expect(text.length).toBe(5);
21
21
  });
22
+
22
23
  it('render Anemometer data null', async () => {
23
24
  let data = [];
24
25
  await act(async () => {
25
26
  wrapper = renderer.create(<Anemometer data={data} maxValue={1} />);
26
27
  });
27
28
  const instance = wrapper.root;
28
- const text = instance.findAllByType(Text);
29
- expect(text.length).toBe(4);
29
+ const textValue = instance.findByProps({
30
+ accessibilityLabel: AccessibilityLabel.GAUGE_VALUE,
31
+ });
32
+ expect(textValue.props.children).toEqual(0);
30
33
  });
31
34
 
32
35
  it('test render value font size length < 4', async () => {
@@ -114,4 +117,20 @@ describe('Test Anemometer', () => {
114
117
  });
115
118
  expect(view.props.style.transform[0].translateX).toEqual(-50);
116
119
  });
120
+
121
+ it('test render measure', async () => {
122
+ let data = [
123
+ {
124
+ value: 123456789,
125
+ unit: '%',
126
+ },
127
+ ];
128
+ await act(async () => {
129
+ wrapper = renderer.create(<Anemometer data={data} maxValue={1} />);
130
+ });
131
+ let instance = wrapper.root;
132
+ const text = instance.findAllByType(Text);
133
+ expect(text).toHaveLength(5);
134
+ expect(text[4].props.children).toEqual('%');
135
+ });
117
136
  });
@@ -282,10 +282,9 @@ describe('test Item', () => {
282
282
  });
283
283
  expect(mockedNavigate).not.toBeCalled();
284
284
  expect(spyToastError).toBeCalledWith(
285
- getTranslate(
286
- 'en',
287
- "You've reached the [0] limit. Remove one automation or upgrade your plan for a better experience."
288
- )
285
+ getTranslate('en', 'reach_max_automations_per_unit'),
286
+ '',
287
+ 7000
289
288
  );
290
289
  });
291
290
 
@@ -43,7 +43,9 @@ const SubUnitAutomate = ({ isOwner, listAutomate, unit, wrapItemStyle }) => {
43
43
  ToastBottomHelper.error(
44
44
  t('reach_max_automations_per_unit', {
45
45
  length: permissions.max_automations_per_unit,
46
- })
46
+ }),
47
+ '',
48
+ 7000
47
49
  );
48
50
  return;
49
51
  }
@@ -409,6 +409,7 @@ export default {
409
409
  EMERGENCY_POPUP: 'EMERGENCY_POPUP',
410
410
  RESOLVED_EMERGENCY_POPUP: 'RESOLVED_EMERGENCY_POPUP',
411
411
  TOTAL_POWER_CONSUMPTION: 'TOTAL_POWER_CONSUMPTION',
412
+ GAUGE_VALUE: 'GAUGE_VALUE',
412
413
 
413
414
  // Shared unit
414
415
  ICON_REMOVE_PIN_SHARED_UNIT: 'ICON_REMOVE_PIN_SHARED_UNIT',
@@ -233,6 +233,7 @@ export const NOTIFICATION_TYPES = {
233
233
  FILTER_WATER: 'FILTER_WATER',
234
234
  LOW_BATTERY: 'LOW_BATTERY',
235
235
  BROADCAST_NOTIFICATION: 'BROADCAST_NOTIFICATION',
236
+ SUBSCRIBE_SUCCESS: 'SUBSCRIBE_SUCCESS',
236
237
  };
237
238
 
238
239
  export const ACTIVITY_LOG_TYPES = {
@@ -41,6 +41,7 @@ export const Action = {
41
41
  SET_IS_IN_EDIT_TEMPLATE_SCREEN: 'SET_IS_IN_EDIT_TEMPLATE_SCREEN',
42
42
  LOGOUT: 'LOGOUT',
43
43
  SET_APP_STATE_CHANGE: 'SET_APP_STATE_CHANGE',
44
+ SET_MY_UNITS: 'SET_MY_UNITS',
44
45
  };
45
46
 
46
47
  export type AuthData = {
@@ -25,6 +25,7 @@ export const mockDataStore: ContextData = {
25
25
  barStyle: '',
26
26
  },
27
27
  listAction: [],
28
+ myUnits: [],
28
29
  unit: {
29
30
  favoriteDeviceIds: [],
30
31
  },
@@ -95,6 +96,7 @@ export const mockSCStore = (data: ContextData): ContextData => {
95
96
  appState: 'active',
96
97
  ...data.app,
97
98
  },
99
+ myUnits: [...mockDataStore.myUnits, ...(data?.myUnits || [])],
98
100
  unit: {
99
101
  favoriteDeviceIds: [
100
102
  ...mockDataStore.unit.favoriteDeviceIds,
@@ -130,5 +132,6 @@ export const mockSCStore = (data: ContextData): ContextData => {
130
132
  bluetooth: {
131
133
  ...mockDataStore.bluetooth,
132
134
  },
135
+ percent: mockDataStore.percent,
133
136
  };
134
137
  };
@@ -33,6 +33,7 @@ export type ContextData = {
33
33
  devMode: DevModeType;
34
34
  bluetooth: BluetoothType;
35
35
  percent: number;
36
+ myUnits: [];
36
37
  };
37
38
 
38
39
  export type Action = {
@@ -54,6 +55,7 @@ export const initialState = {
54
55
  listDevice: {} as ListDevice,
55
56
  listAction: [] as ListAction,
56
57
  percent: 0,
58
+ myUnits: [],
57
59
  unit: {
58
60
  favoriteDeviceIds: [],
59
61
  },
@@ -537,7 +539,11 @@ export const reducer = (currentState: ContextData, action: Action) => {
537
539
  appState: payload,
538
540
  },
539
541
  };
540
-
542
+ case Action.SET_MY_UNITS:
543
+ return {
544
+ ...currentState,
545
+ myUnits: payload,
546
+ };
541
547
  default:
542
548
  return currentState;
543
549
  }
@@ -62,7 +62,11 @@ export default ({ id, type, share, filterEnabled }) => {
62
62
  if (api?.needPermission && !permissions?.[api?.needPermission]) {
63
63
  setIsLoading(false);
64
64
  setIsRefreshing(false);
65
- ToastBottomHelper.error(t(`no_permission_${api?.needPermission}`));
65
+ ToastBottomHelper.error(
66
+ t(`no_permission_${api?.needPermission}`),
67
+ '',
68
+ 7000
69
+ );
66
70
  return;
67
71
  }
68
72
 
@@ -77,6 +77,7 @@ const AddCommonSelectUnit = ({ route }) => {
77
77
  password: route.params.password,
78
78
  brand: route.params.brand,
79
79
  unit_id: unit?.id,
80
+ unitName: unit?.name,
80
81
  },
81
82
  });
82
83
  break;
@@ -22,11 +22,6 @@ const ScanGatewayQR = memo(({ route }) => {
22
22
  setLoading(false);
23
23
  return;
24
24
  }
25
- if (data?.prefix !== 'lite') {
26
- setIsInvalidQrCode(true);
27
- setLoading(false);
28
- return;
29
- }
30
25
  navigate(Routes.ConnectingWifiGuide, {
31
26
  unit,
32
27
  stationId,
@@ -150,7 +150,7 @@ const SelectDeviceType = ({ route }) => {
150
150
  const handleOnSelect = (itemSelect) => {
151
151
  const needPermission = getPermissionCode(itemSelect);
152
152
  if (!permissions?.[needPermission]) {
153
- ToastBottomHelper.error(t(`no_permission_${needPermission}`));
153
+ ToastBottomHelper.error(t(`no_permission_${needPermission}`), '', 7000);
154
154
  return false;
155
155
  }
156
156
 
@@ -159,7 +159,9 @@ const SelectDeviceType = ({ route }) => {
159
159
  ToastBottomHelper.error(
160
160
  t('reach_max_chips_per_unit', {
161
161
  length: permissions?.max_chips_per_unit,
162
- })
162
+ }),
163
+ '',
164
+ 7000
163
165
  );
164
166
  return false;
165
167
  }
@@ -172,7 +174,9 @@ const SelectDeviceType = ({ route }) => {
172
174
  ToastBottomHelper.error(
173
175
  t('reach_max_configs_per_unit', {
174
176
  length: permissions?.max_configs_per_unit,
175
- })
177
+ }),
178
+ '',
179
+ 7000
176
180
  );
177
181
  return false;
178
182
  }
@@ -62,7 +62,6 @@ describe('test scan gateway device QR', () => {
62
62
  const bottomSheet = instance.findByProps({
63
63
  title: getTranslate('en', 'invalid_qr_code'),
64
64
  });
65
- expect(bottomSheet.props.isVisible).toBe(true);
66
65
 
67
66
  const buttonBottom = instance.findByType(ViewButtonBottom);
68
67
  await act(async () => {
@@ -14,6 +14,7 @@ import api from '../../../utils/Apis/axios';
14
14
  import API from '../../../configs/API';
15
15
  import { ToastBottomHelper } from '../../../utils/Utils';
16
16
  import { getTranslate } from '../../../utils/I18n';
17
+
17
18
  const wrapComponent = (route, storeData = {}) => (
18
19
  <SCProvider initState={mockSCStore(storeData)}>
19
20
  <SelectDeviceType route={route} />
@@ -147,10 +148,9 @@ describe('Test select device type', () => {
147
148
  });
148
149
  expect(mockedNavigate).not.toBeCalled();
149
150
  expect(spyToastError).toBeCalledWith(
150
- getTranslate(
151
- 'en',
152
- "You've reached the [1] limit. Remove one config or upgrade your plan for a better experience."
153
- )
151
+ getTranslate('en', 'reach_max_configs_per_unit'),
152
+ '',
153
+ 7000
154
154
  );
155
155
  });
156
156
 
@@ -189,10 +189,9 @@ describe('Test select device type', () => {
189
189
  });
190
190
  expect(mockedNavigate).not.toBeCalled();
191
191
  expect(spyToastError).toBeCalledWith(
192
- getTranslate(
193
- 'en',
194
- "You've reached the [1] limit. Remove one chip or upgrade your plan for a better experience."
195
- )
192
+ getTranslate('en', 'reach_max_chips_per_unit'),
193
+ '',
194
+ 7000
196
195
  );
197
196
  });
198
197
 
@@ -209,7 +208,9 @@ describe('Test select device type', () => {
209
208
  },
210
209
  });
211
210
  expect(spyToastError).toBeCalledWith(
212
- getTranslate('en', 'no_permission_plug_and_play_gateway')
211
+ getTranslate('en', 'no_permission_plug_and_play_gateway'),
212
+ '',
213
+ 7000
213
214
  );
214
215
  });
215
216
 
@@ -226,7 +227,9 @@ describe('Test select device type', () => {
226
227
  },
227
228
  });
228
229
  expect(spyToastError).toBeCalledWith(
229
- getTranslate('en', 'no_permission_plug_and_play_zigbee')
230
+ getTranslate('en', 'no_permission_plug_and_play_zigbee'),
231
+ '',
232
+ 7000
230
233
  );
231
234
  });
232
235
 
@@ -243,7 +246,9 @@ describe('Test select device type', () => {
243
246
  },
244
247
  });
245
248
  expect(spyToastError).toBeCalledWith(
246
- getTranslate('en', 'no_permission_plug_and_play_wifi')
249
+ getTranslate('en', 'no_permission_plug_and_play_wifi'),
250
+ '',
251
+ 7000
247
252
  );
248
253
  });
249
254
 
@@ -260,7 +265,9 @@ describe('Test select device type', () => {
260
265
  },
261
266
  });
262
267
  expect(spyToastError).toBeCalledWith(
263
- getTranslate('en', 'no_permission_plug_and_play_modbus')
268
+ getTranslate('en', 'no_permission_plug_and_play_modbus'),
269
+ '',
270
+ 7000
264
271
  );
265
272
  });
266
273
 
@@ -68,25 +68,16 @@ const SetupConfigCondition = () => {
68
68
  };
69
69
 
70
70
  const onSave = useCallback(() => {
71
- const conditionValue = parseFloat(itemActiveModal?.value);
72
- if (isNaN(conditionValue)) {
71
+ const conditionValue = itemActiveModal?.value;
72
+
73
+ if (isNaN(parseFloat(conditionValue)) || !isFinite(conditionValue)) {
73
74
  ToastBottomHelper.error(t('value_must_be_a_number'));
74
75
  return;
75
76
  }
76
77
 
77
- if (['<', '>', '='].includes(itemActiveModal?.condition)) {
78
- if (item.range_min !== null && conditionValue < item.range_min) {
79
- ToastBottomHelper.error(
80
- t('value_must_be_greater_than_min', { min: item.range_min })
81
- );
82
- return;
83
- }
84
- if (item.range_max !== null && conditionValue > item.range_max) {
85
- ToastBottomHelper.error(
86
- t('value_must_be_less_than_max', { max: item.range_max })
87
- );
88
- return;
89
- }
78
+ if (Math.abs(conditionValue)?.toString()?.length > 6) {
79
+ ToastBottomHelper.error(t('value_must_be_6_digits_or_less'));
80
+ return;
90
81
  }
91
82
 
92
83
  navigate({
@@ -99,7 +90,7 @@ const SetupConfigCondition = () => {
99
90
  },
100
91
  },
101
92
  });
102
- }, [item.id, item.range_max, item.range_min, itemActiveModal, navigate, t]);
93
+ }, [item.id, itemActiveModal, navigate, t]);
103
94
 
104
95
  useEffect(() => {
105
96
  !hasNoValueEvaluation && setValue(1);
@@ -80,13 +80,11 @@ describe('Test SetupConfigCondition', () => {
80
80
  expect(mockGoBack).toBeCalled();
81
81
  });
82
82
 
83
- const testWithRange = async (rangeMin, rangeMax, value, message) => {
83
+ const testConditionValue = async (value, message) => {
84
84
  useRoute.mockReturnValue({
85
85
  params: {
86
86
  item: {
87
87
  id: 1,
88
- range_min: rangeMin,
89
- range_max: rangeMax,
90
88
  decimal_behind: 0,
91
89
  title: 'is below (<)',
92
90
  sensor_type: 'air_quality',
@@ -116,22 +114,10 @@ describe('Test SetupConfigCondition', () => {
116
114
  }
117
115
  };
118
116
 
119
- it('Test render when have maximum', async () => {
120
- await testWithRange(0, 10, '128', 'Value must be less than 10');
121
- });
122
-
123
- it('Test render when have no maximum', async () => {
124
- await testWithRange(0, null, '128', null);
125
- });
126
- it('Test render when have minium', async () => {
127
- await testWithRange(4, 10, '1', 'Value must be greater than 4');
128
- });
129
-
130
- it('Test render when have no minium', async () => {
131
- await testWithRange(null, 10, '1', null);
132
- });
133
-
134
117
  it('Test render when have input not number', async () => {
135
- await testWithRange(null, 10, 'abc', 'Value must be a number');
118
+ await testConditionValue('abc', 'Value must be a number');
119
+ });
120
+ it('Test render when have input value must be 6 digits or less', async () => {
121
+ await testConditionValue('1234567', 'Value must be 6 digits or less');
136
122
  });
137
123
  });
@@ -81,12 +81,18 @@ const MultiUnits = () => {
81
81
  ToastBottomHelper.error(
82
82
  t('reach_max_automations_per_unit', {
83
83
  length: permissions.max_automations_per_unit,
84
- })
84
+ }),
85
+ '',
86
+ 7000
85
87
  );
86
88
  return;
87
89
  }
88
90
  } else if (!permissions?.smart_script_for_multi_unit) {
89
- ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
91
+ ToastBottomHelper.error(
92
+ t('no_permission_smart_script_for_multi_unit'),
93
+ '',
94
+ 7000
95
+ );
90
96
  return;
91
97
  }
92
98
  if (tabActive === AUTOMATE_TABS.SCENARIO) {
@@ -271,10 +271,9 @@ describe('Test ScriptDetail', () => {
271
271
  });
272
272
  expect(mockNavigate).not.toBeCalled();
273
273
  expect(spyToastError).toBeCalledWith(
274
- getTranslate(
275
- 'en',
276
- "You've reached the [0] limit. Remove one action or upgrade your plan for a better experience."
277
- )
274
+ getTranslate('en', 'reach_max_actions_per_automation'),
275
+ '',
276
+ 7000
278
277
  );
279
278
  });
280
279
 
@@ -305,7 +305,9 @@ const ItemAdd = ({ automate, index }) => {
305
305
  ToastBottomHelper.error(
306
306
  t('reach_max_actions_per_automation', {
307
307
  length: permissions?.max_actions_per_automation,
308
- })
308
+ }),
309
+ '',
310
+ 7000
309
311
  );
310
312
  return;
311
313
  }
@@ -252,10 +252,9 @@ describe('Test MultiUnits', () => {
252
252
  });
253
253
  expect(mockedNavigate).not.toBeCalled();
254
254
  expect(spyToastError).toBeCalledWith(
255
- getTranslate(
256
- 'en',
257
- "You've reached the [0] limit. Remove one automation or upgrade your plan for a better experience."
258
- )
255
+ getTranslate('en', 'reach_max_automations_per_unit'),
256
+ '',
257
+ 7000
259
258
  );
260
259
  });
261
260
 
@@ -291,7 +290,9 @@ describe('Test MultiUnits', () => {
291
290
  });
292
291
  expect(mockedNavigate).not.toBeCalled();
293
292
  expect(spyToastError).toBeCalledWith(
294
- getTranslate('en', 'no_permission_smart_script_for_multi_unit')
293
+ getTranslate('en', 'no_permission_smart_script_for_multi_unit'),
294
+ '',
295
+ 7000
295
296
  );
296
297
  });
297
298
 
@@ -148,10 +148,9 @@ describe('Test Automate', () => {
148
148
  });
149
149
  expect(mockedNavigate).not.toBeCalled();
150
150
  expect(spyToastError).toBeCalledWith(
151
- getTranslate(
152
- 'en',
153
- "You've reached the [0] limit. Remove one automation or upgrade your plan for a better experience."
154
- )
151
+ getTranslate('en', 'reach_max_automations_per_unit'),
152
+ '',
153
+ 7000
155
154
  );
156
155
  });
157
156
 
@@ -295,7 +294,9 @@ describe('Test Automate', () => {
295
294
  });
296
295
  expect(mockedNavigate).not.toBeCalled();
297
296
  expect(spyToastError).toBeCalledWith(
298
- getTranslate('en', 'no_permission_smart_script_for_multi_unit')
297
+ getTranslate('en', 'no_permission_smart_script_for_multi_unit'),
298
+ '',
299
+ 7000
299
300
  );
300
301
  });
301
302
  });
@@ -88,12 +88,18 @@ const Automate = () => {
88
88
  ToastBottomHelper.error(
89
89
  t('reach_max_automations_per_unit', {
90
90
  length: permissions.max_automations_per_unit,
91
- })
91
+ }),
92
+ '',
93
+ 7000
92
94
  );
93
95
  return;
94
96
  }
95
97
  } else if (!permissions?.smart_script_for_multi_unit) {
96
- ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
98
+ ToastBottomHelper.error(
99
+ t('no_permission_smart_script_for_multi_unit'),
100
+ '',
101
+ 7000
102
+ );
97
103
  return;
98
104
  }
99
105
  navigate(Routes.UnitStack, {
@@ -501,13 +501,14 @@ const DeviceDetail = ({ route }) => {
501
501
  const configIds = [];
502
502
 
503
503
  display.items.map((item) => {
504
+ const type = item?.template || item?.type;
504
505
  if (!item.configuration) {
505
506
  return;
506
507
  }
507
- if (!['action', 'value'].includes(item.type)) {
508
+ if (!['action', 'value'].includes(type)) {
508
509
  return;
509
510
  }
510
- if (item.type === 'action') {
511
+ if (type === 'action') {
511
512
  if (
512
513
  [
513
514
  'on_off_button_action_template',
@@ -518,7 +519,7 @@ const DeviceDetail = ({ route }) => {
518
519
  configIds.push(item.configuration.configuration.config);
519
520
  }
520
521
  }
521
- if (item.type === 'value') {
522
+ if (type === 'value') {
522
523
  item.configuration.configs.map((config) => {
523
524
  if (!configIds.includes(config.id)) {
524
525
  configIds.push(config.id);
@@ -1,7 +1,8 @@
1
1
  export const getConfigControlFromDeviceDisplay = (deviceDisplay) => {
2
2
  const configIds = [];
3
3
  (deviceDisplay?.items || []).map((item) => {
4
- if (item.type !== 'action') {
4
+ const type = item?.template || item?.type;
5
+ if (type !== 'action') {
5
6
  return;
6
7
  }
7
8
  const actionGroup = item?.configuration || {};
@@ -55,6 +55,7 @@ describe('test NotificationItem', () => {
55
55
  NOTIFICATION_TYPES.NOTIFY_REMOVE_SUB_UNIT,
56
56
  NOTIFICATION_TYPES.NOTIFY_REMOVE_DEVICE,
57
57
  NOTIFICATION_TYPES.NOTIFY_CHANGE_UNIT_OLD_OWNER,
58
+ NOTIFICATION_TYPES.SUBSCRIBE_SUCCESS,
58
59
  ];
59
60
 
60
61
  for (const content_code of listCase2) {
@@ -408,6 +408,14 @@ const NotificationItem = memo(({ item }) => {
408
408
  redirect: () => null,
409
409
  iconContent: <Image source={Images.logo} style={styles.logo} />,
410
410
  };
411
+ case NOTIFICATION_TYPES.SUBSCRIBE_SUCCESS:
412
+ return {
413
+ content: customColorText(
414
+ t('text_notification_content_subscribe_success')
415
+ ),
416
+ redirect: () => null,
417
+ iconContent: <Image source={Images.logo} style={styles.logo} />,
418
+ };
411
419
  default:
412
420
  return null;
413
421
  }
@@ -41,7 +41,9 @@ const MemberList = ({ route }) => {
41
41
  ToastBottomHelper.error(
42
42
  t('reach_max_members_per_unit', {
43
43
  length: permissions?.max_members_per_unit,
44
- })
44
+ }),
45
+ '',
46
+ 7000
45
47
  );
46
48
  return;
47
49
  }
@@ -101,10 +101,9 @@ describe('test MemberList', () => {
101
101
  });
102
102
  expect(mockedNavigate).not.toBeCalled();
103
103
  expect(spyToastError).toBeCalledWith(
104
- getTranslate(
105
- 'en',
106
- "You've reached the [0] limit. Remove one member or upgrade your plan for a better experience."
107
- )
104
+ getTranslate('en', 'reach_max_members_per_unit'),
105
+ '',
106
+ 7000
108
107
  );
109
108
  });
110
109
  });
@@ -2,7 +2,6 @@ import React, { useEffect, useCallback, useContext } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import { useNavigation } from '@react-navigation/native';
4
4
  import * as Progress from 'react-native-progress';
5
- import LoadingDots from 'react-native-loading-dots';
6
5
 
7
6
  import Text from '../../../commons/Text';
8
7
  import { useTranslations } from '../../../hooks/Common/useTranslations';
@@ -25,6 +24,7 @@ const SmartAccountConnecting = ({ route }) => {
25
24
  password,
26
25
  brand,
27
26
  unit_id,
27
+ unitName,
28
28
  listDeviceIds,
29
29
  stationId,
30
30
  nameSubUnit,
@@ -95,6 +95,7 @@ const SmartAccountConnecting = ({ route }) => {
95
95
  smart_account_id: smart_account_id,
96
96
  smart_account_id_from_backend: smart_account_id_from_backend,
97
97
  unit_id: unit_id,
98
+ unitName,
98
99
  stationId,
99
100
  username,
100
101
  brand,
@@ -139,7 +140,6 @@ const SmartAccountConnecting = ({ route }) => {
139
140
  <Text bold style={styles.connectingText}>
140
141
  {t('connecting_smart_account')}
141
142
  </Text>
142
- <LoadingDots />
143
143
  </View>
144
144
 
145
145
  <Text style={styles.warningText}>
@@ -22,7 +22,7 @@ export default StyleSheet.create({
22
22
  },
23
23
  warningText: {
24
24
  marginHorizontal: 30,
25
- marginTop: 16,
25
+ marginTop: 8,
26
26
  fontSize: 14,
27
27
  color: Colors.Gray8,
28
28
  },
@@ -21,12 +21,13 @@ const SuccessfullyConnected = memo(({ route }) => {
21
21
  listSelectDevice,
22
22
  smart_account_id,
23
23
  unit_id,
24
+ unitName,
24
25
  stationId,
25
26
  smart_account_id_from_backend,
26
27
  username,
27
28
  brand,
28
29
  chipId,
29
- } = route.params;
30
+ } = route.params || {};
30
31
  const t = useTranslations();
31
32
  const { navigate, dispatch } = useNavigation();
32
33
  const [info, setInfo] = useState({});
@@ -139,7 +140,7 @@ const SuccessfullyConnected = memo(({ route }) => {
139
140
  {t('successfully_connected')}
140
141
  </Text>
141
142
  <Text size={14} style={styles.textHome}>
142
- {`Home - ${nameSubUnit}`}
143
+ {`${unitName} - ${nameSubUnit}`}
143
144
  </Text>
144
145
  {!loading && info?.sensors?.length !== 0 ? (
145
146
  <View>
@@ -187,6 +188,7 @@ const SuccessfullyConnected = memo(({ route }) => {
187
188
  accessibilityLabelPrefix={
188
189
  AccessibilityLabel.PREFIX.BUTTON_BOTTOM_SMART_ACCOUNT
189
190
  }
191
+ wrapStyle={styles.wrapViewButton}
190
192
  />
191
193
  </View>
192
194
  </>
@@ -96,4 +96,7 @@ export default StyleSheet.create({
96
96
  resizeMode: 'contain',
97
97
  marginRight: 10,
98
98
  },
99
+ wrapViewButton: {
100
+ position: 'relative',
101
+ },
99
102
  });
@@ -50,6 +50,7 @@ const SmartAccount = ({ route }) => {
50
50
  password: password,
51
51
  brand: 'google_home',
52
52
  unit_id: route?.params?.unitId,
53
+ unitName: route?.params?.unitName,
53
54
  });
54
55
  } else {
55
56
  navigate(Routes.AddDeviceStack, {
@@ -20,11 +20,16 @@ import { AccessibilityLabel } from '../../configs/Constants';
20
20
  import styles from './AddSubUnitStyles';
21
21
  import useKeyboardShow from '../../hooks/Common/useKeyboardShow';
22
22
  import { replace } from '../../navigations/utils';
23
+ import { useBackendPermission } from '../../utils/Permission/backend';
24
+ import { useSCContextSelector } from '../../context';
23
25
 
24
26
  const AddSubUnit = ({ route }) => {
25
27
  const t = useTranslations();
26
28
  const { dismissKeyboard } = useKeyboardShow();
27
29
  const { navigate, goBack, dispatch } = useNavigation();
30
+ const permissions = useBackendPermission();
31
+ const myUnits = useSCContextSelector((state) => state.myUnits);
32
+
28
33
  const {
29
34
  unit,
30
35
  addType,
@@ -38,6 +43,10 @@ const AddSubUnit = ({ route }) => {
38
43
  const [showImagePicker, setShowImagePicker] = useState(false);
39
44
  let awaitCreate = useRef(false);
40
45
 
46
+ const unitChoose = useMemo(() => {
47
+ return myUnits?.find((item) => item?.id === unit?.id);
48
+ }, [myUnits, unit?.id]);
49
+
41
50
  const cleanData = () => {
42
51
  setRoomName('');
43
52
  setWallpaper('');
@@ -124,6 +133,11 @@ const AddSubUnit = ({ route }) => {
124
133
  cleanData();
125
134
  } else {
126
135
  awaitCreate.current = false;
136
+ if (
137
+ unitChoose?.stations?.length >= permissions?.max_stations_per_unit
138
+ ) {
139
+ ToastBottomHelper.error(t('reach_max_stations_per_unit'), '', 7000);
140
+ }
127
141
  }
128
142
  }
129
143
  }
@@ -140,6 +154,8 @@ const AddSubUnit = ({ route }) => {
140
154
  goBack,
141
155
  navigate,
142
156
  route.params,
157
+ unitChoose?.stations?.length,
158
+ permissions?.max_stations_per_unit,
143
159
  ]);
144
160
 
145
161
  const onChoosePhoto = useCallback(() => {
@@ -14,9 +14,10 @@ import { SCProvider } from '../../../context';
14
14
  import { mockSCStore } from '../../../context/mockStore';
15
15
  import api from '../../../utils/Apis/axios';
16
16
  import { API } from '../../../configs';
17
+ import { ToastBottomHelper } from '../../../utils/Utils';
17
18
 
18
- const wrapComponent = (route) => (
19
- <SCProvider initState={mockSCStore({})}>
19
+ const wrapComponent = (route, storeData = {}) => (
20
+ <SCProvider initState={mockSCStore(storeData)}>
20
21
  <AddSubUnit route={route} />
21
22
  </SCProvider>
22
23
  );
@@ -297,7 +298,19 @@ describe('Test AddSubUnit', () => {
297
298
  };
298
299
  mock.onPost(API.SUB_UNIT.CREATE_SUB_UNIT(1)).reply(200, {});
299
300
  await act(async () => {
300
- tree = await create(wrapComponent(route));
301
+ tree = await create(
302
+ wrapComponent(route, {
303
+ auth: {
304
+ account: {
305
+ user: {
306
+ permissions: {
307
+ max_stations_per_unit: 100,
308
+ },
309
+ },
310
+ },
311
+ },
312
+ })
313
+ );
301
314
  });
302
315
  const instance = tree.root;
303
316
  const viewButtonBottom = await makeValidateData(instance);
@@ -307,6 +320,45 @@ describe('Test AddSubUnit', () => {
307
320
  expect(mockNavigationDispatch).toBeCalledWith(mockReplace);
308
321
  });
309
322
 
323
+ it('test no permission create fail sub-unit is out side Unit', async () => {
324
+ route.params = {
325
+ ...route.params,
326
+ isInsideUnit: false,
327
+ };
328
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
329
+ mock.onPost(API.SUB_UNIT.CREATE_SUB_UNIT(1)).reply(400, {});
330
+ await act(async () => {
331
+ tree = await create(
332
+ wrapComponent(route, {
333
+ auth: {
334
+ account: {
335
+ user: {
336
+ permissions: {
337
+ max_stations_per_unit: 0,
338
+ },
339
+ },
340
+ },
341
+ },
342
+ myUnits: [
343
+ { id: 1, name: 'unit 1', stations: [{ id: 1, name: 'station' }] },
344
+ ],
345
+ })
346
+ );
347
+ });
348
+ const instance = tree.root;
349
+ const viewButtonBottom = await makeValidateData(instance);
350
+
351
+ await act(async () => {
352
+ await viewButtonBottom.props.onRightClick();
353
+ });
354
+ expect(spyToastError).toBeCalledWith(
355
+ getTranslate('en', 'reach_max_stations_per_unit'),
356
+ '',
357
+ 7000
358
+ );
359
+ expect(mockNavigationDispatch).not.toBeCalled();
360
+ });
361
+
310
362
  it('test create sub-unit type AddHassiDevice', async () => {
311
363
  route.params = {
312
364
  ...route.params,
@@ -36,7 +36,9 @@ const AddMenu = memo(({ unit, afterItemClick, showAdd, setHideAdd }) => {
36
36
  ToastBottomHelper.error(
37
37
  t('reach_max_stations_per_unit', {
38
38
  length: permissions.max_stations_per_unit,
39
- })
39
+ }),
40
+ '',
41
+ 7000
40
42
  );
41
43
  return;
42
44
  }
@@ -885,6 +885,9 @@ export default {
885
885
  text_notification_content_when_trial_will_end:
886
886
  'The service usage period is only **days** days left, ' +
887
887
  'Package **product_name** will automatically renew on **trial_end** with **amount** **currency**',
888
+ text_notification_content_subscribe_success:
889
+ 'Your account has just successfully registered for the **plan_name** plan - expires on **expire**. ' +
890
+ "Let's experience the new features now!",
888
891
  text_notification_content_your_account_upgraded_3_month:
889
892
  'Congratulations! Your account has just been upgraded to a 3-month free PREMIUM plan ' +
890
893
  'as a sincere thanking word to your loyalty of the BETA version of E-Ra IoT Platform. ' +
@@ -1392,34 +1395,31 @@ export default {
1392
1395
  'To provide a better user experience, [Era] will perform a system upgrade.The upgrade will take about 10 ' +
1393
1396
  'minutes.\n\n Some device control functions will be interrupted during the upgrade.[Era] would like to ask ' +
1394
1397
  'for your understanding for this inconvenience.\n\nBest regards.',
1395
- value_must_be_greater_than_min: 'Value must be greater than {min}',
1396
- value_must_be_less_than_max: 'Value must be less than {max}',
1397
1398
  value_must_be_a_number: 'Value must be a number',
1398
1399
  reach_max_stations_per_unit:
1399
- "You've reached the [{length}] limit. Remove one sub-unit or upgrade your plan for a better experience.",
1400
+ 'Sub-unit is incomplete. Please visit app.e-ra.io to complete your operation.',
1400
1401
  not_support_plug_and_play: 'Not support plug and play',
1401
1402
  reach_max_actions_per_automation:
1402
- "You've reached the [{length}] limit. Remove one action or upgrade your plan for a better experience.",
1403
+ 'Action is incomplete. Please visit app.e-ra.io to complete your operation.',
1403
1404
  reach_max_automations_per_unit:
1404
- "You've reached the [{length}] limit. Remove one automation or upgrade your plan for a better experience.",
1405
+ 'Automation is incomplete. Please visit app.e-ra.io to complete your operation.',
1405
1406
  reach_max_members_per_unit:
1406
- "You've reached the [{length}] limit. Remove one member or upgrade your plan for a better experience.",
1407
+ 'Member is incomplete. Please visit app.e-ra.io to complete your operation.',
1407
1408
  reach_max_chips_per_unit:
1408
- "You've reached the [{length}] limit. Remove one chip or upgrade your plan for a better experience.",
1409
+ 'Chip is incomplete. Please visit app.e-ra.io to complete your operation.',
1409
1410
  reach_max_configs_per_unit:
1410
- "You've reached the [{length}] limit. Remove one config or upgrade your plan for a better experience.",
1411
+ 'Config is incomplete. Please visit app.e-ra.io to complete your operation.',
1411
1412
  no_permission_plug_and_play_modbus:
1412
- 'Add modbus is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1413
+ 'Add modbus is incomplete. Please visit app.e-ra.io to complete your operation.',
1413
1414
  no_permission_plug_and_play_zigbee:
1414
- 'Add zigbee is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1415
+ 'Add zigbee is incomplete. Please visit app.e-ra.io to complete your operation.',
1415
1416
  no_permission_plug_and_play_wifi:
1416
- 'Add wifi is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1417
+ 'Add wifi is incomplete. Please visit app.e-ra.io to complete your operation.',
1417
1418
  no_permission_plug_and_play_gateway:
1418
- 'Add gateway is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1419
+ 'Add gateway is incomplete. Please visit app.e-ra.io to complete your operation.',
1419
1420
  no_permission_view_action_log:
1420
- // eslint-disable-next-line max-len
1421
- 'View action log is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1421
+ 'View action log is incomplete. Please visit app.e-ra.io to complete your operation.',
1422
1422
  no_permission_smart_script_for_multi_unit:
1423
- // eslint-disable-next-line max-len
1424
- 'Smart script for Multi Unit is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1423
+ 'Smart script for Multi Unit is incomplete. Please visit app.e-ra.io to complete your operation.',
1424
+ value_must_be_6_digits_or_less: 'Value must be 6 digits or less',
1425
1425
  };
@@ -901,6 +901,9 @@ export default {
901
901
  text_notification_content_when_trial_will_end:
902
902
  'Thời hạn sử dụng dịch vụ chỉ còn **days** ngày, ' +
903
903
  'Gói **product_name** sẽ tự động gia hạn vào ngày **trial_end** với **amount** **currency**',
904
+ text_notification_content_subscribe_success:
905
+ 'Tài khoản của bạn vừa đăng ký thành công gói **plan_name** - ngày kết thúc **expire**. ' +
906
+ 'Cùng trải nghiệm các tính năng mới ngay thôi!',
904
907
  text_notification_content_your_account_upgraded_3_month:
905
908
  'Xin chúc mừng! Tài khoản của bạn vừa được nâng cấp lên gói PREMIUM miễn phí 3 tháng ' +
906
909
  'nhằm tri ân người dùng phiên bản BETA của E-Ra IoT Platform. ' +
@@ -1404,29 +1407,29 @@ export default {
1404
1407
  value_must_be_less_than_max: 'Giá trị phải nhỏ hơn {max}',
1405
1408
  value_must_be_a_number: 'Giá trị phải là một số',
1406
1409
  reach_max_stations_per_unit:
1407
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một sub-unit hoặc nâng cấp gói để trải nghiệm tốt hơn.',
1410
+ 'Sub-unit chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1408
1411
  not_support_plug_and_play: 'Không hỗ trợ kết nối tự động',
1409
1412
  reach_max_actions_per_automation:
1410
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một action hoặc nâng cấp gói để có trải nghiệm tốt hơn.',
1413
+ 'Action chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1411
1414
  reach_max_automations_per_unit:
1412
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một automation hoặc nâng cấp gói để có trải nghiệm tốt hơn.',
1415
+ 'Automation chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1413
1416
  reach_max_members_per_unit:
1414
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một member hoặc nâng cấp gói để có trải nghiệm tốt hơn.',
1417
+ 'Member chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1415
1418
  reach_max_chips_per_unit:
1416
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một chip hoặc nâng cấp gói để có trải nghiệm tốt hơn.',
1419
+ 'Chip chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1417
1420
  reach_max_configs_per_unit:
1418
- 'Bạn đã đạt giới hạn [{length}]. Vui lòng xoá bớt một config hoặc nâng cấp gói để có trải nghiệm tốt hơn.',
1421
+ 'Config chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1419
1422
  no_permission_plug_and_play_modbus:
1420
- 'Kết nối tự động modbus hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1423
+ 'Kết nối tự động modbus chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1421
1424
  no_permission_plug_and_play_zigbee:
1422
- 'Kết nối tự động zigbee hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1425
+ 'Kết nối tự động zigbee chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1423
1426
  no_permission_plug_and_play_wifi:
1424
- 'Kết nối tự động wifi hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1427
+ 'Kết nối tự động wifi chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1425
1428
  no_permission_plug_and_play_gateway:
1426
- 'Kết nối tự động gateway hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1429
+ 'Kết nối tự động gateway chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1427
1430
  no_permission_view_action_log:
1428
- 'Xem lịch sử hoạt động hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1431
+ 'Xem lịch sử hoạt động chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1429
1432
  no_permission_smart_script_for_multi_unit:
1430
- // eslint-disable-next-line max-len
1431
- 'Kịch bản thông minh cho nhiều địa điểm hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1433
+ 'Kịch bản thông minh cho nhiều địa điểm chưa hoàn tất. Hãy truy cập app.e-ra.io để hoàn thiện thao tác của bạn.',
1434
+ value_must_be_6_digits_or_less: 'Giá trị phải nhỏ hơn 6 chữ số',
1432
1435
  };
@@ -88,31 +88,31 @@ export const openMapDirection = (item) => () => {
88
88
  };
89
89
 
90
90
  export const ToastBottomHelper = {
91
- success: (msg, line2) => {
91
+ success: (msg, line2, visibilityTime = 1000) => {
92
92
  Toast.show({
93
93
  type: 'success',
94
94
  position: 'bottom',
95
95
  text1: msg,
96
96
  text2: line2,
97
- visibilityTime: 1000,
97
+ visibilityTime: visibilityTime,
98
98
  });
99
99
  },
100
- error: (msg, line2) => {
100
+ error: (msg, line2, visibilityTime = 1000) => {
101
101
  Toast.show({
102
102
  type: 'error',
103
103
  position: 'bottom',
104
104
  text1: msg,
105
105
  text2: line2,
106
- visibilityTime: 1000,
106
+ visibilityTime: visibilityTime,
107
107
  });
108
108
  },
109
- info: (msg, line2) => {
109
+ info: (msg, line2, visibilityTime = 1000) => {
110
110
  Toast.show({
111
111
  type: 'info',
112
112
  position: 'bottom',
113
113
  text1: msg,
114
114
  text2: line2,
115
- visibilityTime: 1000,
115
+ visibilityTime: visibilityTime,
116
116
  });
117
117
  },
118
118
  };