onairos 2.0.7 → 2.0.9
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.
- package/SDK_MIGRATION_SUMMARY.md +140 -0
- package/dist/iframe.bundle.js +1 -1
- package/dist/iframe.bundle.js.map +1 -1
- package/dist/onairos.bundle.js +1 -1
- package/dist/onairos.bundle.js.map +1 -1
- package/dist/onairos.esm.js +1 -1
- package/dist/onairos.esm.js.map +1 -1
- package/onairos.d.ts +0 -182
- package/package.json +8 -18
- package/src/components/DataRequest.js +138 -127
- package/src/components/UniversalOnboarding.js +72 -18
- package/src/components/connectors/GmailConnector.js +177 -0
- package/src/components/connectors/InstagramConnector.js +171 -0
- package/src/components/connectors/LinkedInConnector.js +168 -0
- package/src/components/connectors/PinterestConnector.js +168 -0
- package/src/components/connectors/README.md +292 -0
- package/src/components/connectors/RedditConnector.js +168 -0
- package/src/components/connectors/YoutubeConnector.js +172 -0
- package/src/components/connectors/index.js +21 -0
- package/src/components/utils/UpdateConnections.js +43 -0
- package/src/iframe/DataRequestPage.jsx +5 -1
- package/src/index.js +0 -9
- package/src/onairos.jsx +0 -16
- package/src/onairosButton.jsx +2 -1
- package/src/overlay/overlay.js +1 -1
- package/src/sdk/LLMWrapper.js +0 -221
- package/src/sdk/MemoryManager.js +0 -290
- package/src/sdk/OnairosClient.js +0 -152
- package/src/sdk/SessionManager.js +0 -257
package/onairos.d.ts
CHANGED
|
@@ -21,188 +21,6 @@ declare module 'onairos' {
|
|
|
21
21
|
*/
|
|
22
22
|
export function Onairos(props: OnairosProps): JSX.Element;
|
|
23
23
|
|
|
24
|
-
// SDK Interfaces
|
|
25
|
-
export interface OnairosClientConfig {
|
|
26
|
-
openaiApiKey?: string;
|
|
27
|
-
anthropicApiKey?: string;
|
|
28
|
-
googleApiKey?: string;
|
|
29
|
-
pineconeApiKey?: string;
|
|
30
|
-
pineconeEnvironment?: string;
|
|
31
|
-
jwtSecret: string;
|
|
32
|
-
indexName?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface CompletionMessage {
|
|
36
|
-
role: 'system' | 'user' | 'assistant';
|
|
37
|
-
content: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface CompletionOptions {
|
|
41
|
-
temperature?: number;
|
|
42
|
-
max_tokens?: number;
|
|
43
|
-
top_p?: number;
|
|
44
|
-
frequency_penalty?: number;
|
|
45
|
-
presence_penalty?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface CompletionParams {
|
|
49
|
-
model: string;
|
|
50
|
-
messages: CompletionMessage[];
|
|
51
|
-
userId: string;
|
|
52
|
-
sessionToken: string;
|
|
53
|
-
options?: CompletionOptions;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface CompletionResponse {
|
|
57
|
-
id: string;
|
|
58
|
-
object: string;
|
|
59
|
-
created: number;
|
|
60
|
-
model: string;
|
|
61
|
-
choices: Array<{
|
|
62
|
-
index: number;
|
|
63
|
-
message: {
|
|
64
|
-
role: string;
|
|
65
|
-
content: string;
|
|
66
|
-
};
|
|
67
|
-
finish_reason: string;
|
|
68
|
-
}>;
|
|
69
|
-
usage: {
|
|
70
|
-
prompt_tokens: number;
|
|
71
|
-
completion_tokens: number;
|
|
72
|
-
total_tokens: number;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface MemoryEntry {
|
|
77
|
-
id: string;
|
|
78
|
-
data: string;
|
|
79
|
-
timestamp: string;
|
|
80
|
-
query?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface SessionTokenOptions {
|
|
84
|
-
expiresIn?: string;
|
|
85
|
-
additionalClaims?: Record<string, any>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Main Onairos SDK Client for RAG-enhanced completions
|
|
90
|
-
*/
|
|
91
|
-
export class OnairosClient {
|
|
92
|
-
constructor(config: OnairosClientConfig);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Initialize the client (sets up vector store)
|
|
96
|
-
*/
|
|
97
|
-
initialize(): Promise<void>;
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create a completion with RAG enhancement
|
|
101
|
-
*/
|
|
102
|
-
completions(params: CompletionParams): Promise<CompletionResponse>;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Create a completion (alias for completions)
|
|
106
|
-
*/
|
|
107
|
-
create(params: CompletionParams): Promise<CompletionResponse>;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Generate a session token for a user
|
|
111
|
-
*/
|
|
112
|
-
generateSessionToken(userId: string, options?: SessionTokenOptions): string;
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Clear memory for a specific user
|
|
116
|
-
*/
|
|
117
|
-
clearUserMemory(userId: string): Promise<void>;
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get user memory summary
|
|
121
|
-
*/
|
|
122
|
-
getUserMemory(userId: string): Promise<MemoryEntry[]>;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* LLM Wrapper for multiple providers
|
|
127
|
-
*/
|
|
128
|
-
export class LLMWrapper {
|
|
129
|
-
constructor(config: {
|
|
130
|
-
openaiApiKey?: string;
|
|
131
|
-
anthropicApiKey?: string;
|
|
132
|
-
googleApiKey?: string;
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
createCompletion(params: {
|
|
136
|
-
model: string;
|
|
137
|
-
messages: CompletionMessage[];
|
|
138
|
-
options?: CompletionOptions;
|
|
139
|
-
}): Promise<{
|
|
140
|
-
id: string;
|
|
141
|
-
content: string;
|
|
142
|
-
finish_reason: string;
|
|
143
|
-
usage?: any;
|
|
144
|
-
}>;
|
|
145
|
-
|
|
146
|
-
getAvailableModels(): {
|
|
147
|
-
openai: string[];
|
|
148
|
-
anthropic: string[];
|
|
149
|
-
google: string[];
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Memory Manager for RAG functionality
|
|
155
|
-
*/
|
|
156
|
-
export class MemoryManager {
|
|
157
|
-
constructor(config: {
|
|
158
|
-
pineconeApiKey: string;
|
|
159
|
-
pineconeEnvironment: string;
|
|
160
|
-
indexName?: string;
|
|
161
|
-
openaiApiKey: string;
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
initialize(): Promise<void>;
|
|
165
|
-
storeInteraction(userId: string, query: string, response: string): Promise<void>;
|
|
166
|
-
retrieveMemory(userId: string, query: string): Promise<string[]>;
|
|
167
|
-
clearUserMemory(userId: string): Promise<void>;
|
|
168
|
-
getUserMemory(userId: string): Promise<MemoryEntry[]>;
|
|
169
|
-
storeCustomMemory(userId: string, memoryData: string, category?: string): Promise<void>;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Session Manager for authentication
|
|
174
|
-
*/
|
|
175
|
-
export class SessionManager {
|
|
176
|
-
constructor(config: { jwtSecret: string });
|
|
177
|
-
|
|
178
|
-
generateSessionToken(userId: string, options?: SessionTokenOptions): string;
|
|
179
|
-
validateSession(token: string): Promise<any>;
|
|
180
|
-
refreshSession(token: string, options?: any): Promise<string>;
|
|
181
|
-
extractUserId(token: string): string | null;
|
|
182
|
-
isTokenExpired(token: string): boolean;
|
|
183
|
-
getTokenExpiration(token: string): Date | null;
|
|
184
|
-
generateGuestToken(options?: any): string;
|
|
185
|
-
validateUserAccess(token: string, expectedUserId: string): Promise<any>;
|
|
186
|
-
generateApiKey(clientId: string, scopes?: string[]): string;
|
|
187
|
-
validateApiKey(apiKey: string): Promise<any>;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Utility functions
|
|
191
|
-
export function extractMemory(params: { query: string; response: string }): string | null;
|
|
192
|
-
export function hasMeaningfulMemory(query: string, response: string): boolean;
|
|
193
|
-
export function cleanMemoryData(memoryData: string): string;
|
|
194
|
-
|
|
195
|
-
export interface DataRequestResult {
|
|
196
|
-
approved: boolean | string[];
|
|
197
|
-
dataTypes?: string[];
|
|
198
|
-
timestamp: string;
|
|
199
|
-
userEmail?: string;
|
|
200
|
-
appName?: string;
|
|
201
|
-
apiResponse?: any; // Present when autoFetch is true and API call succeeds
|
|
202
|
-
apiError?: string; // Present when autoFetch is true and API call fails
|
|
203
|
-
apiUrl?: string; // The API endpoint used for the request
|
|
204
|
-
}
|
|
205
|
-
|
|
206
24
|
export interface PopupHandlerOptions {
|
|
207
25
|
autoFetch?: boolean;
|
|
208
26
|
onApiResponse?: (response: any) => void;
|
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onairos",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@anthropic-ai/sdk": "^0.24.3",
|
|
6
|
-
"@google/generative-ai": "^0.15.0",
|
|
7
|
-
"@pinecone-database/pinecone": "^2.2.2",
|
|
8
5
|
"@react-oauth/google": "^0.12.1",
|
|
9
6
|
"@telegram-apps/sdk-react": "^2.0.25",
|
|
10
7
|
"@testing-library/jest-dom": "^5.17.0",
|
|
@@ -13,12 +10,9 @@
|
|
|
13
10
|
"axios": "^1.6.5",
|
|
14
11
|
"buffer": "^6.0.3",
|
|
15
12
|
"caniuse-lite": "^1.0.30001718",
|
|
16
|
-
"jsonwebtoken": "^9.0.2",
|
|
17
|
-
"openai": "^4.52.7",
|
|
18
13
|
"process": "^0.11.10",
|
|
19
14
|
"react-scripts": "^5.0.1",
|
|
20
15
|
"stream-browserify": "^3.0.0",
|
|
21
|
-
"uuid": "^9.0.1",
|
|
22
16
|
"web-vitals": "^2.1.4"
|
|
23
17
|
},
|
|
24
18
|
"sideEffects": false,
|
|
@@ -28,10 +22,7 @@
|
|
|
28
22
|
"copy-assets": "echo 'Assets copied by webpack'",
|
|
29
23
|
"dev": "webpack --mode development --watch",
|
|
30
24
|
"test": "react-scripts test",
|
|
31
|
-
"eject": "react-scripts eject"
|
|
32
|
-
"test:sdk": "node test-sdk.js",
|
|
33
|
-
"example": "node example-usage.js",
|
|
34
|
-
"test:all": "npm run test:sdk && npm run example"
|
|
25
|
+
"eject": "react-scripts eject"
|
|
35
26
|
},
|
|
36
27
|
"eslintConfig": {
|
|
37
28
|
"extends": [
|
|
@@ -96,13 +87,9 @@
|
|
|
96
87
|
"react-dom": "^18.2.0",
|
|
97
88
|
"react-native": ">=0.69.0",
|
|
98
89
|
"tailwindcss": "^3.3.5",
|
|
99
|
-
"ajv": "^8.12.0"
|
|
100
|
-
"@anthropic-ai/sdk": "^0.24.3",
|
|
101
|
-
"@google/generative-ai": "^0.15.0",
|
|
102
|
-
"@pinecone-database/pinecone": "^2.2.2",
|
|
103
|
-
"openai": "^4.52.7"
|
|
90
|
+
"ajv": "^8.12.0"
|
|
104
91
|
},
|
|
105
|
-
"description": "The Onairos Library is a collection of functions that enable Applications to connect and communicate data with Onairos Identities via User Authorization. Integration for developers is
|
|
92
|
+
"description": "The Onairos Library is a collection of functions that enable Applications to connect and communicate data with Onairos Identities via User Authorization. Integration for developers is seamless, simple and effective for all applications. LLM SDK capabilities have been migrated to backend for enhanced security and performance.",
|
|
106
93
|
"main": "dist/onairos.bundle.js",
|
|
107
94
|
"module": "dist/onairos.esm.js",
|
|
108
95
|
"types": "onairos.d.ts",
|
|
@@ -114,7 +101,10 @@
|
|
|
114
101
|
},
|
|
115
102
|
"keywords": [
|
|
116
103
|
"Onairos",
|
|
117
|
-
"Identity"
|
|
104
|
+
"Identity",
|
|
105
|
+
"Authentication",
|
|
106
|
+
"Data-Sharing",
|
|
107
|
+
"Privacy"
|
|
118
108
|
],
|
|
119
109
|
"author": "Zion Darko",
|
|
120
110
|
"license": "Apache-2.0",
|
|
@@ -1,43 +1,66 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const dataTypes = [
|
|
4
|
+
{
|
|
5
|
+
id: 'basic',
|
|
6
|
+
name: 'Basic Information',
|
|
7
|
+
description: 'Name, email, and essential account details',
|
|
8
|
+
icon: '👤',
|
|
9
|
+
required: true // Cannot be deselected
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'memories',
|
|
13
|
+
name: 'Memories',
|
|
14
|
+
description: 'Preferences and interests',
|
|
15
|
+
icon: '🧠',
|
|
16
|
+
required: false
|
|
17
|
+
}
|
|
9
18
|
];
|
|
10
19
|
|
|
11
20
|
export default function DataRequest({
|
|
12
21
|
onComplete,
|
|
13
22
|
userEmail,
|
|
14
|
-
requestData,
|
|
15
23
|
appName = 'App',
|
|
16
24
|
autoFetch = true
|
|
17
25
|
}) {
|
|
18
|
-
const [selectedData, setSelectedData] = useState({
|
|
26
|
+
const [selectedData, setSelectedData] = useState({
|
|
27
|
+
basic: true, // Always selected by default
|
|
28
|
+
memories: false
|
|
29
|
+
});
|
|
19
30
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
20
31
|
const [isLoadingApi, setIsLoadingApi] = useState(false);
|
|
21
32
|
const [apiResponse, setApiResponse] = useState(null);
|
|
22
33
|
const [apiError, setApiError] = useState(null);
|
|
23
34
|
|
|
24
|
-
// Use provided requestData or default data types
|
|
25
|
-
const dataTypes = Array.isArray(requestData)
|
|
26
|
-
? requestData.map(id => defaultDataTypes.find(dt => dt.id === id) || { id, name: id, description: `${id} data`, icon: '📋' })
|
|
27
|
-
: Object.values(defaultDataTypes);
|
|
28
|
-
|
|
29
35
|
const handleDataToggle = (dataId) => {
|
|
36
|
+
// Don't allow toggling basic information (it's required)
|
|
37
|
+
if (dataId === 'basic') return;
|
|
38
|
+
|
|
30
39
|
setSelectedData(prev => ({
|
|
31
40
|
...prev,
|
|
32
41
|
[dataId]: !prev[dataId]
|
|
33
42
|
}));
|
|
34
43
|
};
|
|
35
44
|
|
|
45
|
+
const generateUserHash = (email) => {
|
|
46
|
+
// Simple hash function for user identification
|
|
47
|
+
let hash = 0;
|
|
48
|
+
const str = email + Date.now().toString();
|
|
49
|
+
for (let i = 0; i < str.length; i++) {
|
|
50
|
+
const char = str.charCodeAt(i);
|
|
51
|
+
hash = ((hash << 5) - hash) + char;
|
|
52
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
53
|
+
}
|
|
54
|
+
return `user_${Math.abs(hash).toString(36)}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
36
57
|
const makeApiCall = async (approvedData) => {
|
|
37
58
|
try {
|
|
38
59
|
setIsLoadingApi(true);
|
|
39
60
|
setApiError(null);
|
|
40
61
|
|
|
62
|
+
const userHash = generateUserHash(userEmail);
|
|
63
|
+
|
|
41
64
|
const response = await fetch('https://api2.onairos.uk/inferenceTest', {
|
|
42
65
|
method: 'POST',
|
|
43
66
|
headers: {
|
|
@@ -46,6 +69,7 @@ export default function DataRequest({
|
|
|
46
69
|
body: JSON.stringify({
|
|
47
70
|
approvedData,
|
|
48
71
|
userEmail,
|
|
72
|
+
userHash, // Add user hash for backend LLM SDK
|
|
49
73
|
appName,
|
|
50
74
|
timestamp: new Date().toISOString()
|
|
51
75
|
})
|
|
@@ -56,8 +80,15 @@ export default function DataRequest({
|
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
const data = await response.json();
|
|
59
|
-
|
|
60
|
-
|
|
83
|
+
|
|
84
|
+
// Ensure user hash is included in response
|
|
85
|
+
const responseWithHash = {
|
|
86
|
+
...data,
|
|
87
|
+
userHash
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
setApiResponse(responseWithHash);
|
|
91
|
+
return responseWithHash;
|
|
61
92
|
} catch (error) {
|
|
62
93
|
console.error('API call error:', error);
|
|
63
94
|
setApiError(error.message);
|
|
@@ -77,17 +108,14 @@ export default function DataRequest({
|
|
|
77
108
|
.filter(([_, isSelected]) => isSelected)
|
|
78
109
|
.map(([dataId]) => dataId);
|
|
79
110
|
|
|
80
|
-
|
|
81
|
-
alert('Please select at least one data type to continue.');
|
|
82
|
-
setIsSubmitting(false);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
111
|
+
const userHash = generateUserHash(userEmail);
|
|
85
112
|
|
|
86
113
|
const baseResult = {
|
|
87
114
|
approved: true,
|
|
88
115
|
dataTypes: approved,
|
|
89
116
|
timestamp: new Date().toISOString(),
|
|
90
117
|
userEmail: userEmail,
|
|
118
|
+
userHash: userHash, // Include user hash in response
|
|
91
119
|
appName: appName
|
|
92
120
|
};
|
|
93
121
|
|
|
@@ -129,6 +157,7 @@ export default function DataRequest({
|
|
|
129
157
|
dataTypes: [],
|
|
130
158
|
timestamp: new Date().toISOString(),
|
|
131
159
|
userEmail: userEmail,
|
|
160
|
+
userHash: generateUserHash(userEmail),
|
|
132
161
|
appName: appName
|
|
133
162
|
});
|
|
134
163
|
};
|
|
@@ -136,127 +165,109 @@ export default function DataRequest({
|
|
|
136
165
|
const selectedCount = Object.values(selectedData).filter(Boolean).length;
|
|
137
166
|
|
|
138
167
|
return (
|
|
139
|
-
<div className="
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
<div className="
|
|
143
|
-
<
|
|
144
|
-
<
|
|
145
|
-
|
|
168
|
+
<div className="w-full max-w-md mx-auto bg-white rounded-lg shadow-xl overflow-hidden" style={{ maxHeight: '90vh', height: 'auto' }}>
|
|
169
|
+
<div className="p-4 sm:p-6 overflow-y-auto" style={{ maxHeight: 'calc(90vh - 4rem)' }}>
|
|
170
|
+
{/* Header */}
|
|
171
|
+
<div className="text-center mb-4 sm:mb-6">
|
|
172
|
+
<div className="w-12 h-12 sm:w-16 sm:h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-3 sm:mb-4">
|
|
173
|
+
<svg className="w-6 h-6 sm:w-8 sm:h-8 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
174
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
175
|
+
</svg>
|
|
176
|
+
</div>
|
|
177
|
+
<h2 className="text-lg sm:text-xl font-semibold text-gray-900 mb-2">Data Access Request</h2>
|
|
178
|
+
<p className="text-gray-600 text-xs sm:text-sm">
|
|
179
|
+
<span className="font-medium">{appName}</span> would like to access some of your data.
|
|
180
|
+
</p>
|
|
146
181
|
</div>
|
|
147
|
-
<h2 className="text-xl font-semibold text-gray-900 mb-2">Data Access Request</h2>
|
|
148
|
-
<p className="text-gray-600 text-sm">
|
|
149
|
-
<span className="font-medium">{appName}</span> would like to access some of your data.
|
|
150
|
-
{autoFetch && " Your data will be processed automatically after approval."}
|
|
151
|
-
</p>
|
|
152
|
-
</div>
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<div className="text-2xl">
|
|
166
|
-
{dataType.icon}
|
|
167
|
-
</div>
|
|
168
|
-
<div>
|
|
169
|
-
<h3 className="font-medium text-gray-900">{dataType.name}</h3>
|
|
170
|
-
<p className="text-sm text-gray-500">{dataType.description}</p>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
{/* Toggle Switch */}
|
|
175
|
-
<button
|
|
176
|
-
onClick={() => handleDataToggle(dataType.id)}
|
|
177
|
-
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
|
|
178
|
-
isSelected ? 'bg-blue-600' : 'bg-gray-200'
|
|
183
|
+
{/* Data Types Selection */}
|
|
184
|
+
<div className="space-y-2 sm:space-y-3 mb-4 sm:mb-6">
|
|
185
|
+
{dataTypes.map((dataType) => {
|
|
186
|
+
const isSelected = selectedData[dataType.id] || false;
|
|
187
|
+
const isRequired = dataType.required;
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div
|
|
191
|
+
key={dataType.id}
|
|
192
|
+
className={`flex items-center justify-between p-3 sm:p-4 border rounded-lg transition-colors ${
|
|
193
|
+
isRequired ? 'bg-blue-50 border-blue-200' : 'hover:bg-gray-50'
|
|
179
194
|
}`}
|
|
180
195
|
>
|
|
181
|
-
<
|
|
182
|
-
className=
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
196
|
+
<div className="flex items-center space-x-3">
|
|
197
|
+
<div className="text-xl sm:text-2xl">
|
|
198
|
+
{dataType.icon}
|
|
199
|
+
</div>
|
|
200
|
+
<div>
|
|
201
|
+
<h3 className="font-medium text-gray-900 text-sm sm:text-base">
|
|
202
|
+
{dataType.name}
|
|
203
|
+
{isRequired && <span className="text-blue-600 ml-1">*</span>}
|
|
204
|
+
</h3>
|
|
205
|
+
<p className="text-xs sm:text-sm text-gray-500">{dataType.description}</p>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
{/* Toggle Switch or Required Badge */}
|
|
210
|
+
{isRequired ? (
|
|
211
|
+
<div className="px-2 py-1 bg-blue-600 text-white text-xs rounded-full">
|
|
212
|
+
Required
|
|
213
|
+
</div>
|
|
214
|
+
) : (
|
|
215
|
+
<button
|
|
216
|
+
onClick={() => handleDataToggle(dataType.id)}
|
|
217
|
+
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 ${
|
|
218
|
+
isSelected ? 'bg-blue-600' : 'bg-gray-200'
|
|
219
|
+
}`}
|
|
220
|
+
>
|
|
221
|
+
<span
|
|
222
|
+
className={`inline-block h-3 sm:h-4 w-3 sm:w-4 transform rounded-full bg-white transition-transform ${
|
|
223
|
+
isSelected ? 'translate-x-5 sm:translate-x-6' : 'translate-x-1'
|
|
224
|
+
}`}
|
|
225
|
+
/>
|
|
226
|
+
</button>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
);
|
|
230
|
+
})}
|
|
231
|
+
</div>
|
|
191
232
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<p className="text-green-800 text-sm">
|
|
233
|
+
{/* Selection Summary */}
|
|
234
|
+
<div className="mb-3 sm:mb-4 p-2 sm:p-3 bg-green-50 border border-green-200 rounded-lg">
|
|
235
|
+
<p className="text-green-800 text-xs sm:text-sm">
|
|
196
236
|
✅ {selectedCount} data type{selectedCount > 1 ? 's' : ''} selected for sharing
|
|
197
237
|
</p>
|
|
198
238
|
</div>
|
|
199
|
-
)}
|
|
200
239
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<div className="animate-spin h-4 w-4 border-2 border-blue-600 rounded-full border-t-transparent"></div>
|
|
206
|
-
<p className="text-blue-800 text-sm">Processing your data...</p>
|
|
240
|
+
{/* API Status */}
|
|
241
|
+
{isLoadingApi && (
|
|
242
|
+
<div className="mb-3 sm:mb-4 p-2 sm:p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
|
243
|
+
<p className="text-blue-800 text-xs sm:text-sm">🔄 Processing your data request...</p>
|
|
207
244
|
</div>
|
|
208
|
-
|
|
209
|
-
)}
|
|
245
|
+
)}
|
|
210
246
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
</div>
|
|
217
|
-
)}
|
|
247
|
+
{apiError && (
|
|
248
|
+
<div className="mb-3 sm:mb-4 p-2 sm:p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
249
|
+
<p className="text-red-800 text-xs sm:text-sm">❌ {apiError}</p>
|
|
250
|
+
</div>
|
|
251
|
+
)}
|
|
218
252
|
|
|
219
|
-
|
|
220
|
-
<div className="
|
|
221
|
-
<
|
|
222
|
-
|
|
223
|
-
|
|
253
|
+
{/* Action Buttons */}
|
|
254
|
+
<div className="flex space-x-3">
|
|
255
|
+
<button
|
|
256
|
+
onClick={handleReject}
|
|
257
|
+
disabled={isSubmitting}
|
|
258
|
+
className="flex-1 py-2 sm:py-3 px-4 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 text-sm sm:text-base"
|
|
259
|
+
>
|
|
260
|
+
Deny
|
|
261
|
+
</button>
|
|
262
|
+
<button
|
|
263
|
+
onClick={handleApprove}
|
|
264
|
+
disabled={isSubmitting}
|
|
265
|
+
className="flex-1 py-2 sm:py-3 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 text-sm sm:text-base"
|
|
266
|
+
>
|
|
267
|
+
{isSubmitting ? 'Processing...' : 'Approve'}
|
|
268
|
+
</button>
|
|
224
269
|
</div>
|
|
225
|
-
)}
|
|
226
|
-
|
|
227
|
-
{/* Action Buttons */}
|
|
228
|
-
<div className="flex space-x-3">
|
|
229
|
-
<button
|
|
230
|
-
onClick={handleReject}
|
|
231
|
-
disabled={isSubmitting || isLoadingApi}
|
|
232
|
-
className="flex-1 px-4 py-2 text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
233
|
-
>
|
|
234
|
-
Deny
|
|
235
|
-
</button>
|
|
236
|
-
<button
|
|
237
|
-
onClick={handleApprove}
|
|
238
|
-
disabled={isSubmitting || isLoadingApi || selectedCount === 0}
|
|
239
|
-
className="flex-1 px-4 py-2 text-white bg-blue-600 hover:bg-blue-700 rounded-lg font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
|
|
240
|
-
>
|
|
241
|
-
{isSubmitting || isLoadingApi ? (
|
|
242
|
-
<>
|
|
243
|
-
<div className="animate-spin h-4 w-4 border-2 border-white rounded-full border-t-transparent mr-2"></div>
|
|
244
|
-
{autoFetch ? 'Processing...' : 'Approving...'}
|
|
245
|
-
</>
|
|
246
|
-
) : (
|
|
247
|
-
autoFetch ? 'Approve & Process' : 'Approve'
|
|
248
|
-
)}
|
|
249
|
-
</button>
|
|
250
270
|
</div>
|
|
251
|
-
|
|
252
|
-
{/* Auto-fetch info */}
|
|
253
|
-
{autoFetch && (
|
|
254
|
-
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
|
|
255
|
-
<p className="text-blue-800 text-xs">
|
|
256
|
-
🔄 Auto-fetch enabled: Your approved data will be automatically processed and sent to {appName}.
|
|
257
|
-
</p>
|
|
258
|
-
</div>
|
|
259
|
-
)}
|
|
260
271
|
</div>
|
|
261
272
|
);
|
|
262
273
|
}
|