onairos 2.2.0 → 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/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/laravel.txt +430 -0
- package/package.json +31 -2
- package/src/components/DataRequest.js +35 -97
- package/src/laravel/OnairosVue.vue +398 -0
- package/src/laravel/blade-helpers.js +263 -0
- package/src/laravel/vite-plugin.js +179 -0
- package/src/overlay/CheckBox.jsx +83 -23
- package/src/overlay/IndividualConnection.js +145 -15
- package/src/overlay/IndividualConnectionNew.jsx +101 -22
- 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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
const platformConnectors = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
];
|
|
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
|
+
// ];
|
|
14
14
|
|
|
15
15
|
const dataTypes = [
|
|
16
16
|
{
|
|
@@ -37,7 +37,7 @@ const dataTypes = [
|
|
|
37
37
|
description: 'User preferences, interests, settings and personal choices',
|
|
38
38
|
icon: '⚙️',
|
|
39
39
|
required: false,
|
|
40
|
-
tooltip: 'Your
|
|
40
|
+
tooltip: 'Your stated preferences and interests from connected platforms. Helps customize your experience.',
|
|
41
41
|
privacyLink: 'https://onairos.uk/privacy#preferences-data'
|
|
42
42
|
}
|
|
43
43
|
];
|
|
@@ -86,23 +86,24 @@ export default function DataRequest({
|
|
|
86
86
|
preferences: false
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
// Remove connector states - not needed for data request
|
|
90
|
+
// const [connectorStates, setConnectorStates] = useState({});
|
|
90
91
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
91
92
|
const [isLoadingApi, setIsLoadingApi] = useState(false);
|
|
92
93
|
const [apiResponse, setApiResponse] = useState(null);
|
|
93
94
|
const [apiError, setApiError] = useState(null);
|
|
94
95
|
|
|
95
|
-
//
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}, [connectedAccounts]);
|
|
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]);
|
|
106
107
|
|
|
107
108
|
const handleDataToggle = (dataId) => {
|
|
108
109
|
// Don't allow toggling required items
|
|
@@ -115,15 +116,7 @@ export default function DataRequest({
|
|
|
115
116
|
}));
|
|
116
117
|
};
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
setConnectorStates(prev => ({
|
|
120
|
-
...prev,
|
|
121
|
-
[platformName]: {
|
|
122
|
-
...prev[platformName],
|
|
123
|
-
selected: !prev[platformName]?.selected
|
|
124
|
-
}
|
|
125
|
-
}));
|
|
126
|
-
};
|
|
119
|
+
// handleConnectorToggle removed - connectors are handled in onboarding
|
|
127
120
|
|
|
128
121
|
const handleRowClick = (dataId) => {
|
|
129
122
|
const dataType = dataTypes.find(dt => dt.id === dataId);
|
|
@@ -155,9 +148,9 @@ export default function DataRequest({
|
|
|
155
148
|
.map(([key]) => key);
|
|
156
149
|
|
|
157
150
|
// Get selected connectors
|
|
158
|
-
const selectedConnectors = Object.entries(connectorStates)
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
// const selectedConnectors = Object.entries(connectorStates)
|
|
152
|
+
// .filter(([platform, state]) => state.selected && state.connected)
|
|
153
|
+
// .map(([platform]) => platform);
|
|
161
154
|
|
|
162
155
|
const mapDataTypesToConfirmations = (approvedData) => {
|
|
163
156
|
const confirmations = [];
|
|
@@ -189,7 +182,7 @@ export default function DataRequest({
|
|
|
189
182
|
userHash,
|
|
190
183
|
appName,
|
|
191
184
|
approvedData,
|
|
192
|
-
selectedConnectors,
|
|
185
|
+
// selectedConnectors, // Removed as connectors are handled in onboarding
|
|
193
186
|
apiUrl: apiEndpoint,
|
|
194
187
|
testMode,
|
|
195
188
|
timestamp: new Date().toISOString()
|
|
@@ -201,7 +194,7 @@ export default function DataRequest({
|
|
|
201
194
|
|
|
202
195
|
const requestBody = testMode ? {
|
|
203
196
|
approvedData,
|
|
204
|
-
selectedConnectors,
|
|
197
|
+
// selectedConnectors, // Removed as connectors are handled in onboarding
|
|
205
198
|
userEmail,
|
|
206
199
|
appName,
|
|
207
200
|
testMode,
|
|
@@ -211,7 +204,7 @@ export default function DataRequest({
|
|
|
211
204
|
storage: "local",
|
|
212
205
|
appId: appName,
|
|
213
206
|
confirmations: confirmations,
|
|
214
|
-
connectors: selectedConnectors,
|
|
207
|
+
// connectors: selectedConnectors, // Removed as connectors are handled in onboarding
|
|
215
208
|
EncryptedUserPin: "pending_pin_integration",
|
|
216
209
|
account: userEmail,
|
|
217
210
|
proofMode: false,
|
|
@@ -297,8 +290,8 @@ export default function DataRequest({
|
|
|
297
290
|
};
|
|
298
291
|
|
|
299
292
|
const selectedCount = Object.values(selectedData).filter(Boolean).length;
|
|
300
|
-
const selectedConnectorCount = Object.values(connectorStates).filter(state => state.selected).length;
|
|
301
|
-
const connectedCount = Object.values(connectorStates).filter(state => state.connected).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
|
|
302
295
|
|
|
303
296
|
return (
|
|
304
297
|
<div className="w-full max-w-md mx-auto bg-white rounded-lg shadow-xl overflow-hidden" style={{ maxHeight: '90vh', height: 'auto' }}>
|
|
@@ -378,65 +371,10 @@ export default function DataRequest({
|
|
|
378
371
|
</div>
|
|
379
372
|
</div>
|
|
380
373
|
|
|
381
|
-
{/* Platform Connectors */}
|
|
382
|
-
<div className="mb-6">
|
|
383
|
-
<h3 className="text-md font-semibold text-gray-900 mb-3">Data Connectors</h3>
|
|
384
|
-
<p className="text-xs text-gray-600 mb-3">Select connected platforms to include their data</p>
|
|
385
|
-
|
|
386
|
-
<div className="grid grid-cols-2 gap-2">
|
|
387
|
-
{platformConnectors.map((platform) => {
|
|
388
|
-
const connectorState = connectorStates[platform.name];
|
|
389
|
-
const isConnected = connectorState?.connected || false;
|
|
390
|
-
const isSelected = connectorState?.selected || false;
|
|
391
|
-
|
|
392
|
-
return (
|
|
393
|
-
<div
|
|
394
|
-
key={platform.name}
|
|
395
|
-
className={`p-2 border rounded-lg transition-all duration-200 cursor-pointer ${
|
|
396
|
-
!isConnected ? 'opacity-50 cursor-not-allowed border-gray-200 bg-gray-50' :
|
|
397
|
-
isSelected ? 'border-green-400 bg-green-50' :
|
|
398
|
-
'border-gray-200 bg-white hover:border-gray-300'
|
|
399
|
-
}`}
|
|
400
|
-
onClick={() => isConnected && handleConnectorToggle(platform.name)}
|
|
401
|
-
>
|
|
402
|
-
<div className={`w-6 h-6 rounded ${platform.color} flex items-center justify-center text-white text-sm mb-2 mx-auto relative`}>
|
|
403
|
-
{platform.icon}
|
|
404
|
-
|
|
405
|
-
{/* Connection Status Indicator */}
|
|
406
|
-
{isConnected && isSelected && (
|
|
407
|
-
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full flex items-center justify-center">
|
|
408
|
-
<svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
|
|
409
|
-
<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" />
|
|
410
|
-
</svg>
|
|
411
|
-
</div>
|
|
412
|
-
)}
|
|
413
|
-
</div>
|
|
414
|
-
|
|
415
|
-
<div className="text-center">
|
|
416
|
-
<h4 className="font-medium text-gray-900 text-xs">{platform.name}</h4>
|
|
417
|
-
<p className={`text-xs ${
|
|
418
|
-
isConnected ? (isSelected ? 'text-green-600' : 'text-blue-600') : 'text-gray-400'
|
|
419
|
-
}`}>
|
|
420
|
-
{!isConnected ? 'Not connected' :
|
|
421
|
-
isSelected ? 'Selected' : 'Tap to select'}
|
|
422
|
-
</p>
|
|
423
|
-
</div>
|
|
424
|
-
</div>
|
|
425
|
-
);
|
|
426
|
-
})}
|
|
427
|
-
</div>
|
|
428
|
-
|
|
429
|
-
{connectedCount === 0 && (
|
|
430
|
-
<p className="text-xs text-yellow-600 mt-2 text-center">
|
|
431
|
-
⚠️ No platforms connected. Connect platforms in onboarding to select data sources.
|
|
432
|
-
</p>
|
|
433
|
-
)}
|
|
434
|
-
</div>
|
|
435
|
-
|
|
436
374
|
{/* Selection Summary */}
|
|
437
375
|
<div className="mb-3 sm:mb-4 p-2 sm:p-3 bg-green-50 border border-green-200 rounded-lg">
|
|
438
376
|
<p className="text-green-800 text-xs sm:text-sm">
|
|
439
|
-
✅ {selectedCount} data type{selectedCount > 1 ? 's' : ''}
|
|
377
|
+
✅ {selectedCount} data type{selectedCount > 1 ? 's' : ''} selected
|
|
440
378
|
</p>
|
|
441
379
|
</div>
|
|
442
380
|
|
|
@@ -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>
|