onairos 2.1.13 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,66 +1,136 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ // Remove the platform connectors - these should be handled in onboarding
4
+ // const platformConnectors = [
5
+ // { name: 'YouTube', icon: '📺', color: 'bg-red-500', connector: 'youtube' },
6
+ // { name: 'LinkedIn', icon: '💼', color: 'bg-blue-700', connector: 'linkedin' },
7
+ // { name: 'Reddit', icon: '🔥', color: 'bg-orange-500', connector: 'reddit' },
8
+ // { name: 'Pinterest', icon: '📌', color: 'bg-red-600', connector: 'pinterest' },
9
+ // { name: 'Instagram', icon: '📷', color: 'bg-pink-500', connector: 'instagram' },
10
+ // { name: 'GitHub', icon: '⚡', color: 'bg-gray-800', connector: 'github' },
11
+ // { name: 'Facebook', icon: '👥', color: 'bg-blue-600', connector: 'facebook' },
12
+ // { name: 'Gmail', icon: '✉️', color: 'bg-red-400', connector: 'gmail' }
13
+ // ];
2
14
 
3
15
  const dataTypes = [
4
16
  {
5
17
  id: 'basic',
6
18
  name: 'Basic Info',
7
- description: 'Essential profile information and account details',
19
+ description: 'Essential profile information, account details, and basic demographics',
8
20
  icon: '👤',
9
- required: false
21
+ required: true, // Auto-selected and non-deselectable
22
+ tooltip: 'Includes name, email, basic profile information. This data is essential for personalization and is always included.',
23
+ privacyLink: 'https://onairos.uk/privacy#basic-info'
10
24
  },
11
25
  {
12
26
  id: 'personality',
13
27
  name: 'Personality',
14
- description: 'Personality traits, behavioral patterns and insights',
28
+ description: 'Personality traits, behavioral patterns and psychological insights',
15
29
  icon: '🧠',
16
- required: false
30
+ required: false,
31
+ tooltip: 'AI-analyzed personality traits based on your social media activity and interactions. Used to improve content recommendations.',
32
+ privacyLink: 'https://onairos.uk/privacy#personality-data'
17
33
  },
18
34
  {
19
35
  id: 'preferences',
20
36
  name: 'Preferences',
21
- description: 'User preferences, settings and choices',
37
+ description: 'User preferences, interests, settings and personal choices',
22
38
  icon: '⚙️',
23
- required: false
39
+ required: false,
40
+ tooltip: 'Your stated preferences and interests from connected platforms. Helps customize your experience.',
41
+ privacyLink: 'https://onairos.uk/privacy#preferences-data'
24
42
  }
25
43
  ];
26
44
 
45
+ // Tooltip component
46
+ const Tooltip = ({ children, content, privacyLink }) => {
47
+ const [showTooltip, setShowTooltip] = useState(false);
48
+
49
+ return (
50
+ <div className="relative inline-block">
51
+ <span
52
+ onMouseEnter={() => setShowTooltip(true)}
53
+ onMouseLeave={() => setShowTooltip(false)}
54
+ className="border-b border-dotted border-gray-400 cursor-help"
55
+ >
56
+ {children}
57
+ </span>
58
+ {showTooltip && (
59
+ <div className="absolute z-50 w-64 p-3 mt-2 text-sm bg-white border border-gray-200 rounded-lg shadow-lg left-0">
60
+ <p className="mb-2 text-gray-700">{content}</p>
61
+ <a
62
+ href={privacyLink}
63
+ target="_blank"
64
+ rel="noopener noreferrer"
65
+ className="text-blue-600 hover:text-blue-800 text-xs font-medium"
66
+ >
67
+ Learn more about privacy →
68
+ </a>
69
+ </div>
70
+ )}
71
+ </div>
72
+ );
73
+ };
74
+
27
75
  export default function DataRequest({
28
76
  onComplete,
29
77
  userEmail,
30
78
  appName = 'App',
31
79
  autoFetch = false,
32
- testMode = false
80
+ testMode = false,
81
+ connectedAccounts = {} // Connected platforms from onboarding
33
82
  }) {
34
83
  const [selectedData, setSelectedData] = useState({
35
- basic: false, // User can choose
84
+ basic: true, // Auto-selected and required
36
85
  personality: false,
37
86
  preferences: false
38
87
  });
88
+
89
+ // Remove connector states - not needed for data request
90
+ // const [connectorStates, setConnectorStates] = useState({});
39
91
  const [isSubmitting, setIsSubmitting] = useState(false);
40
92
  const [isLoadingApi, setIsLoadingApi] = useState(false);
41
93
  const [apiResponse, setApiResponse] = useState(null);
42
94
  const [apiError, setApiError] = useState(null);
43
95
 
96
+ // Remove connector initialization - not needed
97
+ // useEffect(() => {
98
+ // const initialStates = {};
99
+ // platformConnectors.forEach(platform => {
100
+ // initialStates[platform.name] = {
101
+ // connected: connectedAccounts[platform.name] || false,
102
+ // selected: false
103
+ // };
104
+ // });
105
+ // setConnectorStates(initialStates);
106
+ // }, [connectedAccounts]);
107
+
44
108
  const handleDataToggle = (dataId) => {
109
+ // Don't allow toggling required items
110
+ const dataType = dataTypes.find(dt => dt.id === dataId);
111
+ if (dataType?.required) return;
112
+
45
113
  setSelectedData(prev => ({
46
114
  ...prev,
47
115
  [dataId]: !prev[dataId]
48
116
  }));
49
117
  };
50
118
 
119
+ // handleConnectorToggle removed - connectors are handled in onboarding
120
+
51
121
  const handleRowClick = (dataId) => {
52
- // Make the entire row clickable for better UX
122
+ const dataType = dataTypes.find(dt => dt.id === dataId);
123
+ if (dataType?.required) return; // Don't allow clicking required items
53
124
  handleDataToggle(dataId);
54
125
  };
55
126
 
56
127
  const generateUserHash = (email) => {
57
- // Simple hash function for user identification
58
128
  let hash = 0;
59
129
  const str = email + Date.now().toString();
60
130
  for (let i = 0; i < str.length; i++) {
61
131
  const char = str.charCodeAt(i);
62
132
  hash = ((hash << 5) - hash) + char;
63
- hash = hash & hash; // Convert to 32-bit integer
133
+ hash = hash & hash;
64
134
  }
65
135
  return `user_${Math.abs(hash).toString(36)}`;
66
136
  };
@@ -70,7 +140,6 @@ export default function DataRequest({
70
140
  setApiError(null);
71
141
 
72
142
  try {
73
- // Create a unique user hash for this request
74
143
  const userHash = generateUserHash(userEmail);
75
144
 
76
145
  // Get selected data types
@@ -78,16 +147,19 @@ export default function DataRequest({
78
147
  .filter(([key, value]) => value)
79
148
  .map(([key]) => key);
80
149
 
81
- // Map frontend data types to backend confirmation types
150
+ // Get selected connectors
151
+ // const selectedConnectors = Object.entries(connectorStates)
152
+ // .filter(([platform, state]) => state.selected && state.connected)
153
+ // .map(([platform]) => platform);
154
+
82
155
  const mapDataTypesToConfirmations = (approvedData) => {
83
156
  const confirmations = [];
84
157
  const currentDate = new Date().toISOString();
85
158
 
86
- // Map frontend types to backend types according to API expectations
87
159
  const dataTypeMapping = {
88
- 'basic': 'Medium', // Basic info -> Medium data
89
- 'personality': 'Large', // Personality -> Large analysis
90
- 'preferences': 'Traits' // Preferences -> Traits data
160
+ 'basic': 'Medium',
161
+ 'personality': 'Large',
162
+ 'preferences': 'Traits'
91
163
  };
92
164
 
93
165
  approvedData.forEach(dataType => {
@@ -102,46 +174,43 @@ export default function DataRequest({
102
174
  return confirmations;
103
175
  };
104
176
 
105
- // Determine API endpoint based on test mode
106
177
  const apiEndpoint = testMode
107
178
  ? 'https://api2.onairos.uk/inferenceTest'
108
179
  : 'https://api2.onairos.uk/getAPIurlMobile';
109
180
 
110
- // Prepare the base result
111
181
  const baseResult = {
112
182
  userHash,
113
183
  appName,
114
184
  approvedData,
185
+ // selectedConnectors, // Removed as connectors are handled in onboarding
115
186
  apiUrl: apiEndpoint,
116
187
  testMode,
117
188
  timestamp: new Date().toISOString()
118
189
  };
119
190
 
120
191
  if (autoFetch) {
121
- // Auto mode true: make API request and return results
122
192
  try {
123
193
  const confirmations = mapDataTypesToConfirmations(approvedData);
124
194
 
125
- // Format request according to backend expectations
126
195
  const requestBody = testMode ? {
127
- // Test mode: simple format for testing
128
196
  approvedData,
197
+ // selectedConnectors, // Removed as connectors are handled in onboarding
129
198
  userEmail,
130
199
  appName,
131
200
  testMode,
132
201
  timestamp: new Date().toISOString()
133
202
  } : {
134
- // Live mode: proper Info format for backend
135
203
  Info: {
136
204
  storage: "local",
137
205
  appId: appName,
138
206
  confirmations: confirmations,
139
- EncryptedUserPin: "pending_pin_integration", // TODO: Get from user PIN setup
207
+ // connectors: selectedConnectors, // Removed as connectors are handled in onboarding
208
+ EncryptedUserPin: "pending_pin_integration",
140
209
  account: userEmail,
141
210
  proofMode: false,
142
211
  Domain: window.location.hostname,
143
- web3Type: "standard", // or "Othent" if using Othent
144
- OthentSub: null // Only if using Othent authentication
212
+ web3Type: "standard",
213
+ OthentSub: null
145
214
  }
146
215
  };
147
216
 
@@ -149,52 +218,55 @@ export default function DataRequest({
149
218
  method: 'POST',
150
219
  headers: {
151
220
  'Content-Type': 'application/json',
221
+ 'x-api-key': 'onairos_web_sdk_live_key_2024'
152
222
  },
153
223
  body: JSON.stringify(requestBody)
154
224
  });
155
225
 
156
226
  if (!response.ok) {
157
- throw new Error(`API call failed with status: ${response.status}`);
227
+ throw new Error(`API request failed: ${response.status}`);
158
228
  }
159
229
 
160
- const apiData = await response.json();
161
-
162
- // Format response according to test mode requirements
163
- let formattedData = apiData;
164
- if (testMode && apiData) {
165
- formattedData = {
166
- InferenceResult: {
167
- output: apiData.croppedInference || apiData.output || apiData.inference,
168
- traits: apiData.traitResult || apiData.traits || apiData.personalityData
169
- }
170
- };
171
- }
172
-
173
- setApiResponse(formattedData);
174
- return {
230
+ const data = await response.json();
231
+ setApiResponse(data);
232
+
233
+ const result = {
175
234
  ...baseResult,
176
- apiResponse: formattedData,
235
+ apiResponse: data,
177
236
  success: true
178
237
  };
238
+
239
+ setTimeout(() => {
240
+ onComplete(result);
241
+ }, 1500);
242
+
243
+ return result;
244
+
179
245
  } catch (error) {
180
- setApiError(error.message);
181
- return {
246
+ console.error('API request failed:', error);
247
+ setApiError(`API request failed: ${error.message}`);
248
+
249
+ const result = {
182
250
  ...baseResult,
183
- apiError: error.message,
251
+ error: error.message,
184
252
  success: false
185
253
  };
254
+
255
+ setTimeout(() => {
256
+ onComplete(result);
257
+ }, 2000);
258
+
259
+ return result;
186
260
  }
187
261
  } else {
188
- // Auto mode false (default): return API endpoint URL for manual calling
189
- return {
190
- ...baseResult,
191
- success: true,
192
- message: 'Data request approved. Use the provided API URL to fetch user data.'
193
- };
262
+ onComplete(baseResult);
263
+ return baseResult;
194
264
  }
265
+
195
266
  } catch (error) {
196
- setApiError(`Failed to process request: ${error.message}`);
197
- return null;
267
+ console.error('Data processing failed:', error);
268
+ setApiError(`Processing failed: ${error.message}`);
269
+ throw error;
198
270
  } finally {
199
271
  setIsLoadingApi(false);
200
272
  }
@@ -218,6 +290,8 @@ export default function DataRequest({
218
290
  };
219
291
 
220
292
  const selectedCount = Object.values(selectedData).filter(Boolean).length;
293
+ // const selectedConnectorCount = Object.values(connectorStates).filter(state => state.selected).length; // Removed as connectors are handled in onboarding
294
+ // const connectedCount = Object.values(connectorStates).filter(state => state.connected).length; // Removed as connectors are handled in onboarding
221
295
 
222
296
  return (
223
297
  <div className="w-full max-w-md mx-auto bg-white rounded-lg shadow-xl overflow-hidden" style={{ maxHeight: '90vh', height: 'auto' }}>
@@ -225,7 +299,7 @@ export default function DataRequest({
225
299
  <div className="text-center mb-4 sm:mb-6">
226
300
  <h2 className="text-lg sm:text-xl font-bold text-gray-900 mb-2">Data Request</h2>
227
301
  <p className="text-gray-600 text-xs sm:text-sm">
228
- Select the data types you'd like to share with <span className="font-medium">{appName}</span>
302
+ Select the data types and connections to share with <span className="font-medium">{appName}</span>
229
303
  </p>
230
304
  </div>
231
305
 
@@ -237,65 +311,70 @@ export default function DataRequest({
237
311
  </div>
238
312
 
239
313
  {/* Data Types Selection */}
240
- <div className="space-y-2 sm:space-y-3 mb-4 sm:mb-6">
241
- {dataTypes.map((dataType) => {
242
- const isSelected = selectedData[dataType.id] || false;
243
- const isRequired = dataType.required;
244
-
245
- return (
246
- <div
247
- key={dataType.id}
248
- className={`flex items-center justify-between p-3 sm:p-4 border rounded-lg transition-colors ${
249
- isRequired
250
- ? 'bg-blue-50 border-blue-200'
251
- : 'hover:bg-gray-50 cursor-pointer'
252
- }`}
253
- onClick={() => handleRowClick(dataType.id)}
254
- >
255
- <div className="flex items-center space-x-3">
256
- <div className="text-xl sm:text-2xl">
257
- {dataType.icon}
314
+ <div className="mb-6">
315
+ <h3 className="text-md font-semibold text-gray-900 mb-3">Data Types</h3>
316
+ <div className="space-y-2 sm:space-y-3">
317
+ {dataTypes.map((dataType) => {
318
+ const isSelected = selectedData[dataType.id] || false;
319
+ const isRequired = dataType.required;
320
+
321
+ return (
322
+ <div
323
+ key={dataType.id}
324
+ className={`flex items-center justify-between p-3 sm:p-4 border rounded-lg transition-colors ${
325
+ isRequired
326
+ ? 'bg-gray-100 border-gray-300 cursor-not-allowed'
327
+ : 'hover:bg-gray-50 cursor-pointer border-gray-200'
328
+ }`}
329
+ onClick={() => handleRowClick(dataType.id)}
330
+ >
331
+ <div className="flex items-center space-x-3">
332
+ <div className="text-xl sm:text-2xl">
333
+ {dataType.icon}
334
+ </div>
335
+ <div>
336
+ <h4 className="font-medium text-gray-900 text-sm sm:text-base">
337
+ <Tooltip content={dataType.tooltip} privacyLink={dataType.privacyLink}>
338
+ {dataType.name}
339
+ </Tooltip>
340
+ {isRequired && <span className="text-gray-500 ml-1 text-xs">(Required)</span>}
341
+ </h4>
342
+ <p className="text-xs sm:text-sm text-gray-500">{dataType.description}</p>
343
+ </div>
258
344
  </div>
259
- <div>
260
- <h3 className="font-medium text-gray-900 text-sm sm:text-base">
261
- {dataType.name}
262
- {isRequired && <span className="text-blue-600 ml-1">*</span>}
263
- </h3>
264
- <p className="text-xs sm:text-sm text-gray-500">{dataType.description}</p>
265
- </div>
266
- </div>
267
-
268
- {/* Toggle Switch or Required Badge */}
269
- {isRequired ? (
270
- <div className="px-2 py-1 bg-blue-600 text-white text-xs rounded-full">
271
- Required
272
- </div>
273
- ) : (
274
- <button
275
- onClick={(e) => {
276
- e.stopPropagation();
277
- handleDataToggle(dataType.id);
278
- }}
279
- className={`relative inline-flex h-5 sm:h-6 w-9 sm:w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
280
- isSelected ? 'bg-blue-600' : 'bg-gray-200'
281
- }`}
282
- >
283
- <span
284
- className={`inline-block h-3 sm:h-4 w-3 sm:w-4 transform rounded-full bg-white transition-transform ${
285
- isSelected ? 'translate-x-5 sm:translate-x-6' : 'translate-x-1'
345
+
346
+ {/* Toggle Switch or Required Badge */}
347
+ {isRequired ? (
348
+ <div className="px-2 py-1 bg-gray-400 text-white text-xs rounded-full">
349
+ Required
350
+ </div>
351
+ ) : (
352
+ <button
353
+ onClick={(e) => {
354
+ e.stopPropagation();
355
+ handleDataToggle(dataType.id);
356
+ }}
357
+ className={`relative inline-flex h-5 sm:h-6 w-9 sm:w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
358
+ isSelected ? 'bg-blue-600' : 'bg-gray-200'
286
359
  }`}
287
- />
288
- </button>
289
- )}
290
- </div>
291
- );
292
- })}
360
+ >
361
+ <span
362
+ className={`inline-block h-3 sm:h-4 w-3 sm:w-4 transform rounded-full bg-white transition-transform ${
363
+ isSelected ? 'translate-x-5 sm:translate-x-6' : 'translate-x-1'
364
+ }`}
365
+ />
366
+ </button>
367
+ )}
368
+ </div>
369
+ );
370
+ })}
371
+ </div>
293
372
  </div>
294
373
 
295
374
  {/* Selection Summary */}
296
375
  <div className="mb-3 sm:mb-4 p-2 sm:p-3 bg-green-50 border border-green-200 rounded-lg">
297
376
  <p className="text-green-800 text-xs sm:text-sm">
298
- ✅ {selectedCount} data type{selectedCount > 1 ? 's' : ''} selected for sharing
377
+ ✅ {selectedCount} data type{selectedCount > 1 ? 's' : ''} selected
299
378
  </p>
300
379
  </div>
301
380
 
@@ -323,12 +402,12 @@ export default function DataRequest({
323
402
  : 'bg-gray-300 text-gray-500 cursor-not-allowed'
324
403
  }`}
325
404
  >
326
- {isSubmitting ? 'Processing...' : `Share ${selectedCount} data type${selectedCount > 1 ? 's' : ''}`}
405
+ {isSubmitting ? 'Processing...' : `Share Selected Data`}
327
406
  </button>
328
407
 
329
408
  <button
330
409
  type="button"
331
- onClick={() => onComplete({ selectedData: {}, cancelled: true })}
410
+ onClick={() => onComplete({ selectedData: {}, selectedConnectors: [], cancelled: true })}
332
411
  className="w-full py-2 text-gray-500 hover:text-gray-700 text-xs sm:text-sm"
333
412
  >
334
413
  Cancel
@@ -224,6 +224,7 @@ export function OnairosButton({
224
224
  autoFetch={autoFetch}
225
225
  testMode={testMode}
226
226
  appIcon={appIcon}
227
+ connectedAccounts={userData?.connectedAccounts || {}}
227
228
  />
228
229
  );
229
230
 
@@ -8,46 +8,106 @@ function Box({
8
8
  setSelected,
9
9
  number,
10
10
  type,
11
- title}) {
11
+ title
12
+ }) {
12
13
  const [isChecked, setIsChecked] = useState(false);
13
14
 
14
15
  const handleCheckboxChange = (newState) => {
15
16
  setIsChecked(newState);
16
- console.log("This data request is active :", active)
17
- console.log("With new Checked state now being: ", newState)
17
+ console.log("This data request is active:", active);
18
+ console.log("With new Checked state now being:", newState);
19
+
18
20
  // Update the counter
19
21
  if (newState) {
20
22
  changeGranted(1);
21
23
  } else {
22
24
  changeGranted(-1);
23
25
  }
24
- // onSelectionChange(type);
26
+
27
+ // Call parent callback if provided
28
+ if (onSelectionChange) {
29
+ onSelectionChange(newState);
30
+ }
25
31
  };
26
32
 
27
- return (
28
- <div className="group">
29
- <div
30
-
31
- >
32
- <input
33
- // disabled={!active}
34
- type="checkbox"
35
- checked={isChecked}
36
- onChange={(e) => {
37
- // setIsChecked(e.target.checked);
38
-
39
- handleCheckboxChange(e.target.checked);
40
- }}
41
- />
33
+ return (
34
+ <div className="group">
35
+ <div className="relative">
36
+ {/* Custom styled checkbox with clear visual feedback */}
37
+ <label className="flex items-center cursor-pointer">
38
+ <div className="relative">
39
+ <input
40
+ type="checkbox"
41
+ checked={isChecked}
42
+ disabled={!active}
43
+ onChange={(e) => handleCheckboxChange(e.target.checked)}
44
+ className="sr-only" // Hide default checkbox
45
+ />
46
+
47
+ {/* Custom checkbox appearance */}
48
+ <div className={`
49
+ w-6 h-6 border-2 rounded-md flex items-center justify-center transition-all duration-200
50
+ ${!active
51
+ ? 'border-gray-300 bg-gray-100 cursor-not-allowed opacity-50'
52
+ : isChecked
53
+ ? 'border-green-500 bg-green-500 shadow-md transform scale-105'
54
+ : 'border-gray-400 bg-white hover:border-green-400 hover:bg-green-50'
55
+ }
56
+ `}>
57
+ {/* Checkmark icon */}
58
+ {isChecked && (
59
+ <svg
60
+ className="w-4 h-4 text-white animate-pulse"
61
+ fill="currentColor"
62
+ viewBox="0 0 20 20"
63
+ >
64
+ <path
65
+ fillRule="evenodd"
66
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
67
+ clipRule="evenodd"
68
+ />
69
+ </svg>
70
+ )}
71
+ </div>
72
+ </div>
73
+
74
+ {/* Selection status text */}
75
+ <span className={`ml-3 text-sm font-medium transition-colors duration-200 ${
76
+ !active
77
+ ? 'text-gray-400'
78
+ : isChecked
79
+ ? 'text-green-600 font-semibold'
80
+ : 'text-gray-700'
81
+ }`}>
82
+ {!active
83
+ ? 'Not available'
84
+ : isChecked
85
+ ? '✓ Selected'
86
+ : 'Click to select'
87
+ }
88
+ </span>
89
+ </label>
90
+
91
+ {/* Selection indicator badge */}
92
+ {isChecked && active && (
93
+ <div className="absolute -top-2 -right-2 w-4 h-4 bg-green-500 rounded-full flex items-center justify-center animate-bounce">
94
+ <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
95
+ <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
96
+ </svg>
97
+ </div>
98
+ )}
99
+ </div>
42
100
  </div>
43
- </div>
44
- );
101
+ );
45
102
  }
46
103
 
47
104
  Box.propTypes = {
48
105
  active: PropTypes.bool.isRequired,
49
- onSelectionChange: PropTypes.func.isRequired,
50
- disabled: PropTypes.bool,
106
+ onSelectionChange: PropTypes.func,
107
+ changeGranted: PropTypes.func.isRequired,
108
+ setSelected: PropTypes.func,
109
+ number: PropTypes.number,
110
+ type: PropTypes.string,
51
111
  title: PropTypes.string.isRequired
52
112
  };
53
113