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.
- package/LARAVEL_INTEGRATION_GUIDE.md +643 -0
- package/LARAVEL_TECHNICAL_EXPLANATION.md +465 -0
- package/README.md +122 -43
- package/dist/onairos-laravel.js +2 -0
- package/dist/onairos-laravel.js.map +1 -0
- package/laravel.txt +430 -0
- package/package.json +31 -2
- package/src/laravel/OnairosVue.vue +398 -0
- package/src/laravel/blade-helpers.js +263 -0
- package/src/laravel/vite-plugin.js +179 -0
- package/tests/laravel/examples/blade-example.test.js +283 -0
- package/tests/laravel/laravel-integration.test.js +647 -0
- package/tests/laravel/setup.js +84 -0
- package/tests/laravel/vitest.config.js +20 -0
- package/webpack.config.js +46 -13
|
@@ -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
|
+
}
|