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.
@@ -1,32 +1,48 @@
1
1
  import React, { useState, useEffect } from 'react';
2
+ import YoutubeConnector from './connectors/YoutubeConnector';
3
+ import LinkedInConnector from './connectors/LinkedInConnector';
4
+ import InstagramConnector from './connectors/InstagramConnector';
5
+ import PinterestConnector from './connectors/PinterestConnector';
6
+ import RedditConnector from './connectors/RedditConnector';
7
+ import GmailConnector from './connectors/GmailConnector';
2
8
 
3
9
  const platforms = [
4
- { name: 'YouTube', icon: '📺', color: 'bg-red-500' },
5
- { name: 'Reddit', icon: '🔥', color: 'bg-orange-500' },
6
- { name: 'Instagram', icon: '📷', color: 'bg-pink-500' },
7
- { name: 'Pinterest', icon: '📌', color: 'bg-red-600' },
8
- { name: 'TikTok', icon: '🎵', color: 'bg-black' },
9
- { name: 'Twitter', icon: '🐦', color: 'bg-blue-500' },
10
- { name: 'LinkedIn', icon: '💼', color: 'bg-blue-700' },
11
- { name: 'Facebook', icon: '👥', color: 'bg-blue-600' }
10
+ { name: 'YouTube', icon: '📺', color: 'bg-red-500', connector: 'youtube' },
11
+ { name: 'Reddit', icon: '🔥', color: 'bg-orange-500', connector: 'reddit' },
12
+ { name: 'Instagram', icon: '📷', color: 'bg-pink-500', connector: 'instagram' },
13
+ { name: 'Pinterest', icon: '📌', color: 'bg-red-600', connector: 'pinterest' },
14
+ { name: 'LinkedIn', icon: '💼', color: 'bg-blue-700', connector: 'linkedin' },
15
+ { name: 'Gmail', icon: '📧', color: 'bg-red-500', connector: 'gmail' }
12
16
  ];
13
17
 
14
- export default function UniversalOnboarding({ onComplete, appIcon, appName = 'App' }) {
18
+ export default function UniversalOnboarding({ onComplete, appIcon, appName = 'App', username }) {
15
19
  const [connectedAccounts, setConnectedAccounts] = useState({});
16
20
  const [isConnecting, setIsConnecting] = useState(false);
21
+ const [activeConnector, setActiveConnector] = useState(null);
17
22
 
18
- const handleToggle = async (platformName) => {
19
- if (isConnecting) return;
20
-
21
- setIsConnecting(true);
22
- await new Promise(resolve => setTimeout(resolve, 1000));
23
-
23
+ const handleConnectionChange = (platformName, isConnected) => {
24
24
  setConnectedAccounts(prev => ({
25
25
  ...prev,
26
- [platformName]: !prev[platformName]
26
+ [platformName]: isConnected
27
27
  }));
28
+ setActiveConnector(null);
29
+ };
30
+
31
+ const handleToggle = async (platformName, connectorType) => {
32
+ if (isConnecting) return;
28
33
 
29
- setIsConnecting(false);
34
+ const isCurrentlyConnected = connectedAccounts[platformName];
35
+
36
+ if (isCurrentlyConnected) {
37
+ // Disconnect - call the connector's disconnect method
38
+ setConnectedAccounts(prev => ({
39
+ ...prev,
40
+ [platformName]: false
41
+ }));
42
+ } else {
43
+ // Connect - open the OAuth dialog
44
+ setActiveConnector(connectorType);
45
+ }
30
46
  };
31
47
 
32
48
  const handleContinue = () => {
@@ -104,7 +120,7 @@ export default function UniversalOnboarding({ onComplete, appIcon, appName = 'Ap
104
120
 
105
121
  {/* Toggle Switch */}
106
122
  <button
107
- onClick={() => handleToggle(platform.name)}
123
+ onClick={() => handleToggle(platform.name, platform.connector)}
108
124
  disabled={isConnecting}
109
125
  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 ${
110
126
  isConnected ? 'bg-blue-600' : 'bg-gray-200'
@@ -151,6 +167,44 @@ export default function UniversalOnboarding({ onComplete, appIcon, appName = 'Ap
151
167
  Skip for now
152
168
  </button>
153
169
  </div>
170
+
171
+ {/* OAuth Connector Dialogs */}
172
+ <YoutubeConnector
173
+ open={activeConnector === 'youtube'}
174
+ onClose={() => setActiveConnector(null)}
175
+ onConnectionChange={handleConnectionChange}
176
+ username={username}
177
+ />
178
+ <LinkedInConnector
179
+ open={activeConnector === 'linkedin'}
180
+ onClose={() => setActiveConnector(null)}
181
+ onConnectionChange={handleConnectionChange}
182
+ username={username}
183
+ />
184
+ <InstagramConnector
185
+ open={activeConnector === 'instagram'}
186
+ onClose={() => setActiveConnector(null)}
187
+ onConnectionChange={handleConnectionChange}
188
+ username={username}
189
+ />
190
+ <PinterestConnector
191
+ open={activeConnector === 'pinterest'}
192
+ onClose={() => setActiveConnector(null)}
193
+ onConnectionChange={handleConnectionChange}
194
+ username={username}
195
+ />
196
+ <RedditConnector
197
+ open={activeConnector === 'reddit'}
198
+ onClose={() => setActiveConnector(null)}
199
+ onConnectionChange={handleConnectionChange}
200
+ username={username}
201
+ />
202
+ <GmailConnector
203
+ open={activeConnector === 'gmail'}
204
+ onClose={() => setActiveConnector(null)}
205
+ onConnectionChange={handleConnectionChange}
206
+ username={username}
207
+ />
154
208
  </div>
155
209
  );
156
210
  }
@@ -0,0 +1,177 @@
1
+ import React, { Component } from 'react';
2
+
3
+ class GmailConnector extends Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = {
7
+ connected: false,
8
+ open: false,
9
+ isConnecting: false,
10
+ };
11
+ this.handleClose = this.handleClose.bind(this);
12
+ this.handleOpen = this.handleOpen.bind(this);
13
+ this.gmailConnect = this.gmailConnect.bind(this);
14
+ this.setConnected = this.setConnected.bind(this);
15
+ this.setDisconnected = this.setDisconnected.bind(this);
16
+ }
17
+
18
+ setConnected() {
19
+ this.setState({ connected: true });
20
+ if (this.props.onConnectionChange) {
21
+ this.props.onConnectionChange('Gmail', true);
22
+ }
23
+ this.handleClose();
24
+ }
25
+
26
+ setDisconnected() {
27
+ this.updateConnections('Remove', 'Gmail').then(() => {
28
+ this.setState({ connected: false });
29
+ if (this.props.onConnectionChange) {
30
+ this.props.onConnectionChange('Gmail', false);
31
+ }
32
+ this.handleClose();
33
+ }).catch((error) => {
34
+ console.error('Error removing Gmail connection:', error);
35
+ });
36
+ }
37
+
38
+ async updateConnections(updateType, newConnection) {
39
+ const jsonData = {
40
+ session: {
41
+ username: localStorage.getItem("username") || this.props.username
42
+ },
43
+ updateType,
44
+ newConnection
45
+ };
46
+
47
+ try {
48
+ const response = await fetch('https://api2.onairos.uk/connections/update', {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify(jsonData),
54
+ });
55
+ return await response.json();
56
+ } catch (error) {
57
+ console.error('UpdateConnections error:', error);
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ handleOpen() {
63
+ this.setState({ open: true });
64
+ }
65
+
66
+ handleClose() {
67
+ this.setState({ open: false });
68
+ if (this.props.onClose) {
69
+ this.props.onClose();
70
+ }
71
+ }
72
+
73
+ async gmailConnect() {
74
+ this.setState({ isConnecting: true });
75
+
76
+ const jsonData = {
77
+ session: {
78
+ username: localStorage.getItem("username") || this.props.username
79
+ },
80
+ };
81
+
82
+ try {
83
+ const response = await fetch('https://api2.onairos.uk/gmail/authorize', {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: JSON.stringify(jsonData),
89
+ });
90
+
91
+ const result = await response.json();
92
+
93
+ if (result.gmailURL) {
94
+ window.location.href = result.gmailURL;
95
+ } else {
96
+ console.error('No Gmail URL received');
97
+ this.setState({ isConnecting: false });
98
+ }
99
+ } catch (error) {
100
+ console.error('Gmail connection error:', error);
101
+ this.setState({ isConnecting: false });
102
+ }
103
+ }
104
+
105
+ render() {
106
+ const { open = this.props.open || this.state.open } = this.props;
107
+
108
+ if (!open) return null;
109
+
110
+ return (
111
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
112
+ <div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-hidden">
113
+ <div className="p-6">
114
+ <h2 className="text-xl font-bold text-gray-900 mb-4">
115
+ Grant Onairos Access to your Gmail Account?
116
+ </h2>
117
+
118
+ <div className="space-y-4 text-gray-700">
119
+ <p>
120
+ Grant Permission to your Gmail Account, so we can build your Data Models.
121
+ </p>
122
+
123
+ <div>
124
+ <p className="font-medium mb-2">We will access your Gmail:</p>
125
+ <ul className="list-disc ml-6 space-y-1">
126
+ <li>Basic Account Information</li>
127
+ <li>Email Metadata (subjects, dates, senders)</li>
128
+ <li>Email Categories and Labels</li>
129
+ <li>Communication Patterns</li>
130
+ </ul>
131
+ </div>
132
+
133
+ <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
134
+ <p className="text-yellow-800 text-sm font-medium">
135
+ <strong>Note:</strong> We do NOT read the content of your emails. Only metadata is processed.
136
+ </p>
137
+ </div>
138
+
139
+ <p>
140
+ We will delete all the data used once your Model is Created
141
+ </p>
142
+
143
+ <p className="text-sm">
144
+ <a href="https://onairos.uk/compliance-google-policy" className="text-blue-600 hover:underline">
145
+ Onairos
146
+ </a>{' '}
147
+ complies with{' '}
148
+ <a href="https://policies.google.com/privacy" className="text-blue-600 hover:underline">
149
+ Google API Services User Data Policy
150
+ </a>
151
+ </p>
152
+ </div>
153
+
154
+ <div className="flex space-x-3 mt-6">
155
+ <button
156
+ onClick={this.handleClose}
157
+ disabled={this.state.isConnecting}
158
+ className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 disabled:opacity-50"
159
+ >
160
+ Disagree
161
+ </button>
162
+ <button
163
+ onClick={this.gmailConnect}
164
+ disabled={this.state.isConnecting}
165
+ className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
166
+ >
167
+ {this.state.isConnecting ? 'Connecting...' : 'Agree'}
168
+ </button>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ }
175
+ }
176
+
177
+ export default GmailConnector;
@@ -0,0 +1,171 @@
1
+ import React, { Component } from 'react';
2
+
3
+ class InstagramConnector extends Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = {
7
+ connected: false,
8
+ open: false,
9
+ isConnecting: false,
10
+ };
11
+ this.handleClose = this.handleClose.bind(this);
12
+ this.handleOpen = this.handleOpen.bind(this);
13
+ this.instagramConnect = this.instagramConnect.bind(this);
14
+ this.setConnected = this.setConnected.bind(this);
15
+ this.setDisconnected = this.setDisconnected.bind(this);
16
+ }
17
+
18
+ setConnected() {
19
+ this.setState({ connected: true });
20
+ if (this.props.onConnectionChange) {
21
+ this.props.onConnectionChange('Instagram', true);
22
+ }
23
+ this.handleClose();
24
+ }
25
+
26
+ setDisconnected() {
27
+ this.updateConnections('Remove', 'Instagram').then(() => {
28
+ this.setState({ connected: false });
29
+ if (this.props.onConnectionChange) {
30
+ this.props.onConnectionChange('Instagram', false);
31
+ }
32
+ this.handleClose();
33
+ }).catch((error) => {
34
+ console.error('Error removing Instagram connection:', error);
35
+ });
36
+ }
37
+
38
+ async updateConnections(updateType, newConnection) {
39
+ const jsonData = {
40
+ session: {
41
+ username: localStorage.getItem("username") || this.props.username
42
+ },
43
+ updateType,
44
+ newConnection
45
+ };
46
+
47
+ try {
48
+ const response = await fetch('https://api2.onairos.uk/connections/update', {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify(jsonData),
54
+ });
55
+ return await response.json();
56
+ } catch (error) {
57
+ console.error('UpdateConnections error:', error);
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ handleOpen() {
63
+ this.setState({ open: true });
64
+ }
65
+
66
+ handleClose() {
67
+ this.setState({ open: false });
68
+ if (this.props.onClose) {
69
+ this.props.onClose();
70
+ }
71
+ }
72
+
73
+ async instagramConnect() {
74
+ this.setState({ isConnecting: true });
75
+
76
+ const jsonData = {
77
+ session: {
78
+ username: localStorage.getItem("username") || this.props.username
79
+ },
80
+ };
81
+
82
+ try {
83
+ const response = await fetch('https://api2.onairos.uk/instagram/authorize', {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: JSON.stringify(jsonData),
89
+ });
90
+
91
+ const result = await response.json();
92
+
93
+ if (result.instagramURL) {
94
+ window.location.href = result.instagramURL;
95
+ } else {
96
+ console.error('No Instagram URL received');
97
+ this.setState({ isConnecting: false });
98
+ }
99
+ } catch (error) {
100
+ console.error('Instagram connection error:', error);
101
+ this.setState({ isConnecting: false });
102
+ }
103
+ }
104
+
105
+ render() {
106
+ const { open = this.props.open || this.state.open } = this.props;
107
+
108
+ if (!open) return null;
109
+
110
+ return (
111
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
112
+ <div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-hidden">
113
+ <div className="p-6">
114
+ <h2 className="text-xl font-bold text-gray-900 mb-4">
115
+ Grant Onairos Access to your Instagram Account?
116
+ </h2>
117
+
118
+ <div className="space-y-4 text-gray-700">
119
+ <p>
120
+ Grant Permission to your Instagram Account, so we can build your Data Models.
121
+ </p>
122
+
123
+ <div>
124
+ <p className="font-medium mb-2">We will access your Instagram:</p>
125
+ <ul className="list-disc ml-6 space-y-1">
126
+ <li>Basic Profile Information</li>
127
+ <li>Posts and Stories</li>
128
+ <li>Liked Content</li>
129
+ <li>Following and Followers</li>
130
+ </ul>
131
+ </div>
132
+
133
+ <p>
134
+ We will delete all the data used once your Model is Created
135
+ </p>
136
+
137
+ <p className="text-sm">
138
+ <a href="https://onairos.uk/compliance-meta-policy" className="text-blue-600 hover:underline">
139
+ Onairos
140
+ </a>{' '}
141
+ complies with{' '}
142
+ <a href="https://developers.facebook.com/policy" className="text-blue-600 hover:underline">
143
+ Meta Platform Policy
144
+ </a>
145
+ </p>
146
+ </div>
147
+
148
+ <div className="flex space-x-3 mt-6">
149
+ <button
150
+ onClick={this.handleClose}
151
+ disabled={this.state.isConnecting}
152
+ className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 disabled:opacity-50"
153
+ >
154
+ Disagree
155
+ </button>
156
+ <button
157
+ onClick={this.instagramConnect}
158
+ disabled={this.state.isConnecting}
159
+ className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
160
+ >
161
+ {this.state.isConnecting ? 'Connecting...' : 'Agree'}
162
+ </button>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ );
168
+ }
169
+ }
170
+
171
+ export default InstagramConnector;
@@ -0,0 +1,168 @@
1
+ import React, { Component } from 'react';
2
+
3
+ class LinkedInConnector extends Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = {
7
+ connected: false,
8
+ open: false,
9
+ isConnecting: false,
10
+ };
11
+ this.handleClose = this.handleClose.bind(this);
12
+ this.handleOpen = this.handleOpen.bind(this);
13
+ this.linkedinConnect = this.linkedinConnect.bind(this);
14
+ this.setConnected = this.setConnected.bind(this);
15
+ this.setDisconnected = this.setDisconnected.bind(this);
16
+ }
17
+
18
+ setConnected() {
19
+ this.setState({ connected: true });
20
+ if (this.props.onConnectionChange) {
21
+ this.props.onConnectionChange('LinkedIn', true);
22
+ }
23
+ this.handleClose();
24
+ }
25
+
26
+ setDisconnected() {
27
+ this.updateConnections('Remove', 'LinkedIn').then(() => {
28
+ this.setState({ connected: false });
29
+ if (this.props.onConnectionChange) {
30
+ this.props.onConnectionChange('LinkedIn', false);
31
+ }
32
+ this.handleClose();
33
+ }).catch((error) => {
34
+ console.error('Error removing LinkedIn connection:', error);
35
+ });
36
+ }
37
+
38
+ async updateConnections(updateType, newConnection) {
39
+ const jsonData = {
40
+ session: {
41
+ username: localStorage.getItem("username") || this.props.username
42
+ },
43
+ updateType,
44
+ newConnection
45
+ };
46
+
47
+ try {
48
+ const response = await fetch('https://api2.onairos.uk/connections/update', {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify(jsonData),
54
+ });
55
+ return await response.json();
56
+ } catch (error) {
57
+ console.error('UpdateConnections error:', error);
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ handleOpen() {
63
+ this.setState({ open: true });
64
+ }
65
+
66
+ handleClose() {
67
+ this.setState({ open: false });
68
+ if (this.props.onClose) {
69
+ this.props.onClose();
70
+ }
71
+ }
72
+
73
+ async linkedinConnect() {
74
+ this.setState({ isConnecting: true });
75
+
76
+ const jsonData = {
77
+ session: {
78
+ username: localStorage.getItem("username") || this.props.username
79
+ },
80
+ };
81
+
82
+ try {
83
+ const response = await fetch('https://api2.onairos.uk/linkedin/authorize', {
84
+ method: 'POST',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ body: JSON.stringify(jsonData),
89
+ });
90
+
91
+ const result = await response.json();
92
+
93
+ if (result.linkedinURL) {
94
+ window.location.href = result.linkedinURL;
95
+ } else {
96
+ console.error('No LinkedIn URL received');
97
+ this.setState({ isConnecting: false });
98
+ }
99
+ } catch (error) {
100
+ console.error('LinkedIn connection error:', error);
101
+ this.setState({ isConnecting: false });
102
+ }
103
+ }
104
+
105
+ render() {
106
+ const { open = this.props.open || this.state.open } = this.props;
107
+
108
+ if (!open) return null;
109
+
110
+ return (
111
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
112
+ <div className="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-hidden">
113
+ <div className="p-6">
114
+ <h2 className="text-xl font-bold text-gray-900 mb-4">
115
+ Grant Onairos Access to your LinkedIn Account?
116
+ </h2>
117
+
118
+ <div className="space-y-4 text-gray-700">
119
+ <p>
120
+ Grant Permission to your LinkedIn Account, so we can build your Data Models.
121
+ </p>
122
+
123
+ <div>
124
+ <p className="font-medium mb-2">We will access your LinkedIn:</p>
125
+ <ul className="list-disc ml-6 space-y-1">
126
+ <li>Basic Profile Information</li>
127
+ <li>Professional Experience</li>
128
+ <li>Network Connections</li>
129
+ <li>Posts and Activity</li>
130
+ </ul>
131
+ </div>
132
+
133
+ <p>
134
+ We will delete all the data used once your Model is Created
135
+ </p>
136
+
137
+ <p className="text-sm">
138
+ <a href="https://onairos.uk/privacy-policy" className="text-blue-600 hover:underline">
139
+ Onairos Privacy Policy
140
+ </a>{' '}
141
+ - Your professional data is handled with the highest security standards.
142
+ </p>
143
+ </div>
144
+
145
+ <div className="flex space-x-3 mt-6">
146
+ <button
147
+ onClick={this.handleClose}
148
+ disabled={this.state.isConnecting}
149
+ className="flex-1 px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 disabled:opacity-50"
150
+ >
151
+ Disagree
152
+ </button>
153
+ <button
154
+ onClick={this.linkedinConnect}
155
+ disabled={this.state.isConnecting}
156
+ className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
157
+ >
158
+ {this.state.isConnecting ? 'Connecting...' : 'Agree'}
159
+ </button>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ );
165
+ }
166
+ }
167
+
168
+ export default LinkedInConnector;