@controleonline/ui-common 1.2.80 → 1.2.81

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 (90) hide show
  1. package/AGENTS.md +8 -0
  2. package/package.json +9 -3
  3. package/src/api/fetch.js +7 -7
  4. package/src/react/components/AddImportModal.js +6 -2
  5. package/src/react/components/BackgroundRuntimeBridge.js +201 -215
  6. package/src/react/components/ConfirmModal.js +44 -9
  7. package/src/react/components/ConfirmModal.styles.js +55 -41
  8. package/src/react/components/CopyDeviceConfigModal.js +193 -0
  9. package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
  10. package/src/react/components/DefaultProvider.native.js +22 -0
  11. package/src/react/components/DefaultProvider.web.js +25 -1
  12. package/src/react/components/EntityLogBranch.js +482 -0
  13. package/src/react/components/EntityLogCards.js +369 -0
  14. package/src/react/components/EntityLogContent.js +113 -794
  15. package/src/react/components/LinkedOrderProductsTab.js +3 -3
  16. package/src/react/components/PrintService.js +130 -409
  17. package/src/react/components/RemoteCheckoutService.js +2 -2
  18. package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
  19. package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
  20. package/src/react/components/RuntimeInfoFooter.js +12 -15
  21. package/src/react/components/SystemErrorToast.js +49 -29
  22. package/src/react/components/SystemErrorToast.styles.js +43 -38
  23. package/src/react/components/UserAvatar.js +120 -28
  24. package/src/react/config/deviceConfigBootstrap.js +330 -304
  25. package/src/react/hooks/usePrintSpoolEffects.js +161 -0
  26. package/src/react/pages/DeviceDetailPage.js +6 -2882
  27. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
  28. package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
  29. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
  30. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
  31. package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
  32. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
  33. package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
  34. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
  35. package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
  36. package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
  37. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
  38. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
  39. package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
  40. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
  41. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
  42. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
  44. package/src/react/pages/Devices/deviceTypes/index.js +2 -0
  45. package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
  46. package/src/react/pages/Imports.js +3 -2
  47. package/src/react/pages/IntegrationConfigFields.js +126 -0
  48. package/src/react/pages/IntegrationConfigPage.js +122 -591
  49. package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
  50. package/src/react/print/usePrintButtonController.js +9 -1
  51. package/src/react/services/Getnet/Checkout.js +81 -0
  52. package/src/react/services/Getnet/Getnet.js +77 -0
  53. package/src/react/services/paymentGatewayExecution.js +20 -0
  54. package/src/react/utils/bottomNavigationToolbar.js +161 -0
  55. package/src/react/utils/commercialDocumentOrders.js +18 -6
  56. package/src/react/utils/copyDeviceConfigs.js +198 -0
  57. package/src/react/utils/fileUrl.js +43 -0
  58. package/src/react/utils/importStatus.js +19 -10
  59. package/src/react/utils/logSettings.js +7 -7
  60. package/src/react/utils/maintenanceSettings.js +16 -0
  61. package/src/react/utils/paymentDevices.js +6 -0
  62. package/src/react/utils/printManagedPrinter.js +45 -0
  63. package/src/react/utils/printRouting.js +63 -0
  64. package/src/react/utils/printSpoolUtils.js +119 -0
  65. package/src/react/utils/printerDevices.js +368 -368
  66. package/src/react/utils/runtimeFooter.js +10 -19
  67. package/src/react/utils/shopFranchises.js +186 -65
  68. package/src/react/utils/systemErrorMessage.js +71 -67
  69. package/src/store/device_config/customActions.js +119 -30
  70. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  71. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  72. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  73. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  74. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  75. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  76. package/src/tests/react/components/UserAvatar.test.js +6 -2
  77. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  78. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  79. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  80. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  81. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  82. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  83. package/src/tests/react/utils/fileUrl.test.js +38 -0
  84. package/src/tests/react/utils/importStatus.test.js +26 -9
  85. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  86. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  87. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  88. package/src/tests/react/utils/runtimeFooter.test.js +7 -0
  89. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  90. package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
@@ -125,7 +125,7 @@ const Checkout = () => {
125
125
 
126
126
  if (!paidStatusIri) {
127
127
  invoiceActions.setError(
128
- 'Nao foi possivel resolver o status pago da invoice do PDV.',
128
+ 'Não foi possível resolver o status pago da invoice do PDV.',
129
129
  );
130
130
  return;
131
131
  }
@@ -259,7 +259,7 @@ const Checkout = () => {
259
259
  } catch (error) {
260
260
  const errorMessage = normalizeGatewayPaymentError(
261
261
  error,
262
- 'Nao foi possivel concluir o pagamento remoto.',
262
+ 'Não foi possível concluir o pagamento remoto.',
263
263
  );
264
264
  const wasCanceled = isRemotePaymentCancellation({
265
265
  status: error?.status,
@@ -1,27 +1,23 @@
1
- import React, {useEffect, useMemo, useState} from 'react';
2
- import {useNavigationState} from '@react-navigation/native';
3
- import {useStore} from '@store';
4
- import {api} from '@controleonline/ui-common/src/api';
5
- import {app_type} from '@appType';
6
- import BottomNavigationBar from '@controleonline/ui-common/src/react/components/BottomNavigationBar';
1
+ import React, {useEffect, useMemo, useState} from 'react';
2
+ import {useNavigationState} from '@react-navigation/native';
3
+ import {useStore} from '@store';
4
+ import {api} from '@controleonline/ui-common/src/api';
5
+ import {app_type} from '@appType';
6
+ import BottomNavigationBar from '@controleonline/ui-common/src/react/components/BottomNavigationBar';
7
7
  import {
8
8
  getBottomNavigationPreset,
9
- resolveBottomNavigationItems,
10
9
  resolveBottomNavigationRoute,
11
10
  } from '@controleonline/ui-common/src/react/components/BottomNavigationBar.config';
12
- import {
13
- filterRuntimeMenuModulesByType,
14
- normalizeAppType,
15
- normalizeRuntimeMenuResponse,
16
- resolveRuntimeMenuLabel,
17
- } from '@controleonline/ui-common/src/react/utils/runtimeMenu';
18
-
19
- const sortRuntimeMenuItems = (left, right) => {
20
- const orderDiff = Number(left?.sortOrder || 0) - Number(right?.sortOrder || 0);
21
- if (orderDiff !== 0) return orderDiff;
22
- return String(left?.label || '').localeCompare(String(right?.label || ''));
23
- };
24
-
11
+ import {
12
+ normalizeAppType,
13
+ normalizeRuntimeMenuResponse,
14
+ resolveRuntimeMenuLabel,
15
+ } from '@controleonline/ui-common/src/react/utils/runtimeMenu';
16
+ const {
17
+ mapRuntimeMenusToNavItems,
18
+ resolveToolbarRuntimeMenus,
19
+ } = require('@controleonline/ui-common/src/react/utils/bottomNavigationToolbar');
20
+
25
21
  const RuntimeBottomNavigationBar = ({
26
22
  activeRouteName: activeRouteNameProp,
27
23
  colors: colorsOverride,
@@ -30,15 +26,15 @@ const RuntimeBottomNavigationBar = ({
30
26
  itemMapper,
31
27
  menuType = 'toolbar',
32
28
  navigation,
33
- presetKey,
34
- routeAliases: routeAliasesProp,
35
- testID = 'bottom-navigation',
36
- useModernWebChromeProps = false,
37
- }) => {
38
- const state = useNavigationState(current => current);
39
- const activeRouteName =
40
- activeRouteNameProp || state?.routes?.[state.index]?.name || 'HomePage';
41
-
29
+ presetKey,
30
+ routeAliases: routeAliasesProp,
31
+ testID = 'bottom-navigation',
32
+ useModernWebChromeProps = false,
33
+ }) => {
34
+ const state = useNavigationState(current => current);
35
+ const activeRouteName =
36
+ activeRouteNameProp || state?.routes?.[state.index]?.name || 'HomePage';
37
+
42
38
  const themeStore = useStore('theme');
43
39
  const peopleStore = useStore('people');
44
40
  const themeGetters = themeStore?.getters || {};
@@ -50,129 +46,128 @@ const RuntimeBottomNavigationBar = ({
50
46
  const routeAliases = routeAliasesProp || preset?.routeAliases || {};
51
47
  const appType = normalizeAppType(app_type);
52
48
 
49
+ // Always consult menus-people for role-aware toolbar; preset is fallback only.
53
50
  useEffect(() => {
54
51
  if (!currentCompany?.id || !menuType) {
55
- setRuntimeMenus([]);
52
+ setRuntimeMenus(
53
+ resolveToolbarRuntimeMenus({
54
+ apiMenus: [],
55
+ preset,
56
+ presetKey,
57
+ menuType,
58
+ translate: global.t?.t,
59
+ }),
60
+ );
56
61
  return undefined;
57
62
  }
58
63
 
59
- if (preset) {
60
- const presetMenus = Array.isArray(preset?.items)
61
- ? resolveBottomNavigationItems(
62
- preset.items.map((item, index) => ({
63
- ...item,
64
- menuType,
65
- sortOrder: Number(item?.sortOrder || (index + 1) * 10),
66
- })),
67
- global.t?.t,
68
- )
69
- : [];
64
+ let cancelled = false;
65
+ setRuntimeMenus([]);
70
66
 
71
- setRuntimeMenus([
72
- {
73
- id: presetKey || 'runtime-bottom-navigation',
74
- label: preset?.label || presetKey || '',
75
- icon: preset?.icon || '',
76
- menus: presetMenus,
67
+ api
68
+ .fetch('menus-people', {
69
+ params: {
70
+ myCompany: currentCompany.id,
71
+ appType,
72
+ menuType,
77
73
  },
78
- ]);
79
- return undefined;
74
+ })
75
+ .then(result => {
76
+ if (cancelled) return;
77
+ const apiMenus = normalizeRuntimeMenuResponse(result, {
78
+ appType,
79
+ allowFallback: false,
80
+ });
81
+ setRuntimeMenus(
82
+ resolveToolbarRuntimeMenus({
83
+ apiMenus,
84
+ preset,
85
+ presetKey,
86
+ menuType,
87
+ translate: global.t?.t,
88
+ }),
89
+ );
90
+ })
91
+ .catch(() => {
92
+ if (cancelled) return;
93
+ setRuntimeMenus(
94
+ resolveToolbarRuntimeMenus({
95
+ apiMenus: [],
96
+ preset,
97
+ presetKey,
98
+ menuType,
99
+ translate: global.t?.t,
100
+ }),
101
+ );
102
+ });
103
+
104
+ return () => {
105
+ cancelled = true;
106
+ };
107
+ }, [appType, currentCompany?.id, menuType, preset, presetKey]);
108
+
109
+ const colors = useMemo(() => {
110
+ if (colorsOverride) {
111
+ return colorsOverride;
80
112
  }
81
113
 
82
- let cancelled = false;
83
- setRuntimeMenus([]);
84
-
85
- api
86
- .fetch('menus-people', {
87
- params: {
88
- myCompany: currentCompany.id,
89
- appType,
90
- menuType,
91
- },
92
- })
93
- .then(result => {
94
- if (!cancelled) {
95
- setRuntimeMenus(
96
- normalizeRuntimeMenuResponse(result, {
97
- appType,
98
- allowFallback: false,
99
- }),
100
- );
101
- }
102
- })
103
- .catch(() => {
104
- if (!cancelled) {
105
- setRuntimeMenus([]);
106
- }
107
- });
108
-
109
- return () => {
110
- cancelled = true;
111
- };
112
- }, [
113
- appType,
114
- currentCompany?.id,
115
- menuType,
116
- preset,
117
- presetKey,
118
- ]);
119
-
120
- const colors = useMemo(() => {
121
- if (colorsOverride) {
122
- return colorsOverride;
123
- }
124
-
125
- return {
126
- ...themeColors,
127
- ...(currentCompany?.theme?.colors || {}),
128
- };
129
- }, [colorsOverride, currentCompany?.theme?.colors, themeColors]);
130
-
131
- const navItems = useMemo(() => {
132
- const mapper = typeof itemMapper === 'function' ? itemMapper : item => item;
133
- const filter = typeof itemFilter === 'function' ? itemFilter : () => true;
134
-
135
- return filterRuntimeMenuModulesByType(runtimeMenus, menuType)
136
- .flatMap(module => (Array.isArray(module?.menus) ? module.menus : []))
137
- .map(item => mapper({...item}))
138
- .filter(Boolean)
139
- .filter(filter)
140
- .sort(sortRuntimeMenuItems)
141
- .map(item => ({
142
- route: item.route,
143
- icon: item.icon || 'circle',
144
- label: resolveRuntimeMenuLabel(item, global.t?.t),
145
- routeParams: item.routeParams,
146
- menuType: item.menuType,
147
- }))
148
- .filter(item => Boolean(item.route));
149
- }, [itemFilter, itemMapper, menuType, runtimeMenus]);
150
-
151
- const resolvedActiveRoute = resolveBottomNavigationRoute(
152
- routeAliases,
153
- activeRouteName,
154
- );
155
- const knownRoute = navItems.some(item => item.route === resolvedActiveRoute);
156
- const effectiveActiveRoute = knownRoute
157
- ? resolvedActiveRoute
158
- : navItems[0]?.route || activeRouteName;
159
-
160
- const isDisabled =
161
- typeof disabledOverride === 'boolean'
162
- ? disabledOverride
163
- : !currentCompany || Object.keys(currentCompany).length === 0;
164
-
165
- return (
166
- <BottomNavigationBar
167
- activeRouteName={effectiveActiveRoute}
168
- colors={colors}
169
- disabled={isDisabled}
170
- items={navItems}
171
- navigation={navigation}
172
- testID={testID}
173
- useModernWebChromeProps={useModernWebChromeProps}
174
- />
175
- );
176
- };
177
-
178
- export default RuntimeBottomNavigationBar;
114
+ return {
115
+ ...themeColors,
116
+ ...(currentCompany?.theme?.colors || {}),
117
+ };
118
+ }, [colorsOverride, currentCompany?.theme?.colors, themeColors]);
119
+
120
+ const navItems = useMemo(() => {
121
+ const mapper = typeof itemMapper === 'function' ? itemMapper : item => item;
122
+ const filter = typeof itemFilter === 'function' ? itemFilter : () => true;
123
+ const translate = global.t?.t;
124
+
125
+ return mapRuntimeMenusToNavItems(runtimeMenus, menuType)
126
+ .map(item => {
127
+ const label =
128
+ resolveRuntimeMenuLabel(
129
+ {menuKey: item.menuKey, label: item.label},
130
+ translate,
131
+ ) ||
132
+ (typeof item.label === 'string' ? item.label : '') ||
133
+ item.menuKey ||
134
+ item.route;
135
+
136
+ return mapper({
137
+ ...item,
138
+ label,
139
+ });
140
+ })
141
+ .filter(Boolean)
142
+ .filter(filter)
143
+ .filter(item => Boolean(item?.route));
144
+ }, [itemFilter, itemMapper, menuType, runtimeMenus]);
145
+
146
+ const resolvedActiveRoute = resolveBottomNavigationRoute(
147
+ routeAliases,
148
+ activeRouteName,
149
+ );
150
+ const knownRoute = navItems.some(item => item.route === resolvedActiveRoute);
151
+ const effectiveActiveRoute = knownRoute
152
+ ? resolvedActiveRoute
153
+ : navItems[0]?.route || activeRouteName;
154
+
155
+ const isDisabled =
156
+ typeof disabledOverride === 'boolean'
157
+ ? disabledOverride
158
+ : !currentCompany || Object.keys(currentCompany).length === 0;
159
+
160
+ return (
161
+ <BottomNavigationBar
162
+ activeRouteName={effectiveActiveRoute}
163
+ colors={colors}
164
+ disabled={isDisabled}
165
+ items={navItems}
166
+ navigation={navigation}
167
+ testID={testID}
168
+ useModernWebChromeProps={useModernWebChromeProps}
169
+ />
170
+ );
171
+ };
172
+
173
+ export default RuntimeBottomNavigationBar;
@@ -0,0 +1,137 @@
1
+ const React = require('react');
2
+ const {useEffect, useRef, useState} = React;
3
+ const {Animated, Text, View} = require('react-native');
4
+
5
+ const GAP_PX = 32;
6
+ const MS_PER_PX = 28;
7
+ const MIN_DURATION_MS = 4000;
8
+ const HOLD_MS = 1200;
9
+
10
+ /**
11
+ * Single-line footer text. When content overflows the available width,
12
+ * scrolls horizontally in a continuous loop. Short text stays static.
13
+ */
14
+ const RuntimeFooterMarqueeText = ({
15
+ text,
16
+ color,
17
+ style,
18
+ opacity,
19
+ testID = 'runtime-footer-marquee-text',
20
+ }) => {
21
+ const [containerWidth, setContainerWidth] = useState(0);
22
+ const [contentWidth, setContentWidth] = useState(0);
23
+ const translateX = useRef(new Animated.Value(0)).current;
24
+ const animationRef = useRef(null);
25
+
26
+ const shouldMarquee =
27
+ containerWidth > 0 && contentWidth > containerWidth + 2;
28
+
29
+ useEffect(() => {
30
+ if (animationRef.current) {
31
+ animationRef.current.stop();
32
+ animationRef.current = null;
33
+ }
34
+ translateX.stopAnimation();
35
+ translateX.setValue(0);
36
+
37
+ if (!shouldMarquee) {
38
+ return undefined;
39
+ }
40
+
41
+ const distance = contentWidth + GAP_PX;
42
+ const duration = Math.max(Math.round(distance * MS_PER_PX), MIN_DURATION_MS);
43
+
44
+ const loop = Animated.loop(
45
+ Animated.sequence([
46
+ Animated.delay(HOLD_MS),
47
+ Animated.timing(translateX, {
48
+ toValue: -distance,
49
+ duration,
50
+ useNativeDriver: true,
51
+ }),
52
+ Animated.timing(translateX, {
53
+ toValue: 0,
54
+ duration: 0,
55
+ useNativeDriver: true,
56
+ }),
57
+ ]),
58
+ );
59
+
60
+ animationRef.current = loop;
61
+ loop.start();
62
+
63
+ return () => {
64
+ loop.stop();
65
+ animationRef.current = null;
66
+ translateX.stopAnimation();
67
+ };
68
+ }, [shouldMarquee, contentWidth, containerWidth, text, translateX]);
69
+
70
+ const handleContainerLayout = event => {
71
+ const nextWidth = Math.round(event?.nativeEvent?.layout?.width || 0);
72
+ setContainerWidth(prev => (prev === nextWidth ? prev : nextWidth));
73
+ };
74
+
75
+ const handleTextLayout = event => {
76
+ const nextWidth = Math.round(event?.nativeEvent?.layout?.width || 0);
77
+ setContentWidth(prev => (prev === nextWidth ? prev : nextWidth));
78
+ };
79
+
80
+ const textStyle = [
81
+ style,
82
+ {
83
+ color,
84
+ flex: undefined,
85
+ flexShrink: 0,
86
+ flexGrow: 0,
87
+ },
88
+ shouldMarquee ? {textAlign: 'left'} : {textAlign: 'center'},
89
+ ];
90
+
91
+ return React.createElement(
92
+ View,
93
+ {
94
+ testID,
95
+ style: {flex: 1, overflow: 'hidden', justifyContent: 'center'},
96
+ onLayout: handleContainerLayout,
97
+ },
98
+ React.createElement(
99
+ Animated.View,
100
+ {
101
+ style: {
102
+ flexDirection: 'row',
103
+ alignItems: 'center',
104
+ opacity,
105
+ transform: [{translateX}],
106
+ alignSelf: shouldMarquee ? 'flex-start' : 'stretch',
107
+ },
108
+ },
109
+ React.createElement(
110
+ Text,
111
+ {
112
+ numberOfLines: 1,
113
+ ellipsizeMode: 'clip',
114
+ onLayout: handleTextLayout,
115
+ style: textStyle,
116
+ },
117
+ text,
118
+ ),
119
+ shouldMarquee
120
+ ? React.createElement(
121
+ Text,
122
+ {
123
+ numberOfLines: 1,
124
+ ellipsizeMode: 'clip',
125
+ accessible: false,
126
+ importantForAccessibility: 'no',
127
+ style: [...textStyle, {marginLeft: GAP_PX}],
128
+ },
129
+ text,
130
+ )
131
+ : null,
132
+ ),
133
+ );
134
+ };
135
+
136
+ module.exports = RuntimeFooterMarqueeText;
137
+ module.exports.default = RuntimeFooterMarqueeText;
@@ -21,6 +21,7 @@ import {
21
21
  getRuntimeFooterTextLines,
22
22
  } from '@controleonline/ui-common/src/react/utils/runtimeFooter';
23
23
  import styles from './RuntimeInfoFooter.styles';
24
+ import RuntimeFooterMarqueeText from './RuntimeFooterMarqueeText';
24
25
 
25
26
  const ROTATION_INTERVAL_MS = 4000;
26
27
  const FADE_DURATION_MS = 260;
@@ -256,21 +257,17 @@ const RuntimeInfoFooter = ({
256
257
  ]}
257
258
  />
258
259
  </View>
259
- <Animated.Text
260
- numberOfLines={1}
261
- ellipsizeMode="tail"
262
- minimumFontScale={0.85}
263
- style={[
264
- styles.primaryText,
265
- {
266
- opacity: showDebugInfo ? 1 : fadeOpacity,
267
- },
268
- {
269
- color: textColor,
270
- },
271
- ]}>
272
- {showDebugInfo ? inlineText || primaryText || device?.id || '--' : displayedText}
273
- </Animated.Text>
260
+ <RuntimeFooterMarqueeText
261
+ text={
262
+ showDebugInfo
263
+ ? inlineText || primaryText || device?.id || '--'
264
+ : displayedText
265
+ }
266
+ color={textColor}
267
+ opacity={showDebugInfo ? 1 : fadeOpacity}
268
+ style={styles.primaryText}
269
+ testID="runtime-footer-primary-text"
270
+ />
274
271
  <View style={styles.loadingWrap}>
275
272
  {hasStoreLoading && (
276
273
  <ActivityIndicator color={loadingColor} size="small" />
@@ -1,29 +1,49 @@
1
- import React, {useMemo} from 'react';
2
- import {Text, View} from 'react-native';
3
- import Icon from 'react-native-vector-icons/MaterialIcons';
4
-
5
- import styles from './SystemErrorToast.styles';
6
- import {resolveSystemErrorMessage} from '@controleonline/ui-common/src/react/utils/systemErrorMessage';
7
-
8
- const SystemErrorToast = ({error}) => {
9
- const message = useMemo(() => resolveSystemErrorMessage(error), [error]);
10
-
11
- if (!message) {
12
- return null;
13
- }
14
-
15
- return (
16
- <View style={styles.container}>
17
- <View style={styles.header}>
18
- <View style={styles.iconWrap}>
19
- <Icon name="error-outline" size={16} color="#DC2626" />
20
- </View>
21
- </View>
22
- <View style={styles.copyWrap}>
23
- <Text style={styles.text}>{message}</Text>
24
- </View>
25
- </View>
26
- );
27
- };
28
-
29
- export default SystemErrorToast;
1
+ import React, {useMemo} from 'react';
2
+ import {Text, View} from 'react-native';
3
+ import Icon from 'react-native-vector-icons/MaterialIcons';
4
+ import {useStore} from '@store';
5
+
6
+ import createStyles from './SystemErrorToast.styles';
7
+ import {resolveSystemErrorMessage} from '@controleonline/ui-common/src/react/utils/systemErrorMessage';
8
+
9
+ const SystemErrorToast = ({error}) => {
10
+ const themeStore = useStore('theme');
11
+ const themeColors = themeStore?.getters?.colors || {};
12
+
13
+ const palette = useMemo(
14
+ () => ({
15
+ toastDangerBackground: themeColors.toastDangerBackground,
16
+ toastDangerBorder: themeColors.toastDangerBorder,
17
+ toastDangerIcon: themeColors.toastDangerIcon,
18
+ toastDangerText: themeColors.toastDangerText,
19
+ }),
20
+ [
21
+ themeColors.toastDangerBackground,
22
+ themeColors.toastDangerBorder,
23
+ themeColors.toastDangerIcon,
24
+ themeColors.toastDangerText,
25
+ ],
26
+ );
27
+
28
+ const styles = useMemo(() => createStyles(palette), [palette]);
29
+ const message = useMemo(() => resolveSystemErrorMessage(error), [error]);
30
+
31
+ if (!message) {
32
+ return null;
33
+ }
34
+
35
+ return (
36
+ <View style={styles.container}>
37
+ <View style={styles.header}>
38
+ <View style={styles.iconWrap}>
39
+ <Icon name="error-outline" size={16} color={palette.toastDangerIcon} />
40
+ </View>
41
+ </View>
42
+ <View style={styles.copyWrap}>
43
+ <Text style={styles.text}>{message}</Text>
44
+ </View>
45
+ </View>
46
+ );
47
+ };
48
+
49
+ export default SystemErrorToast;
@@ -1,38 +1,43 @@
1
- import {StyleSheet} from 'react-native';
2
-
3
- const styles = StyleSheet.create({
4
- container: {
5
- flex: 1,
6
- flexDirection: 'row',
7
- alignItems: 'flex-start',
8
- gap: 12,
9
- paddingVertical: 2,
10
- },
11
- header: {
12
- paddingTop: 1,
13
- },
14
- iconWrap: {
15
- width: 26,
16
- height: 26,
17
- borderRadius: 13,
18
- backgroundColor: '#FEE2E2',
19
- alignItems: 'center',
20
- justifyContent: 'center',
21
- flexShrink: 0,
22
- borderWidth: 1,
23
- borderColor: '#FCA5A5',
24
- },
25
- copyWrap: {
26
- flex: 1,
27
- minWidth: 0,
28
- paddingTop: 2,
29
- },
30
- text: {
31
- color: '#111827',
32
- fontSize: 13,
33
- fontWeight: '700',
34
- lineHeight: 20,
35
- },
36
- });
37
-
38
- export default styles;
1
+ import {StyleSheet} from 'react-native';
2
+
3
+ /**
4
+ * Theme-aware styles for SystemErrorToast.
5
+ * Colors must come from themeStore.getters.colors (canonical tokens only).
6
+ */
7
+ const createStyles = palette =>
8
+ StyleSheet.create({
9
+ container: {
10
+ flex: 1,
11
+ flexDirection: 'row',
12
+ alignItems: 'flex-start',
13
+ gap: 12,
14
+ paddingVertical: 2,
15
+ },
16
+ header: {
17
+ paddingTop: 1,
18
+ },
19
+ iconWrap: {
20
+ width: 26,
21
+ height: 26,
22
+ borderRadius: 13,
23
+ backgroundColor: palette.toastDangerBackground,
24
+ alignItems: 'center',
25
+ justifyContent: 'center',
26
+ flexShrink: 0,
27
+ borderWidth: 1,
28
+ borderColor: palette.toastDangerBorder,
29
+ },
30
+ copyWrap: {
31
+ flex: 1,
32
+ minWidth: 0,
33
+ paddingTop: 2,
34
+ },
35
+ text: {
36
+ color: palette.toastDangerText,
37
+ fontSize: 13,
38
+ fontWeight: '700',
39
+ lineHeight: 20,
40
+ },
41
+ });
42
+
43
+ export default createStyles;