my-fleetbo-react-v1 1.0.2
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/README.md +16 -0
- package/eslint.config.js +33 -0
- package/globals.d.ts +139 -0
- package/index.html +14 -0
- package/jsconfig.json +11 -0
- package/package.json +39 -0
- package/public/branding/ic_launcher-playstore.png +0 -0
- package/public/branding/logo.png +0 -0
- package/public/native/iOS/Hello.text +1 -0
- package/src/@fleetbo/components/common/Loader.jsx +45 -0
- package/src/@fleetbo/components/common/PageConfig.js +21 -0
- package/src/@fleetbo/components/layout/Navigation.js +4 -0
- package/src/@fleetbo/components/layout/ProtectedLayout.jsx +14 -0
- package/src/@fleetbo/config/fleetboConfig.js +3 -0
- package/src/@fleetbo/config/fleetboEngine.js +93 -0
- package/src/@fleetbo/config/systemProtocol.js +223 -0
- package/src/@fleetbo/hooks/useLoadingTimeout.js +18 -0
- package/src/@fleetbo/hooks/useModalLauncher.js +22 -0
- package/src/@fleetbo/hooks/useStartupEffect.js +111 -0
- package/src/@fleetbo/index.js +7 -0
- package/src/@fleetbo/utils/FormatDate.js +52 -0
- package/src/@fleetbo/utils/getToken.js +12 -0
- package/src/App.jsx +119 -0
- package/src/app/assets/css/App.css +91 -0
- package/src/app/assets/css/Auth.css +50 -0
- package/src/app/assets/css/Form.css +113 -0
- package/src/app/assets/images/avatar.png +0 -0
- package/src/app/assets/images/logo.png +0 -0
- package/src/app/core/AuthRouter.jsx +36 -0
- package/src/app/core/NotFound.jsx +18 -0
- package/src/app/mocks/Login.jsx +89 -0
- package/src/app/mocks/SampleTab1.jsx +60 -0
- package/src/app/mocks/SampleTab2.jsx +64 -0
- package/src/app/mocks/SampleTab3.jsx +64 -0
- package/src/app/tabs/Tab1.jsx +40 -0
- package/src/app/tabs/Tab2.jsx +25 -0
- package/src/app/tabs/Tab3.jsx +23 -0
- package/src/app/tabs/Welcome.jsx +174 -0
- package/src/main.jsx +11 -0
- package/vite.config.js +32 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* === Fleetbo Developer Guide: The Authentication Gateway (Login.jsx) ===
|
|
3
|
+
*
|
|
4
|
+
* This component is the ignition key for your application.
|
|
5
|
+
* It establishes the secure link with the Fleetbo Native Engine.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import 'app/assets/css/Auth.css';
|
|
10
|
+
import logo from 'app/assets/images/logo.png';
|
|
11
|
+
import { PageConfig } from '@fleetbo';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TAB = 'tab1';
|
|
14
|
+
|
|
15
|
+
const Login = ({ sessionData: sessionDataProp }) => {
|
|
16
|
+
const sessionData = sessionDataProp || (() => {
|
|
17
|
+
try {
|
|
18
|
+
const s = localStorage.getItem('fleetbo_session');
|
|
19
|
+
return s ? JSON.parse(s) : null;
|
|
20
|
+
} catch (e) { return null; }
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
const [phoneNumber, setPhoneNumber] = useState('');
|
|
24
|
+
const [loadingLog, setLoadingLog] = useState(false);
|
|
25
|
+
|
|
26
|
+
const handleVerifyPhoneNumber = async () => {
|
|
27
|
+
if (!phoneNumber.trim()) return;
|
|
28
|
+
setLoadingLog(true);
|
|
29
|
+
try {
|
|
30
|
+
const result = await Fleetbo.logTester(phoneNumber.trim());
|
|
31
|
+
if (result && result.success) {
|
|
32
|
+
await Fleetbo.log(DEFAULT_TAB);
|
|
33
|
+
} else {
|
|
34
|
+
alert("Unauthorized.\n\nPlease check your phone number.");
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
alert("Connection error during verification.");
|
|
38
|
+
} finally {
|
|
39
|
+
setLoadingLog(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<>
|
|
45
|
+
<PageConfig navbar="none" />
|
|
46
|
+
|
|
47
|
+
<div className="login-passerelle-container" style={{ animation: 'fadeIn 0.5s ease-in-out' }}>
|
|
48
|
+
<div className="login-passerelle-box">
|
|
49
|
+
{sessionData ? (
|
|
50
|
+
<div className="w-100 d-flex flex-column align-items-center text-center" style={{ maxWidth: '340px' }}>
|
|
51
|
+
<img className="mb-3" src={logo} alt="logo" style={{ width: '50px', height: '50px' }} />
|
|
52
|
+
<h3 className="fw-bold mb-1" style={{ fontSize: '19px', color: '#4c8a69' }}>
|
|
53
|
+
{sessionData.appName || "Fleetbo"}
|
|
54
|
+
</h3>
|
|
55
|
+
<p className="text-muted mb-4" style={{ fontSize: '13px' }}>
|
|
56
|
+
{sessionData.description || "Mobile Cloud OS"}
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<input
|
|
60
|
+
type="tel"
|
|
61
|
+
className="form-control p-3 text-center mb-3 w-100"
|
|
62
|
+
style={{ height: '55px', borderRadius: '10px' }}
|
|
63
|
+
placeholder="Enter your phone number"
|
|
64
|
+
value={phoneNumber}
|
|
65
|
+
onChange={(e) => setPhoneNumber(e.target.value)}
|
|
66
|
+
/>
|
|
67
|
+
<button
|
|
68
|
+
onClick={handleVerifyPhoneNumber}
|
|
69
|
+
className="btn p-3 fs-6 btn-success w-100"
|
|
70
|
+
style={{ borderRadius: '14px', height: '55px', backgroundColor: '#4c8a69', border: 'none', fontWeight: 'bold' }}
|
|
71
|
+
disabled={loadingLog}
|
|
72
|
+
>
|
|
73
|
+
{loadingLog ? (
|
|
74
|
+
<><span className="spinner-border spinner-border-sm me-2" />Checking...</>
|
|
75
|
+
) : (
|
|
76
|
+
"Login"
|
|
77
|
+
)}
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
) : (
|
|
81
|
+
<p className="text-danger">Failed to load app data.</p>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default Login;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function SampleTab() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className="d-flex flex-column justify-content-center align-items-center w-100"
|
|
7
|
+
style={{
|
|
8
|
+
minHeight: '100vh',
|
|
9
|
+
backgroundColor: '#F8F9FA',
|
|
10
|
+
padding: '24px',
|
|
11
|
+
paddingBottom: '80px'
|
|
12
|
+
}}
|
|
13
|
+
>
|
|
14
|
+
<h1
|
|
15
|
+
style={{
|
|
16
|
+
fontSize: '28px',
|
|
17
|
+
fontWeight: 'bold',
|
|
18
|
+
color: '#212529',
|
|
19
|
+
margin: '0 0 16px 0',
|
|
20
|
+
textAlign: 'center'
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
Tab 1 - Mock
|
|
24
|
+
</h1>
|
|
25
|
+
|
|
26
|
+
<p
|
|
27
|
+
style={{
|
|
28
|
+
fontSize: '16px',
|
|
29
|
+
color: '#6C757D',
|
|
30
|
+
textAlign: 'center',
|
|
31
|
+
lineHeight: '24px',
|
|
32
|
+
margin: '0 0 40px 0',
|
|
33
|
+
maxWidth: '400px'
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
This is a Fleetbo View. The interface is rendered natively at 120 FPS.<br />
|
|
37
|
+
Open the Compose panel, describe your feature in plain language, and Alex forges the Fleetbo View instantly.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<div
|
|
41
|
+
className="d-flex align-items-center justify-content-center text-white"
|
|
42
|
+
style={{
|
|
43
|
+
backgroundColor: '#0E904D',
|
|
44
|
+
borderRadius: '12px',
|
|
45
|
+
width: '100%',
|
|
46
|
+
maxWidth: '400px',
|
|
47
|
+
height: '56px',
|
|
48
|
+
cursor: 'default',
|
|
49
|
+
fontWeight: 'bold',
|
|
50
|
+
fontSize: '16px',
|
|
51
|
+
transition: 'transform 0.1s'
|
|
52
|
+
}}
|
|
53
|
+
onClick={() => Fleetbo.emit('PING_JS', {})}
|
|
54
|
+
>
|
|
55
|
+
EMIT SIGNAL TO JS
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
// ⚡ Forged by Alex on 2026-03-28 at 15:04:20
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function SampleTab() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className="d-flex flex-column justify-content-center align-items-center w-100"
|
|
7
|
+
style={{
|
|
8
|
+
minHeight: '100vh',
|
|
9
|
+
backgroundColor: '#F8F9FA',
|
|
10
|
+
padding: '24px',
|
|
11
|
+
paddingBottom: '80px'
|
|
12
|
+
}}
|
|
13
|
+
>
|
|
14
|
+
<h1
|
|
15
|
+
style={{
|
|
16
|
+
fontSize: '28px',
|
|
17
|
+
fontWeight: 'bold',
|
|
18
|
+
color: '#212529',
|
|
19
|
+
margin: '0 0 16px 0',
|
|
20
|
+
textAlign: 'center'
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
Tab 2 - Mock
|
|
24
|
+
</h1>
|
|
25
|
+
|
|
26
|
+
<p
|
|
27
|
+
style={{
|
|
28
|
+
fontSize: '16px',
|
|
29
|
+
color: '#6C757D',
|
|
30
|
+
textAlign: 'center',
|
|
31
|
+
lineHeight: '24px',
|
|
32
|
+
margin: '0 0 40px 0',
|
|
33
|
+
maxWidth: '400px'
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
This is a Fleetbo View. The interface is rendered natively at 120 FPS.<br />
|
|
37
|
+
Open the Compose panel, describe your feature in plain language, and Alex forges the Fleetbo View instantly.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<div
|
|
41
|
+
className="d-flex align-items-center justify-content-center text-white"
|
|
42
|
+
style={{
|
|
43
|
+
backgroundColor: '#0E904D',
|
|
44
|
+
borderRadius: '12px',
|
|
45
|
+
width: '100%',
|
|
46
|
+
maxWidth: '400px',
|
|
47
|
+
height: '56px',
|
|
48
|
+
cursor: 'default',
|
|
49
|
+
fontWeight: 'bold',
|
|
50
|
+
fontSize: '16px',
|
|
51
|
+
transition: 'transform 0.1s'
|
|
52
|
+
}}
|
|
53
|
+
onTouchStart={(e) => e.currentTarget.style.transform = 'scale(0.96)'}
|
|
54
|
+
onTouchEnd={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
|
55
|
+
onMouseDown={(e) => e.currentTarget.style.transform = 'scale(0.96)'}
|
|
56
|
+
onMouseUp={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
|
57
|
+
onClick={() => Fleetbo.emit('PING_JS', {})}
|
|
58
|
+
>
|
|
59
|
+
EMIT SIGNAL TO JS
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
// ⚡ Forged by Alex on 2026-03-28 at 15:04:20
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function SampleTab() {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className="d-flex flex-column justify-content-center align-items-center w-100"
|
|
7
|
+
style={{
|
|
8
|
+
minHeight: '100vh',
|
|
9
|
+
backgroundColor: '#F8F9FA',
|
|
10
|
+
padding: '24px',
|
|
11
|
+
paddingBottom: '80px'
|
|
12
|
+
}}
|
|
13
|
+
>
|
|
14
|
+
<h1
|
|
15
|
+
style={{
|
|
16
|
+
fontSize: '28px',
|
|
17
|
+
fontWeight: 'bold',
|
|
18
|
+
color: '#212529',
|
|
19
|
+
margin: '0 0 16px 0',
|
|
20
|
+
textAlign: 'center'
|
|
21
|
+
}}
|
|
22
|
+
>
|
|
23
|
+
Tab 3 - Mock
|
|
24
|
+
</h1>
|
|
25
|
+
|
|
26
|
+
<p
|
|
27
|
+
style={{
|
|
28
|
+
fontSize: '16px',
|
|
29
|
+
color: '#6C757D',
|
|
30
|
+
textAlign: 'center',
|
|
31
|
+
lineHeight: '24px',
|
|
32
|
+
margin: '0 0 40px 0',
|
|
33
|
+
maxWidth: '400px'
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
This is a Fleetbo View. The interface is rendered natively at 120 FPS.<br />
|
|
37
|
+
Open the Compose panel, describe your feature in plain language, and Alex forges the Fleetbo View instantly.
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<div
|
|
41
|
+
className="d-flex align-items-center justify-content-center text-white"
|
|
42
|
+
style={{
|
|
43
|
+
backgroundColor: '#de4751ff',
|
|
44
|
+
borderRadius: '12px',
|
|
45
|
+
width: '100%',
|
|
46
|
+
maxWidth: '400px',
|
|
47
|
+
height: '56px',
|
|
48
|
+
cursor: 'default',
|
|
49
|
+
fontWeight: 'bold',
|
|
50
|
+
fontSize: '16px',
|
|
51
|
+
transition: 'transform 0.1s'
|
|
52
|
+
}}
|
|
53
|
+
onTouchStart={(e) => e.currentTarget.style.transform = 'scale(0.96)'}
|
|
54
|
+
onTouchEnd={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
|
55
|
+
onMouseDown={(e) => e.currentTarget.style.transform = 'scale(0.96)'}
|
|
56
|
+
onMouseUp={(e) => e.currentTarget.style.transform = 'scale(1)'}
|
|
57
|
+
onClick={() => Fleetbo.emit('LOGOUT', {})}
|
|
58
|
+
>
|
|
59
|
+
Log Out
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
// ⚡ Forged by Alex on 2026-03-28 at 15:04:20
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab1.jsx — Fleetbo JS in Action
|
|
3
|
+
*
|
|
4
|
+
* JS = Brain (orchestrates) | Native = Muscle (executes) | Alex = Architect (forges)
|
|
5
|
+
*
|
|
6
|
+
* Two patterns:
|
|
7
|
+
* Manager → List + Create in one module. Fleetbo.openView('GuestManager', true)
|
|
8
|
+
* Atomic → One action, closes. await Fleetbo.exec('Scanner', 'open', {})
|
|
9
|
+
*
|
|
10
|
+
* Alex (npm run alex):
|
|
11
|
+
* "Forge a guest manager with list and photo" → Alex builds
|
|
12
|
+
* "A guest manager with list and photo" → Alex stays conversational
|
|
13
|
+
* Rule: use "forge", "create", "modify" to trigger code generation.
|
|
14
|
+
*
|
|
15
|
+
* Quick ref:
|
|
16
|
+
* Fleetbo.openView('Module', true) → Native tab
|
|
17
|
+
* Fleetbo.exec('Module', 'action', {}) → Native overlay
|
|
18
|
+
* Fleetbo.add(db, col, json) → Write doc
|
|
19
|
+
* Fleetbo.getDocsG(db, col) → Read docs
|
|
20
|
+
* Fleetbo.delete(db, col, id) → Delete doc
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { useEffect, useState } from 'react';
|
|
24
|
+
import { PageConfig } from '@fleetbo';
|
|
25
|
+
|
|
26
|
+
export default function Tab1() {
|
|
27
|
+
const [navMode, setNavMode] = useState("show");
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
Fleetbo.openView('SampleTab1', true, {
|
|
30
|
+
emit: async (action, payload) => {
|
|
31
|
+
if (action === 'HIDE_NAVBAR') setNavMode("none");
|
|
32
|
+
if (action === 'SHOW_NAVBAR') setNavMode("show");
|
|
33
|
+
if (action === 'PING_JS') {
|
|
34
|
+
// Example : Fleetbo.exec('ModuleName', 'action', {}); Generate by Alex
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}, []);
|
|
39
|
+
return (<> <PageConfig navbar={navMode} /> </>);
|
|
40
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleetbo Tab Redirect or Not
|
|
3
|
+
*
|
|
4
|
+
* This tab is handled by a native module (or its mock in dev).
|
|
5
|
+
* If React renders this route (e.g. on reload), we redirect to the mock.
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
import { PageConfig } from '@fleetbo';
|
|
9
|
+
|
|
10
|
+
export default function Tab2() {
|
|
11
|
+
const [navMode, setNavMode] = useState("show");
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
Fleetbo.openView('SampleTab2', true, {
|
|
14
|
+
emit: async (action, payload) => {
|
|
15
|
+
if (action === 'HIDE_NAVBAR') setNavMode("none");
|
|
16
|
+
if (action === 'SHOW_NAVBAR') setNavMode("show");
|
|
17
|
+
if (action === 'PING_JS') {
|
|
18
|
+
console.log("Signal reçu du Métal !");
|
|
19
|
+
// Example: Fleetbo.exec('MyModuleName', 'open', {});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}, []);
|
|
24
|
+
return (<> <PageConfig navbar={navMode} /> </>);
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fleetbo Tab Redirect or Not
|
|
3
|
+
* This tab is handled by a fleetbo module (or its mock in dev).
|
|
4
|
+
*/
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { PageConfig } from '@fleetbo';
|
|
7
|
+
|
|
8
|
+
export default function Tab3() {
|
|
9
|
+
const [navMode, setNavMode] = useState("show");
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
Fleetbo.openView('SampleTab3', true, {
|
|
12
|
+
emit: async (action, payload) => {
|
|
13
|
+
if (action === 'HIDE_NAVBAR') setNavMode("none");
|
|
14
|
+
if (action === 'SHOW_NAVBAR') setNavMode("show");
|
|
15
|
+
if (action === 'LOGOUT') { Fleetbo.logout(); }
|
|
16
|
+
//if (action === 'OPEN_EDIT_PROFILE') {
|
|
17
|
+
// Example: Fleetbo.exec('EditProfileModule', 'open', {});
|
|
18
|
+
//}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}, []);
|
|
22
|
+
return (<> <PageConfig navbar={navMode} /> </>);
|
|
23
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
const navItems = [
|
|
5
|
+
{ id: 'Tab1', view: 'tab1', label: 'Tab 1' },
|
|
6
|
+
{ id: 'Tab2', view: 'tab2', label: 'Tab 2' },
|
|
7
|
+
{ id: 'Tab3', view: 'tab3', label: 'Tab 3' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const getNavbarType = () => {
|
|
11
|
+
const params = new URLSearchParams(window.location.search);
|
|
12
|
+
if (params.get('type') === 'header') return 'header';
|
|
13
|
+
if (params.get('type') === 'footer') return 'footer';
|
|
14
|
+
const savedType = localStorage.getItem("navbar");
|
|
15
|
+
return savedType || 'footer';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getInitialTab = () => {
|
|
19
|
+
const params = new URLSearchParams(window.location.search);
|
|
20
|
+
const activeRoute = params.get('activeRoute');
|
|
21
|
+
if (activeRoute) {
|
|
22
|
+
const matchedTab = navItems.find(item => !item.isNative && activeRoute.includes(`/${item.view}`));
|
|
23
|
+
if (matchedTab) return matchedTab.id;
|
|
24
|
+
}
|
|
25
|
+
return localStorage.getItem("activeTab") || 'Tab1';
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const Welcome = () => {
|
|
29
|
+
const location = useLocation();
|
|
30
|
+
|
|
31
|
+
const [navbarType, setNavbarType] = useState(getNavbarType);
|
|
32
|
+
const [activeTab, setActiveTab] = useState(getInitialTab);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleMessage = (event) => {
|
|
36
|
+
if (!event.data) return;
|
|
37
|
+
const { type, route: eventRoute, activeTab: msgActiveTab, navbarMode } = event.data;
|
|
38
|
+
|
|
39
|
+
// Reset navbar
|
|
40
|
+
if (type === 'FLEETBO_FORCE_LOGOUT') {
|
|
41
|
+
setActiveTab('Tab1');
|
|
42
|
+
localStorage.setItem("activeTab", 'Tab1');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (type === 'FLEETBO_UPDATE_ACTIVE_TAB' && msgActiveTab) {
|
|
47
|
+
setActiveTab(msgActiveTab);
|
|
48
|
+
localStorage.setItem("activeTab", msgActiveTab);
|
|
49
|
+
} else if (type === 'FLEETBO_NAVIGATE_INTERNAL' && eventRoute) {
|
|
50
|
+
const matchedTab = navItems.find(item => !item.isNative && eventRoute.includes(`/${item.view}`));
|
|
51
|
+
if (matchedTab) {
|
|
52
|
+
setActiveTab(matchedTab.id);
|
|
53
|
+
localStorage.setItem("activeTab", matchedTab.id);
|
|
54
|
+
}
|
|
55
|
+
} else if (type === 'SET_NAVBAR_TYPE' && navbarMode) {
|
|
56
|
+
setNavbarType(navbarMode);
|
|
57
|
+
localStorage.setItem("navbar", navbarMode);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleStorageChange = (e) => {
|
|
62
|
+
if (e.key === 'activeTab') {
|
|
63
|
+
setActiveTab(e.newValue || 'Tab1');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (e.key === null && !localStorage.getItem('activeTab')) {
|
|
67
|
+
setActiveTab('Tab1');
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
window.addEventListener('message', handleMessage);
|
|
72
|
+
window.addEventListener('storage', handleStorageChange);
|
|
73
|
+
|
|
74
|
+
if (window.top !== window.self) {
|
|
75
|
+
window.top.postMessage({ type: 'FLEETBO_REQUEST_ENGINE' }, '*');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
window.removeEventListener('message', handleMessage);
|
|
80
|
+
window.removeEventListener('storage', handleStorageChange);
|
|
81
|
+
};
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
const handleTabClick = (item) => {
|
|
85
|
+
setActiveTab(item.id);
|
|
86
|
+
|
|
87
|
+
localStorage.setItem("activeTab", item.id);
|
|
88
|
+
localStorage.setItem("activeTabView", item.view);
|
|
89
|
+
localStorage.setItem("activeTabNative", item.isNative ? "true" : "false");
|
|
90
|
+
|
|
91
|
+
if (window.Fleetbo) {
|
|
92
|
+
window.Fleetbo.openView(item.view, !!item.isNative);
|
|
93
|
+
} else {
|
|
94
|
+
console.warn("Fleetbo engine not found");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const getIconClass = (id, isActive) => {
|
|
99
|
+
if (id === 'Tab1') return isActive ? 'fa-solid fa-house' : 'fa-solid fa-house';
|
|
100
|
+
if (id === 'Tab2') return isActive ? 'fa-solid fa-compass' : 'fa-regular fa-compass';
|
|
101
|
+
if (id === 'Tab3') return isActive ? 'fa-solid fa-user' : 'fa-regular fa-user';
|
|
102
|
+
return 'fa-solid fa-circle';
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ==========================================
|
|
106
|
+
// STYLES INLINE
|
|
107
|
+
// ==========================================
|
|
108
|
+
const containerStyle = {
|
|
109
|
+
position: 'fixed',
|
|
110
|
+
left: 0,
|
|
111
|
+
right: 0,
|
|
112
|
+
height: '70px',
|
|
113
|
+
paddingBottom: 'env(safe-area-inset-bottom)',
|
|
114
|
+
boxSizing: 'border-box',
|
|
115
|
+
display: 'flex',
|
|
116
|
+
justifyContent: 'space-around',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
backgroundColor: 'rgba(247, 246, 246, 0.95)',
|
|
119
|
+
zIndex: 1000,
|
|
120
|
+
...(navbarType === 'header'
|
|
121
|
+
? { top: 0, borderBottom: '1px solid #eee' }
|
|
122
|
+
: { bottom: 0, borderTop: '1px solid #eee' }
|
|
123
|
+
)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const baseStyles = {
|
|
127
|
+
button: {
|
|
128
|
+
flex: 1,
|
|
129
|
+
background: 'transparent',
|
|
130
|
+
border: 'none',
|
|
131
|
+
padding: '8px',
|
|
132
|
+
cursor: 'default',
|
|
133
|
+
display: 'flex',
|
|
134
|
+
flexDirection: 'column',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
justifyContent: 'center',
|
|
137
|
+
color: '#999',
|
|
138
|
+
},
|
|
139
|
+
activeButton: {
|
|
140
|
+
color: '#0E904D',
|
|
141
|
+
fontWeight: 'bold'
|
|
142
|
+
},
|
|
143
|
+
label: {
|
|
144
|
+
fontSize: '12px',
|
|
145
|
+
marginTop: '4px'
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div style={containerStyle}>
|
|
151
|
+
{navItems.map((item) => {
|
|
152
|
+
const isActive = activeTab === item.id;
|
|
153
|
+
return (
|
|
154
|
+
<button
|
|
155
|
+
key={item.id}
|
|
156
|
+
onClick={() => handleTabClick(item)}
|
|
157
|
+
style={{
|
|
158
|
+
...baseStyles.button,
|
|
159
|
+
...(isActive ? baseStyles.activeButton : {})
|
|
160
|
+
}}
|
|
161
|
+
>
|
|
162
|
+
<i
|
|
163
|
+
className={getIconClass(item.id, isActive)}
|
|
164
|
+
style={{ fontSize: '20px', marginBottom: '2px' }}
|
|
165
|
+
></i>
|
|
166
|
+
<span style={baseStyles.label}>{item.label}</span>
|
|
167
|
+
</button>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default Welcome;
|
package/src/main.jsx
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { HashRouter } from 'react-router-dom';
|
|
4
|
+
import './app/assets/css/App.css';
|
|
5
|
+
import App from './App';
|
|
6
|
+
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
8
|
+
<HashRouter>
|
|
9
|
+
<App />
|
|
10
|
+
</HashRouter>
|
|
11
|
+
);
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react({
|
|
7
|
+
include: '**/*.{jsx,js}',
|
|
8
|
+
})],
|
|
9
|
+
|
|
10
|
+
base: './',
|
|
11
|
+
|
|
12
|
+
define: {
|
|
13
|
+
'process.env.VITE_FLEETBO_ENTERPRISE_ID': JSON.stringify(process.env.VITE_FLEETBO_ENTERPRISE_ID),
|
|
14
|
+
'process.env.VITE_FLEETBO_KEY_APP': JSON.stringify(process.env.VITE_FLEETBO_KEY_APP),
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
resolve: {
|
|
18
|
+
alias: {
|
|
19
|
+
'@fleetbo': path.resolve(__dirname, 'src/@fleetbo'),
|
|
20
|
+
'app': path.resolve(__dirname, './src/app'),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
server: {
|
|
25
|
+
port: 3000,
|
|
26
|
+
host: '0.0.0.0',
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
build: {
|
|
30
|
+
outDir: 'build',
|
|
31
|
+
},
|
|
32
|
+
});
|