@controleonline/ui-common 1.2.70 → 1.2.71

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 (61) hide show
  1. package/package.json +31 -28
  2. package/src/api/index.js +237 -37
  3. package/src/react/components/AddImportModal.js +1 -1
  4. package/src/react/components/AnimatedModal.js +171 -0
  5. package/src/react/components/AnimatedModal.styles.js +21 -0
  6. package/src/react/components/AppTypeSwitcher.js +164 -0
  7. package/src/react/components/AppTypeSwitcher.styles.js +109 -0
  8. package/src/react/components/BackgroundRuntimeBridge.js +5 -4
  9. package/src/react/components/BottomNavigationBar.js +86 -31
  10. package/src/react/components/BottomNavigationBar.styles.js +118 -110
  11. package/src/react/components/DefaultProvider.native.js +68 -66
  12. package/src/react/components/DefaultProvider.web.js +44 -42
  13. package/src/react/components/DeliveryPushBridge.native.js +2 -2
  14. package/src/react/components/DeviceAlertSoundService.js +3 -3
  15. package/src/react/components/KioskModeBridge.js +2 -2
  16. package/src/react/components/LauncherModeBridge.js +2 -2
  17. package/src/react/components/ManagerPushBridge.native.js +2 -2
  18. package/src/react/components/RemoteCheckoutService.js +28 -20
  19. package/src/react/components/RuntimeBottomNavigationBar.js +146 -119
  20. package/src/react/components/RuntimeInfoFooter.js +37 -9
  21. package/src/react/components/StateStore.js +258 -0
  22. package/src/react/components/WebsocketListener.native.js +11 -11
  23. package/src/react/config/deviceConfigBootstrap.js +66 -27
  24. package/src/react/css/orders.js +72 -0
  25. package/src/react/pages/SettingsPage/PaymentTypesByWalletTab.js +484 -454
  26. package/src/react/pages/SettingsPage/index.js +1266 -1167
  27. package/src/react/print/providers/local.js +1 -1
  28. package/src/react/router/publicRoutes.js +25 -0
  29. package/src/react/services/Cielo/Checkout.js +39 -0
  30. package/src/react/services/Cielo/Cielo.js +193 -0
  31. package/src/react/services/Cielo/Print.js +7 -0
  32. package/src/react/services/InfinitePay/Checkout.js +33 -0
  33. package/src/react/services/InfinitePay/InfinitePay.js +24 -0
  34. package/src/react/{utils → services}/paymentGatewayExecution.js +91 -91
  35. package/src/react/styles/global.js +115 -0
  36. package/src/react/utils/fileUrl.js +182 -16
  37. package/src/react/utils/menuNavigation.js +31 -0
  38. package/src/react/utils/orderIdentity.js +277 -0
  39. package/src/react/utils/remotePayment.js +115 -61
  40. package/src/react/utils/runtimeMenu.js +35 -3
  41. package/src/react/utils/shopConfig.js +50 -24
  42. package/src/react/utils/shopFranchises.js +56 -22
  43. package/src/react/utils/socketRuntimePipeline.js +14 -14
  44. package/src/store/configs/index.js +2 -2
  45. package/src/tests/react/api/loadSmokeIndex.test.js +75 -0
  46. package/src/tests/react/config/deviceConfigBootstrap.test.js +47 -0
  47. package/src/tests/react/print/localPrintProvider.test.js +1 -1
  48. package/src/tests/react/services/Cielo.test.js +190 -0
  49. package/src/tests/react/utils/fileUrl.test.js +62 -0
  50. package/src/tests/react/utils/importStatus.test.js +2 -2
  51. package/src/tests/react/utils/orderIdentity.test.js +139 -0
  52. package/src/tests/react/utils/remotePayment.test.js +62 -0
  53. package/src/tests/react/utils/runtimeFooter.test.js +4 -6
  54. package/src/tests/react/utils/runtimeLanguage.test.js +2 -2
  55. package/src/tests/react/utils/runtimeMenu.test.js +23 -3
  56. package/src/tests/react/utils/shopConfig.test.js +74 -0
  57. package/src/tests/react/utils/shopFranchises.test.js +79 -12
  58. package/src/tests/react/utils/translate.test.mjs +166 -102
  59. package/src/utils/formatter.js +18 -0
  60. package/src/utils/integrationConfigs.js +80 -79
  61. package/src/utils/translate.js +125 -30
@@ -0,0 +1,164 @@
1
+ import React, {useMemo, useState} from 'react';
2
+ import {
3
+ Modal,
4
+ Platform,
5
+ ScrollView,
6
+ Text,
7
+ TextInput,
8
+ TouchableOpacity,
9
+ View,
10
+ } from 'react-native';
11
+ import Icon from 'react-native-vector-icons/Feather';
12
+ import {
13
+ app_type,
14
+ app_type_base,
15
+ app_type_options,
16
+ clear_app_type,
17
+ normalize_app_type,
18
+ set_app_type,
19
+ } from '@appType';
20
+ import styles from './AppTypeSwitcher.styles';
21
+
22
+ const reloadApp = () => {
23
+ if (typeof window !== 'undefined' && typeof window.location?.reload === 'function') {
24
+ window.location.reload();
25
+ }
26
+ };
27
+
28
+ function AppTypeOptionsModal({visible, currentAppType, onClose, onSelect}) {
29
+ const [query, setQuery] = useState('');
30
+
31
+ const filteredOptions = useMemo(() => {
32
+ const normalizedQuery = query.trim().toLowerCase();
33
+ if (!normalizedQuery) {
34
+ return app_type_options;
35
+ }
36
+
37
+ return app_type_options.filter(option =>
38
+ option.toLowerCase().includes(normalizedQuery),
39
+ );
40
+ }, [query]);
41
+
42
+ if (!visible) {
43
+ return null;
44
+ }
45
+
46
+ return (
47
+ <Modal visible transparent animationType="fade" onRequestClose={onClose}>
48
+ <View style={styles.modalOverlay}>
49
+ <View style={styles.modalPanel}>
50
+ <View style={styles.modalHeader}>
51
+ <Text style={styles.modalTitle}>Selecionar tipo de app</Text>
52
+ <TouchableOpacity
53
+ accessibilityRole="button"
54
+ accessibilityLabel="Fechar seletor"
55
+ style={styles.closeButton}
56
+ onPress={onClose}
57
+ >
58
+ <Icon name="x" size={18} color="#334155" />
59
+ </TouchableOpacity>
60
+ </View>
61
+ <TextInput
62
+ style={styles.modalSearch}
63
+ value={query}
64
+ onChangeText={setQuery}
65
+ placeholder="Filtrar"
66
+ placeholderTextColor="#94A3B8"
67
+ />
68
+ <ScrollView contentContainerStyle={styles.optionList}>
69
+ {filteredOptions.map(option => {
70
+ const selected = option === currentAppType;
71
+
72
+ return (
73
+ <TouchableOpacity
74
+ key={option}
75
+ accessibilityRole="button"
76
+ accessibilityLabel={option}
77
+ style={[styles.optionRow, selected && styles.optionRowActive]}
78
+ onPress={() => onSelect(option)}
79
+ >
80
+ <Text style={[styles.optionText, selected && styles.optionTextActive]}>
81
+ {option}
82
+ </Text>
83
+ {selected && <Text style={styles.optionHint}>Visão atual</Text>}
84
+ </TouchableOpacity>
85
+ );
86
+ })}
87
+ </ScrollView>
88
+ </View>
89
+ </View>
90
+ </Modal>
91
+ );
92
+ }
93
+
94
+ const AppTypeSwitcher = () => {
95
+ const buildAppType = normalize_app_type(app_type_base);
96
+ const currentAppType = normalize_app_type(app_type);
97
+ const [selectorOpen, setSelectorOpen] = useState(false);
98
+
99
+ if (Platform.OS !== 'web' || buildAppType !== 'ADMIN') {
100
+ return null;
101
+ }
102
+
103
+ const handleSelect = appType => {
104
+ const normalizedAppType = normalize_app_type(appType);
105
+ if (!normalizedAppType || normalizedAppType === currentAppType) {
106
+ setSelectorOpen(false);
107
+ return;
108
+ }
109
+
110
+ if (normalizedAppType === buildAppType) {
111
+ clear_app_type();
112
+ } else {
113
+ set_app_type(normalizedAppType);
114
+ }
115
+
116
+ setSelectorOpen(false);
117
+ reloadApp();
118
+ };
119
+
120
+ const handleReset = () => {
121
+ clear_app_type();
122
+ setSelectorOpen(false);
123
+ reloadApp();
124
+ };
125
+
126
+ return (
127
+ <>
128
+ <View style={styles.container}>
129
+ <Text style={styles.label}>Tipo de app</Text>
130
+ <TouchableOpacity
131
+ accessibilityRole="button"
132
+ accessibilityLabel="Selecionar tipo de app"
133
+ activeOpacity={0.82}
134
+ style={styles.button}
135
+ onPress={() => setSelectorOpen(true)}
136
+ >
137
+ <Text style={styles.buttonText}>{currentAppType}</Text>
138
+ <Icon name="chevron-down" size={16} color="#334155" />
139
+ </TouchableOpacity>
140
+
141
+ {currentAppType !== buildAppType && (
142
+ <TouchableOpacity
143
+ accessibilityRole="button"
144
+ accessibilityLabel="Voltar para ADMIN"
145
+ activeOpacity={0.82}
146
+ style={styles.resetButton}
147
+ onPress={handleReset}
148
+ >
149
+ <Text style={styles.resetText}>Voltar para ADMIN</Text>
150
+ </TouchableOpacity>
151
+ )}
152
+ </View>
153
+
154
+ <AppTypeOptionsModal
155
+ visible={selectorOpen}
156
+ currentAppType={currentAppType}
157
+ onClose={() => setSelectorOpen(false)}
158
+ onSelect={handleSelect}
159
+ />
160
+ </>
161
+ );
162
+ };
163
+
164
+ export default AppTypeSwitcher;
@@ -0,0 +1,109 @@
1
+ import {StyleSheet} from 'react-native';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ alignItems: 'flex-end',
6
+ gap: 6,
7
+ },
8
+ label: {
9
+ color: '#64748B',
10
+ fontSize: 11,
11
+ fontWeight: '800',
12
+ letterSpacing: 0.2,
13
+ },
14
+ button: {
15
+ alignItems: 'center',
16
+ backgroundColor: '#FFFFFF',
17
+ borderColor: '#D8E0EA',
18
+ borderRadius: 999,
19
+ borderWidth: 1,
20
+ flexDirection: 'row',
21
+ gap: 8,
22
+ justifyContent: 'space-between',
23
+ minWidth: 164,
24
+ paddingHorizontal: 12,
25
+ paddingVertical: 8,
26
+ },
27
+ buttonText: {
28
+ color: '#0F172A',
29
+ fontSize: 13,
30
+ fontWeight: '800',
31
+ },
32
+ resetButton: {
33
+ paddingHorizontal: 2,
34
+ paddingVertical: 2,
35
+ },
36
+ resetText: {
37
+ color: '#1D4ED8',
38
+ fontSize: 11,
39
+ fontWeight: '800',
40
+ },
41
+ modalOverlay: {
42
+ flex: 1,
43
+ backgroundColor: 'rgba(15, 23, 42, 0.46)',
44
+ justifyContent: 'center',
45
+ padding: 16,
46
+ },
47
+ modalPanel: {
48
+ backgroundColor: '#FFFFFF',
49
+ borderRadius: 18,
50
+ maxHeight: '80%',
51
+ overflow: 'hidden',
52
+ },
53
+ modalHeader: {
54
+ alignItems: 'center',
55
+ borderBottomColor: '#E2E8F0',
56
+ borderBottomWidth: 1,
57
+ flexDirection: 'row',
58
+ justifyContent: 'space-between',
59
+ paddingHorizontal: 16,
60
+ paddingVertical: 14,
61
+ },
62
+ modalTitle: {
63
+ color: '#0F172A',
64
+ fontSize: 16,
65
+ fontWeight: '800',
66
+ },
67
+ closeButton: {
68
+ alignItems: 'center',
69
+ borderRadius: 16,
70
+ height: 32,
71
+ justifyContent: 'center',
72
+ width: 32,
73
+ },
74
+ modalSearch: {
75
+ backgroundColor: '#F8FAFC',
76
+ borderBottomColor: '#E2E8F0',
77
+ borderBottomWidth: 1,
78
+ color: '#0F172A',
79
+ fontSize: 13,
80
+ fontWeight: '700',
81
+ minHeight: 42,
82
+ paddingHorizontal: 16,
83
+ paddingVertical: 10,
84
+ },
85
+ optionList: {
86
+ paddingVertical: 8,
87
+ },
88
+ optionRow: {
89
+ paddingHorizontal: 16,
90
+ paddingVertical: 12,
91
+ },
92
+ optionRowActive: {
93
+ backgroundColor: '#EFF6FF',
94
+ },
95
+ optionText: {
96
+ color: '#334155',
97
+ fontSize: 14,
98
+ fontWeight: '800',
99
+ },
100
+ optionTextActive: {
101
+ color: '#1D4ED8',
102
+ },
103
+ optionHint: {
104
+ color: '#64748B',
105
+ fontSize: 11,
106
+ fontWeight: '600',
107
+ marginTop: 2,
108
+ },
109
+ });
@@ -1,6 +1,7 @@
1
1
  import React, {useEffect, useMemo, useRef} from 'react';
2
2
  import {NativeModules} from 'react-native';
3
- import {env as APP_ENV} from '@env';
3
+ import {app_type} from '@appType';
4
+ import { env as APP_ENV } from '@env';
4
5
  import {useStore} from '@store';
5
6
  import DeviceInfo from 'react-native-device-info';
6
7
  import {resolveAppDomain} from '@controleonline/ui-common/src/utils/appDomain';
@@ -61,7 +62,7 @@ const BackgroundRuntimeBridge = () => {
61
62
 
62
63
  const bundleId = safeTrim(DeviceInfo.getBundleId());
63
64
  const runtimeAppKey =
64
- bundleId || safeTrim(APP_ENV.APP_TYPE).toUpperCase() || 'app';
65
+ bundleId || safeTrim(app_type).toUpperCase() || 'app';
65
66
  const runtimeDeviceId = normalizeDeviceId(runtimeDevice?.id);
66
67
  const runtimeDeviceType = normalizeDeviceType(
67
68
  runtimeDeviceConfig?.type ||
@@ -70,7 +71,7 @@ const BackgroundRuntimeBridge = () => {
70
71
  );
71
72
  const currentCompanyId = safeTrim(currentCompany?.id);
72
73
  const sessionToken = readSessionToken();
73
- const isManagerRuntime = isManagerAppType(APP_ENV?.APP_TYPE);
74
+ const isManagerRuntime = isManagerAppType(app_type);
74
75
  const managerOrderNotificationPreferences =
75
76
  resolveManagerOrderNotificationPreferences(user);
76
77
  const userAlertSoundUrl = safeTrim(
@@ -162,7 +163,7 @@ const BackgroundRuntimeBridge = () => {
162
163
  companyAlias: safeTrim(currentCompany?.alias || currentCompany?.name),
163
164
  companyId: currentCompanyId,
164
165
  appDomain: safeTrim(resolveAppDomain(APP_ENV.DOMAIN)),
165
- appType: safeTrim(APP_ENV.APP_TYPE).toUpperCase(),
166
+ appType: safeTrim(app_type).toUpperCase(),
166
167
  backgroundEnabled: true,
167
168
  deviceId: runtimeDeviceId,
168
169
  deviceType: runtimeDeviceType,
@@ -3,7 +3,7 @@ import {Pressable, Text, View} from 'react-native';
3
3
  import Icon from 'react-native-vector-icons/Feather';
4
4
  import {useTheme} from './DefaultProvider';
5
5
  import RuntimeInfoFooter from './RuntimeInfoFooter';
6
- import {resolveMenuRouteParams} from '@controleonline/ui-layout/src/react/utils/menuNavigation';
6
+ import {resolveMenuRouteParams} from '../utils/menuNavigation';
7
7
  import createStyles from './BottomNavigationBar.styles';
8
8
 
9
9
  const BottomNavigationBar = ({
@@ -13,40 +13,80 @@ const BottomNavigationBar = ({
13
13
  disabled = false,
14
14
  colors = {},
15
15
  testID = 'bottom-navigation',
16
+ useModernWebChromeProps = false,
16
17
  }) => {
17
18
  const theme = useTheme?.() || {};
18
19
  const runtimeFooter = theme?.runtimeFooter || null;
19
20
  const registerBottomNavigation =
20
21
  theme?.bottomChrome?.registerBottomNavigation || null;
21
- const primaryColor = colors.primary || '#1B5587';
22
- const dockBackground =
23
- colors['toolbar-background'] || colors.background || '#F8FBFF';
24
- const borderColor = colors['toolbar-border'] || colors.border || '#D1DDE9';
25
- const inactiveText =
26
- colors['toolbar-text-muted'] || colors.textSecondary || '#64748B';
27
- const activeBg = `${primaryColor}1A`;
28
- const activeBorder = `${primaryColor}55`;
22
+ const activeBackground = colors.navigationActiveBackground;
23
+ const activeBorder = colors.navigationActiveBorder;
24
+ const activeIcon = colors.navigationActiveIcon;
25
+ const activeText = colors.navigationActiveText;
26
+ const dockBackground = colors.navigationBackground;
27
+ const dockBorder = colors.navigationBorder;
28
+ const dockShadow = colors.navigationShadow;
29
+ const inactiveIcon = colors.navigationIcon;
30
+ const inactiveText = colors.navigationText;
29
31
 
30
32
  const styles = useMemo(
31
33
  () =>
32
34
  createStyles({
33
- primaryColor,
35
+ activeBackground,
34
36
  dockBackground,
35
- borderColor,
36
- inactiveText,
37
- activeBg,
37
+ dockBorder,
38
+ dockShadow,
38
39
  activeBorder,
40
+ useModernWebChromeProps,
39
41
  }),
40
42
  [
41
- activeBg,
43
+ activeBackground,
42
44
  activeBorder,
43
- borderColor,
44
45
  dockBackground,
45
- inactiveText,
46
- primaryColor,
46
+ dockBorder,
47
+ dockShadow,
48
+ useModernWebChromeProps,
47
49
  ],
48
50
  );
49
51
 
52
+ const resolveItemColors = ({isActive, isDisabled}) => {
53
+ if (isActive) {
54
+ return {
55
+ iconColor: activeIcon,
56
+ textColor: activeText,
57
+ };
58
+ }
59
+
60
+ if (isDisabled) {
61
+ return {
62
+ iconColor: colors.navigationDisabledIcon,
63
+ textColor: colors.navigationDisabledText,
64
+ };
65
+ }
66
+
67
+ return {
68
+ iconColor: inactiveIcon,
69
+ textColor: inactiveText,
70
+ };
71
+ };
72
+
73
+ const resolveItemStateStyles = ({isActive, isDisabled}) => [
74
+ styles.item,
75
+ isActive && styles.itemActive,
76
+ isDisabled && styles.itemDisabled,
77
+ isDisabled && {
78
+ backgroundColor: colors.navigationDisabledBackground,
79
+ borderColor: colors.navigationDisabledBorder,
80
+ },
81
+ ];
82
+
83
+ const resolvePressedStyles = ({pressed, isDisabled}) =>
84
+ pressed && !isDisabled
85
+ ? [
86
+ styles.itemPressed,
87
+ ]
88
+ : [];
89
+
50
90
  useLayoutEffect(() => {
51
91
  if (typeof registerBottomNavigation !== 'function') {
52
92
  return undefined;
@@ -69,15 +109,32 @@ const BottomNavigationBar = ({
69
109
  }
70
110
  };
71
111
 
112
+ const hostProps = useModernWebChromeProps
113
+ ? {}
114
+ : {pointerEvents: 'box-none'};
115
+ const hostStyle = useModernWebChromeProps
116
+ ? [styles.host, styles.hostPointerEventsBoxNone]
117
+ : styles.host;
118
+ const footerProps = {
119
+ appVersion: runtimeFooter?.appVersion,
120
+ colors: runtimeFooter?.colors || {},
121
+ defaultCompany: runtimeFooter?.defaultCompany,
122
+ device: runtimeFooter?.device,
123
+ useModernWebChromeProps,
124
+ };
125
+
72
126
  return (
73
- <View pointerEvents="box-none" style={styles.host}>
127
+ <View {...hostProps} style={hostStyle}>
74
128
  <View style={styles.stack}>
75
129
  <View style={styles.dock} testID={testID}>
76
130
  {routeItems.map(item => {
77
131
  const isActive = effectiveActiveRoute === item.route;
78
132
  const isDisabled = disabled || item.disabled;
79
133
  const iconSize = item.iconSize || 18;
80
- const iconColor = isActive ? primaryColor : inactiveText;
134
+ const {iconColor, textColor} = resolveItemColors({
135
+ isActive,
136
+ isDisabled,
137
+ });
81
138
 
82
139
  return (
83
140
  <Pressable
@@ -86,17 +143,20 @@ const BottomNavigationBar = ({
86
143
  disabled={isDisabled}
87
144
  onPress={() => navigateTo(item)}
88
145
  style={({pressed}) => [
89
- styles.item,
90
- isActive && styles.itemActive,
91
- pressed && !isDisabled && styles.itemPressed,
92
- isDisabled && styles.itemDisabled,
146
+ ...resolveItemStateStyles({isActive, isDisabled}),
147
+ ...resolvePressedStyles({pressed, isDisabled}),
93
148
  ]}>
94
- <View style={[styles.iconWrap, isActive && styles.iconWrapActive]}>
149
+ <View style={styles.iconWrap}>
95
150
  <Icon color={iconColor} name={item.icon} size={iconSize} />
96
151
  </View>
97
152
  <Text
98
153
  numberOfLines={1}
99
- style={[styles.itemLabel, isActive && styles.itemLabelActive]}>
154
+ style={[
155
+ styles.itemLabel,
156
+ {
157
+ color: textColor,
158
+ },
159
+ ]}>
100
160
  {item.label}
101
161
  </Text>
102
162
  </Pressable>
@@ -105,12 +165,7 @@ const BottomNavigationBar = ({
105
165
  </View>
106
166
 
107
167
  {runtimeFooter && (
108
- <RuntimeInfoFooter
109
- appVersion={runtimeFooter.appVersion}
110
- colors={runtimeFooter.colors || colors}
111
- defaultCompany={runtimeFooter.defaultCompany}
112
- device={runtimeFooter.device}
113
- />
168
+ <RuntimeInfoFooter {...footerProps} />
114
169
  )}
115
170
  </View>
116
171
  </View>