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.
@@ -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
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Onairos Vite Plugin for Laravel
3
+ *
4
+ * This plugin provides seamless integration of Onairos components
5
+ * within Laravel Vite applications, handling both development and
6
+ * production builds.
7
+ */
8
+
9
+ import { resolve } from 'path';
10
+
11
+ export function onairosLaravelPlugin(options = {}) {
12
+ const defaultOptions = {
13
+ autoImport: true,
14
+ injectGlobals: true,
15
+ optimizeDeps: true,
16
+ enableHMR: true,
17
+ bladeSupport: true
18
+ };
19
+
20
+ const config = { ...defaultOptions, ...options };
21
+
22
+ return {
23
+ name: 'onairos-laravel',
24
+ config(viteConfig, { command }) {
25
+ // Optimize dependencies for faster dev server startup
26
+ if (config.optimizeDeps) {
27
+ viteConfig.optimizeDeps = viteConfig.optimizeDeps || {};
28
+ viteConfig.optimizeDeps.include = viteConfig.optimizeDeps.include || [];
29
+ viteConfig.optimizeDeps.include.push('onairos');
30
+ }
31
+
32
+ // Add alias for easier imports
33
+ viteConfig.resolve = viteConfig.resolve || {};
34
+ viteConfig.resolve.alias = viteConfig.resolve.alias || {};
35
+ viteConfig.resolve.alias['@onairos'] = resolve('node_modules/onairos');
36
+
37
+ // Configure for Laravel Blade support
38
+ if (config.bladeSupport && command === 'serve') {
39
+ viteConfig.server = viteConfig.server || {};
40
+ viteConfig.server.watch = viteConfig.server.watch || {};
41
+ viteConfig.server.watch.include = viteConfig.server.watch.include || [];
42
+ viteConfig.server.watch.include.push('resources/views/**/*.blade.php');
43
+ }
44
+ },
45
+
46
+ configureServer(server) {
47
+ if (config.enableHMR) {
48
+ // Add custom HMR handling for Onairos components
49
+ server.ws.on('onairos:reload', () => {
50
+ server.ws.send({
51
+ type: 'full-reload'
52
+ });
53
+ });
54
+ }
55
+ },
56
+
57
+ transformIndexHtml: {
58
+ enforce: 'pre',
59
+ transform(html, context) {
60
+ if (config.injectGlobals && context.server) {
61
+ // Inject Onairos initialization script for development
62
+ const initScript = `
63
+ <script type="module">
64
+ import { initializeOnairosForBlade } from '/node_modules/onairos/src/laravel/blade-helpers.js';
65
+
66
+ // Initialize Onairos for Laravel Blade templates
67
+ initializeOnairosForBlade({
68
+ testMode: true,
69
+ autoDetectMobile: true,
70
+ globalStyles: true
71
+ });
72
+
73
+ // Enable HMR for Onairos components
74
+ if (import.meta.hot) {
75
+ import.meta.hot.on('onairos:update', () => {
76
+ console.log('🔥 Onairos components updated');
77
+ window.location.reload();
78
+ });
79
+ }
80
+ </script>
81
+ `;
82
+
83
+ return html.replace('<head>', `<head>${initScript}`);
84
+ }
85
+ return html;
86
+ }
87
+ },
88
+
89
+ generateBundle(options, bundle) {
90
+ // Add Onairos assets to the build output
91
+ if (config.autoImport) {
92
+ // Create a separate chunk for Laravel integration
93
+ this.emitFile({
94
+ type: 'chunk',
95
+ id: 'onairos-laravel-integration',
96
+ fileName: 'onairos-laravel.js'
97
+ });
98
+ }
99
+ },
100
+
101
+ writeBundle(options, bundle) {
102
+ // Create Laravel-specific integration files
103
+ const laravelIntegrationCode = `
104
+ // Onairos Laravel Integration
105
+ import { initializeOnairosForBlade, createOnairosButton } from 'onairos/blade';
106
+
107
+ // Auto-initialize for production
108
+ document.addEventListener('DOMContentLoaded', () => {
109
+ initializeOnairosForBlade({
110
+ testMode: false,
111
+ autoDetectMobile: true,
112
+ globalStyles: true
113
+ });
114
+ });
115
+
116
+ // Export for manual usage
117
+ window.Onairos = {
118
+ init: initializeOnairosForBlade,
119
+ createButton: createOnairosButton
120
+ };
121
+ `;
122
+
123
+ // Write the integration file
124
+ this.emitFile({
125
+ type: 'asset',
126
+ fileName: 'onairos-laravel-integration.js',
127
+ source: laravelIntegrationCode
128
+ });
129
+ }
130
+ };
131
+ }
132
+
133
+ // Vue.js specific plugin for Laravel
134
+ export function onairosVuePlugin(options = {}) {
135
+ return {
136
+ name: 'onairos-vue-laravel',
137
+ config(viteConfig) {
138
+ // Add Vue-specific optimizations for Onairos
139
+ viteConfig.optimizeDeps = viteConfig.optimizeDeps || {};
140
+ viteConfig.optimizeDeps.include = viteConfig.optimizeDeps.include || [];
141
+ viteConfig.optimizeDeps.include.push('onairos', 'vue');
142
+ },
143
+
144
+ transform(code, id) {
145
+ // Auto-import Onairos in Vue components
146
+ if (id.endsWith('.vue') && options.autoImport) {
147
+ if (code.includes('OnairosButton') && !code.includes('import.*OnairosButton')) {
148
+ return `import { OnairosButton } from 'onairos';\n${code}`;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ };
154
+ }
155
+
156
+ // React specific plugin for Laravel
157
+ export function onairosReactPlugin(options = {}) {
158
+ return {
159
+ name: 'onairos-react-laravel',
160
+ config(viteConfig) {
161
+ // Add React-specific optimizations for Onairos
162
+ viteConfig.optimizeDeps = viteConfig.optimizeDeps || {};
163
+ viteConfig.optimizeDeps.include = viteConfig.optimizeDeps.include || [];
164
+ viteConfig.optimizeDeps.include.push('onairos', 'react', 'react-dom');
165
+ },
166
+
167
+ transform(code, id) {
168
+ // Auto-import Onairos in React components
169
+ if ((id.endsWith('.jsx') || id.endsWith('.tsx')) && options.autoImport) {
170
+ if (code.includes('OnairosButton') && !code.includes('import.*OnairosButton')) {
171
+ return `import { OnairosButton } from 'onairos';\n${code}`;
172
+ }
173
+ }
174
+ return null;
175
+ }
176
+ };
177
+ }
178
+
179
+ export default onairosLaravelPlugin;
@@ -8,46 +8,106 @@ function Box({
8
8
  setSelected,
9
9
  number,
10
10
  type,
11
- title}) {
11
+ title
12
+ }) {
12
13
  const [isChecked, setIsChecked] = useState(false);
13
14
 
14
15
  const handleCheckboxChange = (newState) => {
15
16
  setIsChecked(newState);
16
- console.log("This data request is active :", active)
17
- console.log("With new Checked state now being: ", newState)
17
+ console.log("This data request is active:", active);
18
+ console.log("With new Checked state now being:", newState);
19
+
18
20
  // Update the counter
19
21
  if (newState) {
20
22
  changeGranted(1);
21
23
  } else {
22
24
  changeGranted(-1);
23
25
  }
24
- // onSelectionChange(type);
26
+
27
+ // Call parent callback if provided
28
+ if (onSelectionChange) {
29
+ onSelectionChange(newState);
30
+ }
25
31
  };
26
32
 
27
- return (
28
- <div className="group">
29
- <div
30
-
31
- >
32
- <input
33
- // disabled={!active}
34
- type="checkbox"
35
- checked={isChecked}
36
- onChange={(e) => {
37
- // setIsChecked(e.target.checked);
38
-
39
- handleCheckboxChange(e.target.checked);
40
- }}
41
- />
33
+ return (
34
+ <div className="group">
35
+ <div className="relative">
36
+ {/* Custom styled checkbox with clear visual feedback */}
37
+ <label className="flex items-center cursor-pointer">
38
+ <div className="relative">
39
+ <input
40
+ type="checkbox"
41
+ checked={isChecked}
42
+ disabled={!active}
43
+ onChange={(e) => handleCheckboxChange(e.target.checked)}
44
+ className="sr-only" // Hide default checkbox
45
+ />
46
+
47
+ {/* Custom checkbox appearance */}
48
+ <div className={`
49
+ w-6 h-6 border-2 rounded-md flex items-center justify-center transition-all duration-200
50
+ ${!active
51
+ ? 'border-gray-300 bg-gray-100 cursor-not-allowed opacity-50'
52
+ : isChecked
53
+ ? 'border-green-500 bg-green-500 shadow-md transform scale-105'
54
+ : 'border-gray-400 bg-white hover:border-green-400 hover:bg-green-50'
55
+ }
56
+ `}>
57
+ {/* Checkmark icon */}
58
+ {isChecked && (
59
+ <svg
60
+ className="w-4 h-4 text-white animate-pulse"
61
+ fill="currentColor"
62
+ viewBox="0 0 20 20"
63
+ >
64
+ <path
65
+ fillRule="evenodd"
66
+ 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"
67
+ clipRule="evenodd"
68
+ />
69
+ </svg>
70
+ )}
71
+ </div>
72
+ </div>
73
+
74
+ {/* Selection status text */}
75
+ <span className={`ml-3 text-sm font-medium transition-colors duration-200 ${
76
+ !active
77
+ ? 'text-gray-400'
78
+ : isChecked
79
+ ? 'text-green-600 font-semibold'
80
+ : 'text-gray-700'
81
+ }`}>
82
+ {!active
83
+ ? 'Not available'
84
+ : isChecked
85
+ ? '✓ Selected'
86
+ : 'Click to select'
87
+ }
88
+ </span>
89
+ </label>
90
+
91
+ {/* Selection indicator badge */}
92
+ {isChecked && active && (
93
+ <div className="absolute -top-2 -right-2 w-4 h-4 bg-green-500 rounded-full flex items-center justify-center animate-bounce">
94
+ <svg className="w-2 h-2 text-white" fill="currentColor" viewBox="0 0 20 20">
95
+ <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" />
96
+ </svg>
97
+ </div>
98
+ )}
99
+ </div>
42
100
  </div>
43
- </div>
44
- );
101
+ );
45
102
  }
46
103
 
47
104
  Box.propTypes = {
48
105
  active: PropTypes.bool.isRequired,
49
- onSelectionChange: PropTypes.func.isRequired,
50
- disabled: PropTypes.bool,
106
+ onSelectionChange: PropTypes.func,
107
+ changeGranted: PropTypes.func.isRequired,
108
+ setSelected: PropTypes.func,
109
+ number: PropTypes.number,
110
+ type: PropTypes.string,
51
111
  title: PropTypes.string.isRequired
52
112
  };
53
113