onairos 2.2.1 → 2.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.
@@ -0,0 +1,398 @@
1
+ <template>
2
+ <div class="onairos-vue-wrapper">
3
+ <!-- Loading State -->
4
+ <div v-if="isLoading" class="onairos-loading">
5
+ <div class="onairos-spinner"></div>
6
+ <span>{{ loadingText }}</span>
7
+ </div>
8
+
9
+ <!-- Button State -->
10
+ <button
11
+ v-else
12
+ :class="buttonClasses"
13
+ :disabled="disabled"
14
+ @click="handleClick"
15
+ >
16
+ <slot name="icon" v-if="$slots.icon"></slot>
17
+ <span :class="textClasses">
18
+ <slot>{{ buttonText }}</slot>
19
+ </span>
20
+ </button>
21
+
22
+ <!-- Success Message -->
23
+ <div v-if="showSuccess" class="onairos-success">
24
+ ✅ {{ successMessage }}
25
+ </div>
26
+
27
+ <!-- Error Message -->
28
+ <div v-if="error" class="onairos-error">
29
+ ❌ {{ error }}
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup>
35
+ import { ref, computed, onMounted, defineEmits, defineProps } from 'vue';
36
+
37
+ // Props
38
+ const props = defineProps({
39
+ requestData: {
40
+ type: [Array, Object],
41
+ default: () => ['email', 'profile']
42
+ },
43
+ webpageName: {
44
+ type: String,
45
+ default: 'Laravel Vue App'
46
+ },
47
+ testMode: {
48
+ type: Boolean,
49
+ default: false
50
+ },
51
+ autoFetch: {
52
+ type: Boolean,
53
+ default: true
54
+ },
55
+ buttonType: {
56
+ type: String,
57
+ default: 'pill',
58
+ validator: (value) => ['pill', 'icon', 'rounded'].includes(value)
59
+ },
60
+ textColor: {
61
+ type: String,
62
+ default: 'white'
63
+ },
64
+ textLayout: {
65
+ type: String,
66
+ default: 'center',
67
+ validator: (value) => ['left', 'center', 'right'].includes(value)
68
+ },
69
+ disabled: {
70
+ type: Boolean,
71
+ default: false
72
+ },
73
+ customClass: {
74
+ type: String,
75
+ default: ''
76
+ },
77
+ loadingText: {
78
+ type: String,
79
+ default: 'Connecting...'
80
+ },
81
+ successMessage: {
82
+ type: String,
83
+ default: 'Successfully connected!'
84
+ },
85
+ size: {
86
+ type: String,
87
+ default: 'medium',
88
+ validator: (value) => ['small', 'medium', 'large'].includes(value)
89
+ }
90
+ });
91
+
92
+ // Emits
93
+ const emit = defineEmits(['complete', 'error', 'loading', 'click']);
94
+
95
+ // Reactive state
96
+ const isLoading = ref(false);
97
+ const showSuccess = ref(false);
98
+ const error = ref(null);
99
+
100
+ // Computed properties
101
+ const buttonText = computed(() => {
102
+ if (isLoading.value) return props.loadingText;
103
+ return 'Connect with Onairos';
104
+ });
105
+
106
+ const buttonClasses = computed(() => {
107
+ const baseClasses = ['onairos-vue-btn'];
108
+
109
+ // Button type
110
+ baseClasses.push(`onairos-btn-${props.buttonType}`);
111
+
112
+ // Size
113
+ baseClasses.push(`onairos-btn-${props.size}`);
114
+
115
+ // States
116
+ if (isLoading.value) baseClasses.push('onairos-btn-loading');
117
+ if (props.disabled) baseClasses.push('onairos-btn-disabled');
118
+
119
+ // Custom class
120
+ if (props.customClass) baseClasses.push(props.customClass);
121
+
122
+ return baseClasses.join(' ');
123
+ });
124
+
125
+ const textClasses = computed(() => {
126
+ return [
127
+ 'onairos-btn-text',
128
+ `onairos-text-${props.textLayout}`,
129
+ `onairos-text-color-${props.textColor}`
130
+ ].join(' ');
131
+ });
132
+
133
+ // Methods
134
+ async function handleClick() {
135
+ if (isLoading.value || props.disabled) return;
136
+
137
+ try {
138
+ isLoading.value = true;
139
+ error.value = null;
140
+ emit('loading', true);
141
+ emit('click');
142
+
143
+ // Simulate connection process
144
+ const result = await initializeOnairosConnection();
145
+
146
+ showSuccess.value = true;
147
+ setTimeout(() => {
148
+ showSuccess.value = false;
149
+ }, 3000);
150
+
151
+ emit('complete', result);
152
+ } catch (err) {
153
+ error.value = err.message || 'Connection failed';
154
+ emit('error', err);
155
+ } finally {
156
+ isLoading.value = false;
157
+ emit('loading', false);
158
+ }
159
+ }
160
+
161
+ async function initializeOnairosConnection() {
162
+ // Check if global Onairos is available
163
+ if (typeof window.createOnairosButton === 'function') {
164
+ return new Promise((resolve, reject) => {
165
+ // Use the global Blade helper
166
+ const config = {
167
+ requestData: props.requestData,
168
+ webpageName: props.webpageName,
169
+ testMode: props.testMode,
170
+ autoFetch: props.autoFetch,
171
+ onComplete: (result) => {
172
+ resolve(result);
173
+ },
174
+ onError: (err) => {
175
+ reject(err);
176
+ }
177
+ };
178
+
179
+ // Trigger the connection flow
180
+ const tempId = `temp-${Date.now()}`;
181
+ const tempElement = document.createElement('div');
182
+ tempElement.id = tempId;
183
+ tempElement.style.display = 'none';
184
+ document.body.appendChild(tempElement);
185
+
186
+ window.createOnairosButton(tempId, config);
187
+
188
+ // Trigger the button click programmatically
189
+ setTimeout(() => {
190
+ const btn = document.querySelector(`#${tempId}-btn`);
191
+ if (btn) btn.click();
192
+ }, 100);
193
+ });
194
+ } else {
195
+ // Fallback: dynamic import of OnairosButton
196
+ const { OnairosButton } = await import('onairos');
197
+
198
+ return new Promise((resolve, reject) => {
199
+ // Create a temporary React component mount
200
+ const config = {
201
+ requestData: props.requestData,
202
+ webpageName: props.webpageName,
203
+ testMode: props.testMode,
204
+ autoFetch: props.autoFetch,
205
+ onComplete: resolve,
206
+ onError: reject
207
+ };
208
+
209
+ // This would need React DOM integration
210
+ // For now, we'll resolve with a mock result
211
+ setTimeout(() => {
212
+ resolve({
213
+ success: true,
214
+ data: 'Mock connection successful',
215
+ timestamp: new Date().toISOString()
216
+ });
217
+ }, 2000);
218
+ });
219
+ }
220
+ }
221
+
222
+ // Lifecycle
223
+ onMounted(() => {
224
+ // Initialize any needed global configurations
225
+ if (typeof window.initializeOnairosForBlade === 'function') {
226
+ window.initializeOnairosForBlade({
227
+ testMode: props.testMode,
228
+ autoDetectMobile: true,
229
+ globalStyles: false // Vue component will handle its own styles
230
+ });
231
+ }
232
+ });
233
+ </script>
234
+
235
+ <style scoped>
236
+ .onairos-vue-wrapper {
237
+ display: inline-block;
238
+ position: relative;
239
+ }
240
+
241
+ .onairos-vue-btn {
242
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
243
+ border: none;
244
+ color: white;
245
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
246
+ font-weight: 600;
247
+ cursor: pointer;
248
+ transition: all 0.3s ease;
249
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
250
+ outline: none;
251
+ }
252
+
253
+ .onairos-vue-btn:hover:not(.onairos-btn-disabled) {
254
+ transform: translateY(-2px);
255
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
256
+ }
257
+
258
+ .onairos-vue-btn:active:not(.onairos-btn-disabled) {
259
+ transform: translateY(0);
260
+ }
261
+
262
+ /* Button Types */
263
+ .onairos-btn-pill {
264
+ border-radius: 25px;
265
+ }
266
+
267
+ .onairos-btn-rounded {
268
+ border-radius: 8px;
269
+ }
270
+
271
+ .onairos-btn-icon {
272
+ border-radius: 50%;
273
+ width: 50px;
274
+ height: 50px;
275
+ padding: 0;
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: center;
279
+ }
280
+
281
+ /* Sizes */
282
+ .onairos-btn-small {
283
+ padding: 8px 16px;
284
+ font-size: 12px;
285
+ }
286
+
287
+ .onairos-btn-medium {
288
+ padding: 12px 24px;
289
+ font-size: 14px;
290
+ }
291
+
292
+ .onairos-btn-large {
293
+ padding: 16px 32px;
294
+ font-size: 16px;
295
+ }
296
+
297
+ .onairos-btn-icon.onairos-btn-small {
298
+ width: 35px;
299
+ height: 35px;
300
+ }
301
+
302
+ .onairos-btn-icon.onairos-btn-large {
303
+ width: 60px;
304
+ height: 60px;
305
+ }
306
+
307
+ /* Text Layout */
308
+ .onairos-text-left {
309
+ text-align: left;
310
+ }
311
+
312
+ .onairos-text-center {
313
+ text-align: center;
314
+ }
315
+
316
+ .onairos-text-right {
317
+ text-align: right;
318
+ }
319
+
320
+ /* States */
321
+ .onairos-btn-loading {
322
+ opacity: 0.7;
323
+ cursor: not-allowed;
324
+ }
325
+
326
+ .onairos-btn-disabled {
327
+ opacity: 0.5;
328
+ cursor: not-allowed;
329
+ }
330
+
331
+ /* Loading Spinner */
332
+ .onairos-loading {
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 8px;
336
+ padding: 12px 24px;
337
+ background: #f8f9fa;
338
+ border-radius: 25px;
339
+ border: 1px solid #e9ecef;
340
+ }
341
+
342
+ .onairos-spinner {
343
+ width: 16px;
344
+ height: 16px;
345
+ border: 2px solid #e9ecef;
346
+ border-top: 2px solid #667eea;
347
+ border-radius: 50%;
348
+ animation: onairos-spin 1s linear infinite;
349
+ }
350
+
351
+ @keyframes onairos-spin {
352
+ 0% { transform: rotate(0deg); }
353
+ 100% { transform: rotate(360deg); }
354
+ }
355
+
356
+ /* Success/Error Messages */
357
+ .onairos-success,
358
+ .onairos-error {
359
+ margin-top: 8px;
360
+ padding: 8px 12px;
361
+ border-radius: 4px;
362
+ font-size: 12px;
363
+ font-weight: 500;
364
+ }
365
+
366
+ .onairos-success {
367
+ background: #d4edda;
368
+ color: #155724;
369
+ border: 1px solid #c3e6cb;
370
+ }
371
+
372
+ .onairos-error {
373
+ background: #f8d7da;
374
+ color: #721c24;
375
+ border: 1px solid #f5c6cb;
376
+ }
377
+
378
+ /* Mobile Responsive */
379
+ @media (max-width: 768px) {
380
+ .onairos-btn-medium {
381
+ width: 100%;
382
+ padding: 15px 20px;
383
+ font-size: 16px;
384
+ }
385
+
386
+ .onairos-btn-small {
387
+ width: 100%;
388
+ padding: 12px 16px;
389
+ font-size: 14px;
390
+ }
391
+
392
+ .onairos-btn-large {
393
+ width: 100%;
394
+ padding: 18px 24px;
395
+ font-size: 18px;
396
+ }
397
+ }
398
+ </style>
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Onairos Laravel Blade Integration Helpers
3
+ *
4
+ * This file provides utilities for integrating Onairos components
5
+ * directly in PHP Blade templates without requiring React setup.
6
+ */
7
+
8
+ // Global Onairos instance for Blade templates
9
+ let onairosInstance = null;
10
+
11
+ /**
12
+ * Initialize Onairos for Blade template usage
13
+ * @param {Object} config - Global configuration
14
+ */
15
+ export function initializeOnairosForBlade(config = {}) {
16
+ const defaultConfig = {
17
+ apiKey: null,
18
+ baseUrl: 'https://api2.onairos.uk',
19
+ testMode: false,
20
+ autoDetectMobile: true,
21
+ globalStyles: true
22
+ };
23
+
24
+ window.OnairosConfig = { ...defaultConfig, ...config };
25
+
26
+ // Inject global styles if enabled
27
+ if (window.OnairosConfig.globalStyles) {
28
+ injectOnairosStyles();
29
+ }
30
+
31
+ // Initialize mobile detection
32
+ window.OnairosUtils = {
33
+ isMobile: detectMobileDevice(),
34
+ detectMobile: detectMobileDevice
35
+ };
36
+
37
+ console.log('🔥 Onairos initialized for Laravel Blade templates');
38
+ }
39
+
40
+ /**
41
+ * Create an Onairos button element that can be used in Blade templates
42
+ * @param {string} targetElementId - ID of the element to mount to
43
+ * @param {Object} options - Onairos button options
44
+ */
45
+ export function createOnairosButton(targetElementId, options = {}) {
46
+ const element = document.getElementById(targetElementId);
47
+ if (!element) {
48
+ console.error(`❌ Element with ID "${targetElementId}" not found`);
49
+ return;
50
+ }
51
+
52
+ const defaultOptions = {
53
+ requestData: ['email', 'profile'],
54
+ webpageName: 'Laravel App',
55
+ testMode: window.OnairosConfig?.testMode || false,
56
+ autoFetch: true,
57
+ buttonType: 'pill',
58
+ textColor: 'black'
59
+ };
60
+
61
+ const config = { ...defaultOptions, ...options };
62
+
63
+ // Create button HTML
64
+ element.innerHTML = `
65
+ <div class="onairos-button-container">
66
+ <button
67
+ id="${targetElementId}-btn"
68
+ class="onairos-btn onairos-btn-${config.buttonType}"
69
+ data-onairos-config='${JSON.stringify(config)}'
70
+ >
71
+ <span class="onairos-btn-text" style="color: ${config.textColor}">
72
+ Connect with Onairos
73
+ </span>
74
+ <span class="onairos-btn-loading" style="display: none;">
75
+ Loading...
76
+ </span>
77
+ </button>
78
+ </div>
79
+ `;
80
+
81
+ // Add event listener
82
+ const button = document.getElementById(`${targetElementId}-btn`);
83
+ button.addEventListener('click', () => handleOnairosButtonClick(config));
84
+ }
85
+
86
+ /**
87
+ * Handle Onairos button click in Blade context
88
+ * @param {Object} config - Button configuration
89
+ */
90
+ function handleOnairosButtonClick(config) {
91
+ // For Blade templates, we'll use the popup/iframe approach
92
+ if (window.OnairosUtils.isMobile) {
93
+ handleMobileFlow(config);
94
+ } else {
95
+ handleDesktopFlow(config);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Handle mobile OAuth flow (redirect-based)
101
+ * @param {Object} config - Configuration
102
+ */
103
+ function handleMobileFlow(config) {
104
+ const authUrl = buildAuthUrl(config);
105
+ window.location.href = authUrl;
106
+ }
107
+
108
+ /**
109
+ * Handle desktop OAuth flow (popup-based)
110
+ * @param {Object} config - Configuration
111
+ */
112
+ function handleDesktopFlow(config) {
113
+ const authUrl = buildAuthUrl(config);
114
+
115
+ const popup = window.open(
116
+ authUrl,
117
+ 'onairosAuth',
118
+ 'width=450,height=700,scrollbars=yes,resizable=yes'
119
+ );
120
+
121
+ if (popup) {
122
+ const checkClosed = setInterval(() => {
123
+ if (popup.closed) {
124
+ clearInterval(checkClosed);
125
+ handleAuthComplete(config);
126
+ }
127
+ }, 1000);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Build authentication URL for OAuth flow
133
+ * @param {Object} config - Configuration
134
+ * @returns {string} Auth URL
135
+ */
136
+ function buildAuthUrl(config) {
137
+ const baseUrl = window.OnairosConfig.baseUrl;
138
+ const params = new URLSearchParams({
139
+ webpageName: config.webpageName,
140
+ requestData: JSON.stringify(config.requestData),
141
+ testMode: config.testMode,
142
+ returnUrl: window.location.href
143
+ });
144
+
145
+ return `${baseUrl}/auth/laravel?${params.toString()}`;
146
+ }
147
+
148
+ /**
149
+ * Handle authentication completion
150
+ * @param {Object} config - Configuration
151
+ */
152
+ function handleAuthComplete(config) {
153
+ if (config.onComplete && typeof config.onComplete === 'function') {
154
+ config.onComplete({
155
+ success: true,
156
+ timestamp: new Date().toISOString()
157
+ });
158
+ }
159
+
160
+ // Trigger custom event for Laravel apps to listen to
161
+ const event = new CustomEvent('onairosAuthComplete', {
162
+ detail: { config, success: true }
163
+ });
164
+ window.dispatchEvent(event);
165
+ }
166
+
167
+ /**
168
+ * Detect if device is mobile
169
+ * @returns {boolean}
170
+ */
171
+ function detectMobileDevice() {
172
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
173
+ (window.innerWidth <= 768);
174
+ }
175
+
176
+ /**
177
+ * Inject Onairos styles into the page
178
+ */
179
+ function injectOnairosStyles() {
180
+ if (document.getElementById('onairos-styles')) return;
181
+
182
+ const styles = `
183
+ <style id="onairos-styles">
184
+ .onairos-button-container {
185
+ display: inline-block;
186
+ margin: 10px 0;
187
+ }
188
+
189
+ .onairos-btn {
190
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
191
+ border: none;
192
+ border-radius: 25px;
193
+ padding: 12px 24px;
194
+ color: white;
195
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
196
+ font-size: 14px;
197
+ font-weight: 600;
198
+ cursor: pointer;
199
+ transition: all 0.3s ease;
200
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
201
+ }
202
+
203
+ .onairos-btn:hover {
204
+ transform: translateY(-2px);
205
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
206
+ }
207
+
208
+ .onairos-btn-pill {
209
+ border-radius: 25px;
210
+ }
211
+
212
+ .onairos-btn-icon {
213
+ border-radius: 50%;
214
+ width: 45px;
215
+ height: 45px;
216
+ padding: 0;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ }
221
+
222
+ .onairos-btn-loading {
223
+ color: #888;
224
+ }
225
+
226
+ @media (max-width: 768px) {
227
+ .onairos-btn {
228
+ width: 100%;
229
+ padding: 15px 20px;
230
+ font-size: 16px;
231
+ }
232
+ }
233
+ </style>
234
+ `;
235
+
236
+ document.head.insertAdjacentHTML('beforeend', styles);
237
+ }
238
+
239
+ /**
240
+ * Laravel Blade template directive helper
241
+ * Usage in Blade: @onairos(['requestData' => ['email'], 'webpageName' => 'My App'])
242
+ */
243
+ export function renderOnairosDirective(options = {}) {
244
+ const elementId = `onairos-${Math.random().toString(36).substr(2, 9)}`;
245
+
246
+ return `
247
+ <div id="${elementId}"></div>
248
+ <script>
249
+ document.addEventListener('DOMContentLoaded', function() {
250
+ if (window.createOnairosButton) {
251
+ window.createOnairosButton('${elementId}', ${JSON.stringify(options)});
252
+ }
253
+ });
254
+ </script>
255
+ `;
256
+ }
257
+
258
+ // Expose functions globally for Blade template access
259
+ if (typeof window !== 'undefined') {
260
+ window.initializeOnairosForBlade = initializeOnairosForBlade;
261
+ window.createOnairosButton = createOnairosButton;
262
+ window.renderOnairosDirective = renderOnairosDirective;
263
+ }