@microsoft/power-apps-native-host 0.2.25 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +21 -2
  2. package/compatibility/current.json +0 -1
  3. package/lib/NOTICE.txt +6 -12
  4. package/lib/commonjs/auth/AuthContext.js +74 -18
  5. package/lib/commonjs/bridge/NativePowerAppsBridge.js +11 -18
  6. package/lib/commonjs/bridge/PowerPagesBridge.js +27 -8
  7. package/lib/commonjs/bridge/bridgeState.js +40 -0
  8. package/lib/commonjs/connections/ConnectionSetupScreen.js +459 -80
  9. package/lib/commonjs/connections/PowerPlatformConnectionApi.js +946 -0
  10. package/lib/commonjs/connections/connectionMetadata.js +127 -0
  11. package/lib/commonjs/connections/connectionState.js +96 -0
  12. package/lib/commonjs/connections/disambiguate.js +19 -11
  13. package/lib/commonjs/connections/resolveConnections.js +347 -0
  14. package/lib/commonjs/connections/types.js +106 -0
  15. package/lib/commonjs/{platform → connections}/useConnectionRefs.js +128 -65
  16. package/lib/commonjs/connections/useConnectionSetup.js +111 -52
  17. package/lib/commonjs/controls/FilePicker.js +6 -0
  18. package/lib/commonjs/controls/ImagePicker.js +8 -0
  19. package/lib/commonjs/index.js +166 -4
  20. package/lib/commonjs/logger.js +9 -6
  21. package/lib/commonjs/offline/createOfflineHostActivationCallbacks.js +61 -0
  22. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.js +46 -0
  23. package/lib/commonjs/offline/useOfflineIntegrationWithStartupGate.web.js +13 -0
  24. package/lib/commonjs/platform/DataverseDataSources.js +14 -0
  25. package/lib/commonjs/platform/DataverseGlobalDiscoveryApi.js +49 -0
  26. package/lib/commonjs/platform/GlobalDiscovery.js +51 -0
  27. package/lib/commonjs/platform/PowerPlatformEnvironmentApi.js +6 -106
  28. package/lib/commonjs/provider/PowerAppsHostProvider.js +156 -25
  29. package/lib/commonjs/provider/PowerAppsProvider.js +173 -134
  30. package/lib/commonjs/provider/SessionDetailsGesture.js +216 -0
  31. package/lib/commonjs/telemetry/OfflineSdkTelemetry.js +95 -0
  32. package/lib/commonjs/telemetry/OfflineSdkTelemetry.web.js +9 -0
  33. package/lib/commonjs/telemetry/index.js +22 -3
  34. package/lib/module/auth/AuthContext.js +74 -18
  35. package/lib/module/bridge/NativePowerAppsBridge.js +12 -19
  36. package/lib/module/bridge/PowerPagesBridge.js +27 -8
  37. package/lib/module/bridge/bridgeState.js +34 -0
  38. package/lib/module/connections/ConnectionSetupScreen.js +460 -81
  39. package/lib/module/connections/PowerPlatformConnectionApi.js +932 -0
  40. package/lib/module/connections/connectionMetadata.js +112 -0
  41. package/lib/module/connections/connectionState.js +82 -0
  42. package/lib/module/connections/disambiguate.js +19 -12
  43. package/lib/module/connections/resolveConnections.js +342 -0
  44. package/lib/module/connections/types.js +91 -0
  45. package/lib/module/{platform → connections}/useConnectionRefs.js +129 -66
  46. package/lib/module/connections/useConnectionSetup.js +113 -54
  47. package/lib/module/controls/FilePicker.js +6 -0
  48. package/lib/module/controls/ImagePicker.js +8 -0
  49. package/lib/module/index.js +10 -4
  50. package/lib/module/logger.js +8 -6
  51. package/lib/module/offline/createOfflineHostActivationCallbacks.js +55 -0
  52. package/lib/module/offline/useOfflineIntegrationWithStartupGate.js +42 -0
  53. package/lib/module/offline/useOfflineIntegrationWithStartupGate.web.js +9 -0
  54. package/lib/module/platform/DataverseDataSources.js +10 -0
  55. package/lib/module/platform/DataverseGlobalDiscoveryApi.js +45 -0
  56. package/lib/module/platform/GlobalDiscovery.js +45 -0
  57. package/lib/module/platform/PowerPlatformEnvironmentApi.js +5 -104
  58. package/lib/module/provider/PowerAppsHostProvider.js +159 -28
  59. package/lib/module/provider/PowerAppsProvider.js +177 -138
  60. package/lib/module/provider/SessionDetailsGesture.js +211 -0
  61. package/lib/module/telemetry/OfflineSdkTelemetry.js +91 -0
  62. package/lib/module/telemetry/OfflineSdkTelemetry.web.js +5 -0
  63. package/lib/module/telemetry/index.js +15 -4
  64. package/lib/typescript/commonjs/auth/AuthContext.d.ts +3 -1
  65. package/lib/typescript/commonjs/auth/AuthContext.web.d.ts +1 -0
  66. package/lib/typescript/commonjs/bridge/bridgeState.d.ts +5 -0
  67. package/lib/typescript/commonjs/connections/ConnectionSetupScreen.d.ts +28 -7
  68. package/lib/typescript/commonjs/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  69. package/lib/typescript/commonjs/connections/connectionMetadata.d.ts +66 -0
  70. package/lib/typescript/commonjs/connections/connectionState.d.ts +16 -0
  71. package/lib/typescript/commonjs/connections/disambiguate.d.ts +4 -2
  72. package/lib/typescript/commonjs/{platform → connections}/resolveConnections.d.ts +19 -19
  73. package/lib/typescript/commonjs/connections/types.d.ts +129 -1
  74. package/lib/typescript/commonjs/{platform → connections}/useConnectionRefs.d.ts +33 -24
  75. package/lib/typescript/commonjs/connections/useConnectionSetup.d.ts +12 -8
  76. package/lib/typescript/commonjs/controls/FilePicker.d.ts +7 -1
  77. package/lib/typescript/commonjs/controls/ImagePicker.d.ts +9 -1
  78. package/lib/typescript/commonjs/index.d.ts +17 -10
  79. package/lib/typescript/commonjs/logger.d.ts +4 -4
  80. package/lib/typescript/commonjs/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  81. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  82. package/lib/typescript/commonjs/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  83. package/lib/typescript/commonjs/platform/DataverseDataSources.d.ts +27 -0
  84. package/lib/typescript/commonjs/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  85. package/lib/typescript/commonjs/platform/GlobalDiscovery.d.ts +4 -0
  86. package/lib/typescript/commonjs/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  87. package/lib/typescript/commonjs/provider/PowerAppsHostProvider.d.ts +29 -18
  88. package/lib/typescript/commonjs/provider/PowerAppsProvider.d.ts +30 -8
  89. package/lib/typescript/commonjs/provider/SessionDetailsGesture.d.ts +13 -0
  90. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  91. package/lib/typescript/commonjs/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  92. package/lib/typescript/commonjs/telemetry/index.d.ts +13 -1
  93. package/lib/typescript/module/auth/AuthContext.d.ts +3 -1
  94. package/lib/typescript/module/auth/AuthContext.web.d.ts +1 -0
  95. package/lib/typescript/module/bridge/bridgeState.d.ts +5 -0
  96. package/lib/typescript/module/connections/ConnectionSetupScreen.d.ts +28 -7
  97. package/lib/typescript/module/{platform → connections}/PowerPlatformConnectionApi.d.ts +52 -43
  98. package/lib/typescript/module/connections/connectionMetadata.d.ts +66 -0
  99. package/lib/typescript/module/connections/connectionState.d.ts +16 -0
  100. package/lib/typescript/module/connections/disambiguate.d.ts +4 -2
  101. package/lib/typescript/module/{platform → connections}/resolveConnections.d.ts +19 -19
  102. package/lib/typescript/module/connections/types.d.ts +129 -1
  103. package/lib/typescript/module/{platform → connections}/useConnectionRefs.d.ts +33 -24
  104. package/lib/typescript/module/connections/useConnectionSetup.d.ts +12 -8
  105. package/lib/typescript/module/controls/FilePicker.d.ts +7 -1
  106. package/lib/typescript/module/controls/ImagePicker.d.ts +9 -1
  107. package/lib/typescript/module/index.d.ts +17 -10
  108. package/lib/typescript/module/logger.d.ts +4 -4
  109. package/lib/typescript/module/offline/createOfflineHostActivationCallbacks.d.ts +12 -0
  110. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.d.ts +15 -0
  111. package/lib/typescript/module/offline/useOfflineIntegrationWithStartupGate.web.d.ts +6 -0
  112. package/lib/typescript/module/platform/DataverseDataSources.d.ts +27 -0
  113. package/lib/typescript/module/platform/DataverseGlobalDiscoveryApi.d.ts +3 -0
  114. package/lib/typescript/module/platform/GlobalDiscovery.d.ts +4 -0
  115. package/lib/typescript/module/platform/PowerPlatformEnvironmentApi.d.ts +3 -74
  116. package/lib/typescript/module/provider/PowerAppsHostProvider.d.ts +29 -18
  117. package/lib/typescript/module/provider/PowerAppsProvider.d.ts +30 -8
  118. package/lib/typescript/module/provider/SessionDetailsGesture.d.ts +13 -0
  119. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.d.ts +15 -0
  120. package/lib/typescript/module/telemetry/OfflineSdkTelemetry.web.d.ts +2 -0
  121. package/lib/typescript/module/telemetry/index.d.ts +13 -1
  122. package/package.json +11 -6
  123. package/scripts/metro-logger.js +213 -0
  124. package/scripts/upgrade/runtime.js +13 -7
  125. package/scripts/upgrade-authoring.js +5 -6
  126. package/lib/commonjs/platform/PowerPlatformConnectionApi.js +0 -606
  127. package/lib/commonjs/platform/resolveConnections.js +0 -264
  128. package/lib/module/platform/PowerPlatformConnectionApi.js +0 -595
  129. package/lib/module/platform/resolveConnections.js +0 -259
@@ -10,9 +10,11 @@ var WebBrowser = _interopRequireWildcard(require("expo-web-browser"));
10
10
  var Linking = _interopRequireWildcard(require("expo-linking"));
11
11
  var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
12
12
  var _useConnectionSetup = require("../connections/useConnectionSetup.js");
13
- var _PowerPlatformConnectionApi = require("../platform/PowerPlatformConnectionApi.js");
13
+ var _PowerPlatformConnectionApi = require("./PowerPlatformConnectionApi.js");
14
14
  var _PowerPlatformEnvironmentApi = require("../platform/PowerPlatformEnvironmentApi.js");
15
15
  var _types = require("../connections/types.js");
16
+ var _types2 = require("./types.js");
17
+ var _disambiguate = require("./disambiguate.js");
16
18
  var _jsxRuntime = require("react/jsx-runtime");
17
19
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
18
20
  /*!
@@ -66,12 +68,17 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
66
68
  // ---------------------------------------------------------------------------
67
69
 
68
70
  function ConnectorSetupStep({
71
+ localRefId,
69
72
  connectorId,
70
73
  apiId,
71
74
  environmentId,
72
75
  region,
73
76
  acquireToken,
77
+ onSignOut,
74
78
  isvClientId,
79
+ gatewayObjectIdHint,
80
+ parameterSetName,
81
+ sharedConnectionId,
75
82
  onConnected,
76
83
  renderOAuthWebView
77
84
  }) {
@@ -85,18 +92,24 @@ function ConnectorSetupStep({
85
92
  // useConnectionSetup) so the hook stays focused on connection flow only.
86
93
  const [isLoadingMetadata, setIsLoadingMetadata] = (0, _react.useState)(true);
87
94
  const [metadata, setMetadata] = (0, _react.useState)(null);
95
+ const [metadataError, setMetadataError] = (0, _react.useState)(null);
96
+ const [metadataReload, setMetadataReload] = (0, _react.useState)(0);
88
97
  const [showOAuthModal, setShowOAuthModal] = (0, _react.useState)(false);
89
- // Track if there's already a connected instance the user can reuse.
90
- const [existingConnected, setExistingConnected] = (0, _react.useState)(null);
98
+ const [existingConnected, setExistingConnected] = (0, _react.useState)([]);
99
+ const [existingRepairable, setExistingRepairable] = (0, _react.useState)([]);
100
+ const [selectedParameterSet, setSelectedParameterSet] = (0, _react.useState)(parameterSetName ?? '');
101
+ const [parameterValues, setParameterValues] = (0, _react.useState)({});
102
+ const [gatewayObjectId, setGatewayObjectId] = (0, _react.useState)(gatewayObjectIdHint ?? '');
91
103
  const setup = (0, _useConnectionSetup.useConnectionSetup)({
92
104
  connectorId,
93
105
  environmentId,
94
106
  region,
95
107
  acquireToken,
96
108
  isvClientId,
109
+ sharedConnectionId,
97
110
  onConnected: instance => {
98
111
  setShowOAuthModal(false);
99
- onConnected(apiId, instance);
112
+ onConnected(localRefId, instance);
100
113
  },
101
114
  oauthRedirectUrl,
102
115
  polling: {
@@ -108,46 +121,94 @@ function ConnectorSetupStep({
108
121
  // Fetch connector metadata and check for existing non-connected connections.
109
122
  (0, _react.useEffect)(() => {
110
123
  let cancelled = false;
124
+ setIsLoadingMetadata(true);
125
+ setMetadata(null);
126
+ setMetadataError(null);
127
+ setExistingConnected([]);
128
+ setExistingRepairable([]);
129
+ setSelectedParameterSet(parameterSetName ?? '');
130
+ setParameterValues({});
131
+ setGatewayObjectId(gatewayObjectIdHint ?? '');
111
132
  async function load() {
112
133
  const token = await acquireToken([(0, _PowerPlatformEnvironmentApi.powerPlatformScope)(region)]);
113
134
  if (!token || cancelled) {
114
135
  if (!cancelled) {
115
- setMetadata({
116
- displayName: connectorId,
117
- iconUri: '',
118
- description: ''
119
- });
136
+ setMetadataError('Unable to acquire a token to load connector setup.');
120
137
  setIsLoadingMetadata(false);
121
138
  }
122
139
  return;
123
140
  }
124
- const [meta, instances] = await Promise.all([(0, _PowerPlatformConnectionApi.fetchConnectorMetadata)(connectorId, environmentId, region, token), (0, _PowerPlatformConnectionApi.fetchConnectionsForConnector)(connectorId, environmentId, region, token)]);
125
- if (cancelled) return;
126
- setMetadata(meta ?? {
127
- displayName: connectorId,
128
- iconUri: '',
129
- description: ''
130
- });
141
+ try {
142
+ const [meta, instances] = await Promise.all([(0, _PowerPlatformConnectionApi.fetchConnectorMetadata)(connectorId, environmentId, region, token), (0, _PowerPlatformConnectionApi.fetchConnectionsForConnector)(connectorId, environmentId, region, token)]);
143
+ if (cancelled) return;
144
+ if (!meta) throw new Error('Connector metadata is unavailable.');
145
+ const resolvedMetadata = meta;
146
+ setMetadata(resolvedMetadata);
147
+ const preferredSet = resolvedMetadata.parameterSets.find(set => set.name === parameterSetName) ?? resolvedMetadata.parameterSets[0];
148
+ setSelectedParameterSet(preferredSet?.name ?? '');
149
+ setParameterValues(Object.fromEntries((preferredSet?.parameters ?? []).filter(parameter => parameter.defaultValue !== undefined).map(parameter => [parameter.name, parameter.defaultValue])));
131
150
 
132
- // If there's already a Connected instance the disambiguation didn't pick
133
- // (e.g., appeared after the initial load), use it directly.
134
- const connected = instances.find(i => i.status === _types.ConnectionStatus.Connected);
135
- if (connected) setExistingConnected(connected);
136
- setIsLoadingMetadata(false);
151
+ // If there's already a Connected instance the disambiguation didn't pick
152
+ // (e.g., appeared after the initial load), use it directly.
153
+ const sharedId = sharedConnectionId ? (0, _disambiguate.extractConnectionId)(sharedConnectionId) : undefined;
154
+ const eligibleInstances = sharedId ? instances.filter(instance => (0, _disambiguate.connectionIdsEqual)(instance.id, sharedId)) : instances;
155
+ setExistingConnected(eligibleInstances.filter(instance => instance.status === _types.ConnectionStatus.Connected));
156
+ setExistingRepairable(sharedId ? [] : eligibleInstances.filter(instance => instance.status !== _types.ConnectionStatus.Connected));
157
+ } catch (error) {
158
+ if (!cancelled) {
159
+ setMetadataError(error instanceof Error ? error.message : 'Unable to load connector setup.');
160
+ }
161
+ } finally {
162
+ if (!cancelled) setIsLoadingMetadata(false);
163
+ }
137
164
  }
138
165
  void load();
139
166
  return () => {
140
167
  cancelled = true;
141
168
  };
142
- // eslint-disable-next-line react-hooks/exhaustive-deps
143
- }, [connectorId]);
144
- const handleUseExisting = (0, _react.useCallback)(() => {
145
- if (existingConnected) {
146
- onConnected(apiId, existingConnected);
169
+ }, [acquireToken, connectorId, environmentId, gatewayObjectIdHint, parameterSetName, region, sharedConnectionId, metadataReload]);
170
+ const handleUseExisting = (0, _react.useCallback)(async instance => {
171
+ await setup.useExisting(instance);
172
+ }, [setup]);
173
+ const activeParameterSet = metadata?.parameterSets.find(set => (set.name ?? '') === selectedParameterSet) ?? metadata?.parameterSets[0];
174
+ const selectedCapability = gatewayObjectIdHint ? 'gateway' : 'cloud';
175
+ const visibleParameters = activeParameterSet?.parameters.filter(parameter => !parameter.hidden && (!parameter.capabilities?.length || parameter.capabilities.some(capability => capability.toLowerCase() === selectedCapability))) ?? [];
176
+ const requiredParametersComplete = visibleParameters.every(parameter => {
177
+ if (!parameter.required) return true;
178
+ const value = parameterValues[parameter.name];
179
+ if (typeof value === 'object' && value && 'pfx' in value) {
180
+ return Boolean(value.pfx && value.password);
147
181
  }
148
- }, [existingConnected, apiId, onConnected]);
182
+ return value !== undefined && value !== '';
183
+ });
184
+ const buildInput = (0, _react.useCallback)(() => ({
185
+ parameterSetName: activeParameterSet?.name,
186
+ gatewayObjectId: gatewayObjectId || undefined,
187
+ gatewayParameterName: activeParameterSet?.gatewayParameterName,
188
+ parameters: parameterValues
189
+ }), [activeParameterSet?.name, gatewayObjectId, parameterValues]);
149
190
  const handleConnect = (0, _react.useCallback)(async () => {
150
- await setup.startCreate();
191
+ await setup.startCreate(buildInput());
192
+ }, [setup, buildInput]);
193
+ const handleRepair = (0, _react.useCallback)(async instance => {
194
+ const instanceParameterSet = metadata?.parameterSets.find(parameterSet => parameterSet.name === instance.parameterSetName) ?? activeParameterSet;
195
+ const requiresApiConsent = instanceParameterSet?.requiresApiConsent ?? metadata?.hasOAuthParameter ?? false;
196
+ const input = buildInput();
197
+ const hasEditableInput = Object.keys(input.parameters).length > 0 || Boolean(input.gatewayObjectId);
198
+ await setup.startRepair(instance, !requiresApiConsent || hasEditableInput ? input : undefined, requiresApiConsent);
199
+ }, [setup, metadata, activeParameterSet, buildInput]);
200
+ const completeOAuthRedirect = (0, _react.useCallback)(async redirectUrl => {
201
+ let code;
202
+ if (redirectUrl) {
203
+ try {
204
+ const raw = new URL(redirectUrl).searchParams.get('code') ?? '';
205
+ if (/^[0-9a-f]{32}$/i.test(raw)) code = raw;
206
+ } catch {
207
+ setup.reset();
208
+ return;
209
+ }
210
+ }
211
+ await setup.completeConsent(code);
151
212
  }, [setup]);
152
213
 
153
214
  // Open OAuth UI when consentUrl becomes available (set by startCreate in the hook).
@@ -161,29 +222,24 @@ function ConnectorSetupStep({
161
222
  // custom-scheme redirect (e.g. expo-app://oauth-callback).
162
223
  void (async () => {
163
224
  const result = await WebBrowser.openAuthSessionAsync(setup.consentUrl, oauthRedirectUrl);
164
- // Extract a CommitTokenCode from the redirect URL if present.
165
- let code;
166
- if (result.type === 'success' && result.url) {
167
- const params = new URL(result.url).searchParams;
168
- const raw = params.get('code') ?? '';
169
- // CommitTokenCode is a 32-char lowercase hex string.
170
- if (/^[0-9a-f]{32}$/i.test(raw)) {
171
- code = raw;
172
- }
225
+ if (result.type === 'success') {
226
+ await completeOAuthRedirect(result.url);
227
+ } else {
228
+ setup.reset();
173
229
  }
174
- await setup.completeConsent(code);
175
230
  })();
176
231
  }
177
232
  }
178
- }, [setup.phase, setup.consentUrl, renderOAuthWebView, oauthRedirectUrl]);
179
- const handleWebViewDismiss = (0, _react.useCallback)(async () => {
233
+ }, [setup.phase, setup.consentUrl, renderOAuthWebView, oauthRedirectUrl, completeOAuthRedirect]);
234
+ const handleWebViewDismiss = (0, _react.useCallback)(async redirectUrl => {
180
235
  setShowOAuthModal(false);
181
- await setup.checkNow();
182
- }, [setup]);
236
+ if (redirectUrl) await completeOAuthRedirect(redirectUrl);else await setup.checkNow();
237
+ }, [completeOAuthRedirect, setup]);
183
238
  const displayName = metadata?.displayName ?? connectorId;
184
239
  const description = metadata?.description ?? '';
185
240
  if (isLoadingMetadata) {
186
241
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
242
+ testID: `powerApps.connectionSetup.connector.${connectorId}`,
187
243
  style: styles.connectorCard,
188
244
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
189
245
  style: styles.metadataLoading,
@@ -193,7 +249,36 @@ function ConnectorSetupStep({
193
249
  })
194
250
  });
195
251
  }
252
+ if (metadataError || !metadata) {
253
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
254
+ style: styles.connectorCard,
255
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
256
+ style: styles.connectorName,
257
+ children: connectorId
258
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
259
+ style: styles.errorText,
260
+ children: metadataError ?? 'Unable to load connector setup.'
261
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
262
+ style: styles.button,
263
+ onPress: () => setMetadataReload(value => value + 1),
264
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
265
+ style: styles.buttonText,
266
+ children: "Retry"
267
+ })
268
+ }), onSignOut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
269
+ style: [styles.button, styles.buttonSecondary],
270
+ onPress: () => {
271
+ void onSignOut();
272
+ },
273
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
274
+ style: styles.buttonTextSecondary,
275
+ children: "Sign out"
276
+ })
277
+ }) : null]
278
+ });
279
+ }
196
280
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
281
+ testID: `powerApps.connectionSetup.connector.${connectorId}`,
197
282
  style: styles.connectorCard,
198
283
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
199
284
  style: styles.connectorName,
@@ -201,33 +286,140 @@ function ConnectorSetupStep({
201
286
  }), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
202
287
  style: styles.connectorDescription,
203
288
  children: description
204
- }) : null, setup.phase === 'idle' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
205
- children: existingConnected ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
206
- style: styles.buttonRow,
207
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
208
- style: [styles.button, styles.buttonSecondary],
209
- onPress: handleUseExisting,
289
+ }) : null, setup.phase === 'idle' && !sharedConnectionId && (metadata?.parameterSets.length ?? 0) > 1 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
290
+ style: styles.fieldGroup,
291
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
292
+ style: styles.fieldLabel,
293
+ children: "Authentication type"
294
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
295
+ style: styles.choiceRow,
296
+ children: metadata?.parameterSets.map(parameterSet => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
297
+ style: [styles.choiceButton, selectedParameterSet === (parameterSet.name ?? '') && styles.choiceButtonSelected],
298
+ onPress: () => {
299
+ setSelectedParameterSet(parameterSet.name ?? '');
300
+ setParameterValues(Object.fromEntries(parameterSet.parameters.filter(parameter => parameter.defaultValue !== undefined).map(parameter => [parameter.name, parameter.defaultValue])));
301
+ },
210
302
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
211
- style: styles.buttonTextSecondary,
212
- children: "Use existing"
303
+ style: selectedParameterSet === (parameterSet.name ?? '') ? styles.choiceTextSelected : styles.choiceText,
304
+ children: parameterSet.displayName
213
305
  })
214
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
215
- style: styles.button,
216
- onPress: handleConnect,
306
+ }, parameterSet.name ?? 'default'))
307
+ })]
308
+ }) : null, setup.phase === 'idle' && !sharedConnectionId && visibleParameters.map(parameter => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
309
+ style: styles.fieldGroup,
310
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
311
+ style: styles.fieldLabel,
312
+ children: [parameter.displayName, parameter.required ? ' *' : '']
313
+ }), parameter.allowedValues?.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
314
+ style: styles.choiceRow,
315
+ children: parameter.allowedValues.map(option => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
316
+ style: [styles.choiceButton, parameterValues[parameter.name] === option.value && styles.choiceButtonSelected],
317
+ onPress: () => setParameterValues(values => ({
318
+ ...values,
319
+ [parameter.name]: option.value
320
+ })),
217
321
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
218
- style: styles.buttonText,
219
- children: "Create new"
322
+ style: parameterValues[parameter.name] === option.value ? styles.choiceTextSelected : styles.choiceText,
323
+ children: option.displayName
220
324
  })
325
+ }, option.value))
326
+ }) : parameter.type === 'boolean' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Switch, {
327
+ value: Boolean(parameterValues[parameter.name]),
328
+ onValueChange: value => setParameterValues(values => ({
329
+ ...values,
330
+ [parameter.name]: value
331
+ }))
332
+ }) : parameter.type === 'clientcertificate' ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
333
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
334
+ style: styles.textInput,
335
+ placeholder: "PFX certificate (base64)",
336
+ autoCapitalize: "none",
337
+ value: parameterValues[parameter.name]?.pfx ?? '',
338
+ onChangeText: pfx => setParameterValues(values => ({
339
+ ...values,
340
+ [parameter.name]: {
341
+ pfx,
342
+ password: values[parameter.name]?.password ?? ''
343
+ }
344
+ }))
345
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
346
+ style: styles.textInput,
347
+ placeholder: "Certificate password",
348
+ secureTextEntry: true,
349
+ value: parameterValues[parameter.name]?.password ?? '',
350
+ onChangeText: password => setParameterValues(values => ({
351
+ ...values,
352
+ [parameter.name]: {
353
+ pfx: values[parameter.name]?.pfx ?? '',
354
+ password
355
+ }
356
+ }))
221
357
  })]
222
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
223
- style: styles.button,
358
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
359
+ style: styles.textInput,
360
+ secureTextEntry: parameter.type === 'securestring',
361
+ keyboardType: parameter.type === 'integer' ? 'number-pad' : 'default',
362
+ autoCapitalize: "none",
363
+ value: String(parameterValues[parameter.name] ?? ''),
364
+ onChangeText: value => setParameterValues(values => ({
365
+ ...values,
366
+ [parameter.name]: parameter.type === 'integer' && value !== '' ? /^-?\d+$/.test(value) ? parseInt(value, 10) : '' : value
367
+ }))
368
+ })]
369
+ }, parameter.name)), setup.phase === 'idle' && !sharedConnectionId && gatewayObjectIdHint ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
370
+ style: styles.fieldGroup,
371
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
372
+ style: styles.fieldLabel,
373
+ children: "Gateway"
374
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
375
+ style: styles.textInput,
376
+ value: gatewayObjectId,
377
+ onChangeText: setGatewayObjectId,
378
+ autoCapitalize: "none"
379
+ })]
380
+ }) : null, setup.phase === 'idle' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
381
+ children: [existingConnected.map((instance, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
382
+ testID: index === 0 ? `powerApps.connectionSetup.connector.${connectorId}.useExistingButton` : `powerApps.connectionSetup.connector.${connectorId}.useExistingButton.${instance.id}`,
383
+ style: [styles.button, styles.buttonSecondary],
384
+ onPress: () => handleUseExisting(instance),
385
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
386
+ style: styles.buttonTextSecondary,
387
+ children: ["Use ", instance.displayName || instance.id]
388
+ })
389
+ }, instance.id)), existingRepairable.map(instance => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
390
+ style: [styles.button, styles.buttonSecondary],
391
+ disabled: !requiredParametersComplete,
392
+ onPress: () => handleRepair(instance),
393
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
394
+ style: styles.buttonTextSecondary,
395
+ children: ["Fix ", instance.displayName || instance.id]
396
+ })
397
+ }, instance.id)), sharedConnectionId && existingConnected.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
398
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
399
+ style: styles.errorText,
400
+ children: "You do not have access to the shared connection required by this app."
401
+ }), onSignOut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
402
+ style: [styles.button, styles.buttonSecondary],
403
+ onPress: () => {
404
+ void onSignOut();
405
+ },
406
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
407
+ style: styles.buttonTextSecondary,
408
+ children: "Sign out"
409
+ })
410
+ }) : null]
411
+ }) : !sharedConnectionId ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
412
+ testID: `powerApps.connectionSetup.connector.${connectorId}.${existingConnected.length ? 'createButton' : 'connectButton'}`,
413
+ style: [styles.button, !requiredParametersComplete && styles.buttonDisabled],
414
+ disabled: !requiredParametersComplete,
224
415
  onPress: handleConnect,
225
416
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
226
417
  style: styles.buttonText,
227
- children: "Connect"
418
+ children: existingConnected.length ? 'Create new' : metadata?.hasOAuthParameter ? 'Sign in' : 'Connect'
228
419
  })
229
- })
420
+ }) : null]
230
421
  }), (setup.phase === 'creating' || setup.phase === 'confirming') && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
422
+ testID: `powerApps.connectionSetup.connector.${connectorId}.${setup.phase}`,
231
423
  style: styles.statusRow,
232
424
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
233
425
  size: "small",
@@ -237,6 +429,7 @@ function ConnectorSetupStep({
237
429
  children: setup.phase === 'creating' ? 'Creating connection…' : 'Confirming…'
238
430
  })]
239
431
  }), setup.phase === 'oauth' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
432
+ testID: `powerApps.connectionSetup.connector.${connectorId}.oauth`,
240
433
  style: styles.statusRow,
241
434
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
242
435
  size: "small",
@@ -245,30 +438,43 @@ function ConnectorSetupStep({
245
438
  style: styles.statusText,
246
439
  children: "Waiting for sign-in\u2026"
247
440
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
248
- onPress: handleWebViewDismiss,
441
+ testID: `powerApps.connectionSetup.connector.${connectorId}.doneButton`,
442
+ onPress: () => handleWebViewDismiss(),
249
443
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
250
444
  style: styles.linkText,
251
445
  children: "Done"
252
446
  })
253
447
  })]
254
448
  }), setup.phase === 'connected' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
449
+ testID: `powerApps.connectionSetup.connector.${connectorId}.connected`,
255
450
  style: styles.statusRow,
256
451
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
257
452
  style: styles.successText,
258
453
  children: "\u2713 Connected"
259
454
  })
260
455
  }), setup.phase === 'error' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
456
+ testID: `powerApps.connectionSetup.connector.${connectorId}.error`,
261
457
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
262
458
  style: styles.errorText,
263
459
  children: setup.errorMessage ?? 'Connection failed'
264
460
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
461
+ testID: `powerApps.connectionSetup.connector.${connectorId}.retryButton`,
265
462
  style: [styles.button, styles.buttonDanger],
266
463
  onPress: setup.reset,
267
464
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
268
465
  style: styles.buttonText,
269
466
  children: "Retry"
270
467
  })
271
- })]
468
+ }), onSignOut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
469
+ style: [styles.button, styles.buttonSecondary],
470
+ onPress: () => {
471
+ void onSignOut();
472
+ },
473
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
474
+ style: styles.buttonTextSecondary,
475
+ children: "Sign out"
476
+ })
477
+ }) : null]
272
478
  }), showOAuthModal && setup.consentUrl && renderOAuthWebView ? renderOAuthWebView(setup.consentUrl, handleWebViewDismiss) : null]
273
479
  });
274
480
  }
@@ -291,15 +497,31 @@ function ConnectionSetupScreen({
291
497
  environmentId,
292
498
  region = 'prod',
293
499
  acquireToken,
500
+ onSignOut,
294
501
  isvClientId,
295
502
  onAllConnected,
296
503
  onSkip,
504
+ consentDetails = [],
505
+ resourceConsents = [],
506
+ onConsentDeclined,
507
+ showReview = true,
297
508
  renderOAuthWebView,
298
509
  title = 'Set up connections'
299
510
  }) {
300
511
  // resolvedIds accumulates as each connector is connected.
301
512
  const [resolvedIds, setResolvedIds] = (0, _react.useState)({});
513
+ const [isSubmittingConsent, setIsSubmittingConsent] = (0, _react.useState)(false);
514
+ const [consentError, setConsentError] = (0, _react.useState)(null);
515
+ const [consentDeclined, setConsentDeclined] = (0, _react.useState)(false);
516
+ const autoCompletionStartedRef = _react.default.useRef(false);
302
517
  const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
518
+ const missingReferencesKey = _react.default.useMemo(() => missingConnectors.map(connector => `${connector.localRefId}:${(0, _types2.normalizeApiId)(connector.apiId)}`).sort().join('|'), [missingConnectors]);
519
+ (0, _react.useEffect)(() => {
520
+ setResolvedIds({});
521
+ setConsentError(null);
522
+ setConsentDeclined(false);
523
+ autoCompletionStartedRef.current = false;
524
+ }, [missingReferencesKey]);
303
525
 
304
526
  // Build connectorId (short name) for each missing entry from the schemaMap.
305
527
  // schemaMap keys are data source names; we need to find the ConnectorSchema
@@ -315,26 +537,38 @@ function ConnectionSetupScreen({
315
537
  }
316
538
  return map;
317
539
  }, [schemaMap]);
318
- const handleConnected = (0, _react.useCallback)((apiId, instance) => {
540
+ const handleConnected = (0, _react.useCallback)((localRefId, instance) => {
319
541
  setResolvedIds(prev => ({
320
542
  ...prev,
321
- [apiId]: instance.id
543
+ [localRefId]: instance.id
322
544
  }));
323
545
  }, []);
324
-
325
- // Fire onAllConnected once every connector has been resolved.
326
- // Done in an effect so it never runs inside a state updater (which would
327
- // trigger the "Cannot update a component while rendering" React error).
328
- (0, _react.useEffect)(() => {
329
- if (missingConnectors.length === 0) return;
330
- const allResolved = missingConnectors.every(mc => resolvedIds[mc.apiId] !== undefined);
331
- if (allResolved) {
332
- onAllConnected(resolvedIds);
546
+ const allResolved = missingConnectors.every(mc => resolvedIds[mc.localRefId] !== undefined);
547
+ const handleAllow = (0, _react.useCallback)(async () => {
548
+ if (!allResolved || isSubmittingConsent) return;
549
+ setIsSubmittingConsent(true);
550
+ setConsentError(null);
551
+ try {
552
+ await onAllConnected(resolvedIds);
553
+ } catch (error) {
554
+ setConsentError(error instanceof Error ? error.message : 'Unable to save connection consent.');
555
+ } finally {
556
+ setIsSubmittingConsent(false);
333
557
  }
334
- }, [resolvedIds, missingConnectors, onAllConnected]);
558
+ }, [allResolved, isSubmittingConsent, onAllConnected, resolvedIds]);
559
+ const handleDecline = (0, _react.useCallback)(async () => {
560
+ setConsentDeclined(true);
561
+ await onConsentDeclined?.();
562
+ }, [onConsentDeclined]);
563
+ (0, _react.useEffect)(() => {
564
+ if (showReview || !allResolved || autoCompletionStartedRef.current) return;
565
+ autoCompletionStartedRef.current = true;
566
+ void handleAllow();
567
+ }, [allResolved, handleAllow, showReview]);
335
568
  const resolvedCount = Object.keys(resolvedIds).length;
336
569
  const total = missingConnectors.length;
337
570
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
571
+ testID: "powerApps.connectionSetup.root",
338
572
  style: [styles.container, {
339
573
  paddingTop: insets.top,
340
574
  paddingBottom: insets.bottom
@@ -342,41 +576,121 @@ function ConnectionSetupScreen({
342
576
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
343
577
  contentContainerStyle: styles.scrollContent,
344
578
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
579
+ testID: "powerApps.connectionSetup.title",
345
580
  style: styles.title,
346
581
  children: title
347
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
582
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
348
583
  style: styles.subtitle,
349
- children: ["This app needs ", total === 1 ? 'a connection' : `${total} connections`, " to work. Sign in to each service below to continue."]
584
+ children: total > 0 ? `This app needs ${total === 1 ? 'a connection' : `${total} connections`} to work. Sign in to each service below${showReview ? ', then review access' : ''}.` : 'Review the connections and resources this app will use.'
350
585
  }), resolvedCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
586
+ testID: "powerApps.connectionSetup.progress",
351
587
  style: styles.progressText,
352
588
  children: [resolvedCount, " of ", total, " connected"]
353
589
  }), missingConnectors.map(mc => {
354
- const connectorId = connectorIdByApiId[mc.apiId];
590
+ const connectorId = mc.connectorId ?? connectorIdByApiId[(0, _types2.normalizeApiId)(mc.apiId)];
355
591
  if (!connectorId) {
356
592
  // Unknown connector — skip rendering.
357
593
  return null;
358
594
  }
359
- const isResolved = resolvedIds[mc.apiId] !== undefined;
595
+ const isResolved = resolvedIds[mc.localRefId] !== undefined;
360
596
  if (isResolved) {
361
597
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
598
+ testID: `powerApps.connectionSetup.connector.${connectorId}.resolved`,
362
599
  style: [styles.connectorCard, styles.connectorCardResolved],
363
600
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
364
601
  style: styles.successText,
365
602
  children: ["\u2713 ", connectorId.replace('shared_', '')]
366
603
  })
367
- }, mc.apiId);
604
+ }, mc.localRefId);
368
605
  }
369
606
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(ConnectorSetupStep, {
607
+ localRefId: mc.localRefId,
370
608
  connectorId: connectorId,
371
609
  apiId: mc.apiId,
372
610
  environmentId: environmentId,
373
611
  region: region,
374
612
  acquireToken: acquireToken,
613
+ onSignOut: onSignOut,
375
614
  isvClientId: isvClientId,
615
+ gatewayObjectIdHint: mc.gatewayObjectIdHint,
616
+ parameterSetName: mc.parameterSetName,
617
+ sharedConnectionId: mc.sharedConnectionId,
376
618
  onConnected: handleConnected,
377
619
  renderOAuthWebView: renderOAuthWebView
378
- }, mc.apiId);
379
- })]
620
+ }, mc.localRefId);
621
+ }), showReview && allResolved && !consentDeclined ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
622
+ style: styles.consentSection,
623
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
624
+ style: styles.consentTitle,
625
+ children: "Review access"
626
+ }), consentDetails.map(detail => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
627
+ style: styles.consentItem,
628
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
629
+ style: styles.connectorName,
630
+ children: detail.displayName
631
+ }), detail.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
632
+ style: styles.connectorDescription,
633
+ children: detail.description
634
+ }) : null, detail.appActions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
635
+ style: styles.consentActions,
636
+ children: ["Uses: ", detail.appActions.join(', ')]
637
+ }) : null]
638
+ }, detail.localRefId)), resourceConsents.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
639
+ style: styles.consentItem,
640
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
641
+ style: styles.connectorName,
642
+ children: "Protected resources"
643
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
644
+ style: styles.connectorDescription,
645
+ children: resourceConsents.map(resource => resource.displayName).join(', ')
646
+ })]
647
+ }) : null, consentError ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
648
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
649
+ style: styles.errorText,
650
+ children: consentError
651
+ }), onSignOut ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
652
+ style: [styles.button, styles.buttonSecondary],
653
+ onPress: () => {
654
+ void onSignOut();
655
+ },
656
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
657
+ style: styles.buttonTextSecondary,
658
+ children: "Sign out"
659
+ })
660
+ }) : null]
661
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
662
+ style: [styles.button, isSubmittingConsent && styles.buttonDisabled],
663
+ disabled: isSubmittingConsent,
664
+ onPress: handleAllow,
665
+ children: isSubmittingConsent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
666
+ size: "small",
667
+ color: "#FFF"
668
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
669
+ style: styles.buttonText,
670
+ children: "Allow"
671
+ })
672
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
673
+ style: styles.skipButton,
674
+ onPress: handleDecline,
675
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
676
+ style: styles.skipText,
677
+ children: "Do not allow"
678
+ })
679
+ })]
680
+ }) : null, showReview && consentDeclined ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
681
+ style: styles.consentSection,
682
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
683
+ style: styles.consentTitle,
684
+ children: "Access was not granted"
685
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
686
+ style: styles.button,
687
+ onPress: () => setConsentDeclined(false),
688
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
689
+ style: styles.buttonText,
690
+ children: "Review again"
691
+ })
692
+ })]
693
+ }) : null]
380
694
  }), onSkip ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
381
695
  style: styles.footer,
382
696
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
@@ -472,6 +786,9 @@ const styles = _reactNative.StyleSheet.create({
472
786
  buttonDanger: {
473
787
  backgroundColor: '#C4262E'
474
788
  },
789
+ buttonDisabled: {
790
+ backgroundColor: '#A8A8A8'
791
+ },
475
792
  buttonText: {
476
793
  color: '#FFF',
477
794
  fontWeight: '600',
@@ -527,5 +844,67 @@ const styles = _reactNative.StyleSheet.create({
527
844
  alignItems: 'center',
528
845
  justifyContent: 'center',
529
846
  minHeight: 48
847
+ },
848
+ fieldGroup: {
849
+ marginTop: 12,
850
+ gap: 6
851
+ },
852
+ fieldLabel: {
853
+ color: '#333',
854
+ fontSize: 13,
855
+ fontWeight: '600'
856
+ },
857
+ textInput: {
858
+ minHeight: 42,
859
+ borderWidth: 1,
860
+ borderColor: '#C8C8C8',
861
+ borderRadius: 6,
862
+ paddingHorizontal: 10,
863
+ color: '#1A1A1A',
864
+ backgroundColor: '#FFF'
865
+ },
866
+ choiceRow: {
867
+ flexDirection: 'row',
868
+ flexWrap: 'wrap',
869
+ gap: 8
870
+ },
871
+ choiceButton: {
872
+ borderWidth: 1,
873
+ borderColor: '#C8C8C8',
874
+ borderRadius: 6,
875
+ paddingHorizontal: 10,
876
+ paddingVertical: 8
877
+ },
878
+ choiceButtonSelected: {
879
+ borderColor: '#0078D4',
880
+ backgroundColor: '#EFF6FC'
881
+ },
882
+ choiceText: {
883
+ color: '#333',
884
+ fontSize: 13
885
+ },
886
+ choiceTextSelected: {
887
+ color: '#0078D4',
888
+ fontSize: 13,
889
+ fontWeight: '600'
890
+ },
891
+ consentSection: {
892
+ marginTop: 12,
893
+ paddingTop: 16,
894
+ borderTopWidth: _reactNative.StyleSheet.hairlineWidth,
895
+ borderTopColor: '#D8D8D8'
896
+ },
897
+ consentTitle: {
898
+ fontSize: 18,
899
+ fontWeight: '700',
900
+ color: '#1A1A1A',
901
+ marginBottom: 12
902
+ },
903
+ consentItem: {
904
+ marginBottom: 12
905
+ },
906
+ consentActions: {
907
+ fontSize: 13,
908
+ color: '#444'
530
909
  }
531
910
  });