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,18 @@
|
|
|
1
|
+
//src/hooks/useLoadingTimeout
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
export const useLoadingTimeout = (loadingState, setLoadingState, setErrorState, timeoutMs = 15000) => {
|
|
6
|
+
React.useEffect(() => {
|
|
7
|
+
if (!loadingState) return;
|
|
8
|
+
|
|
9
|
+
const failsafeTimeout = setTimeout(() => {
|
|
10
|
+
if (loadingState) {
|
|
11
|
+
setLoadingState(false);
|
|
12
|
+
setErrorState("Délai d'attente dépassé. Veuillez réessayer.");
|
|
13
|
+
}
|
|
14
|
+
}, timeoutMs);
|
|
15
|
+
|
|
16
|
+
return () => clearTimeout(failsafeTimeout);
|
|
17
|
+
}, [loadingState, setLoadingState, setErrorState, timeoutMs]);
|
|
18
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import Fleetbo from 'api/fleetbo';
|
|
3
|
+
const useModalLauncher = () => {
|
|
4
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const handleShowModal = () => {
|
|
7
|
+
setIsModalOpen(true);
|
|
8
|
+
};
|
|
9
|
+
window.addEventListener('showModalRequest', handleShowModal);
|
|
10
|
+
return () => {
|
|
11
|
+
window.removeEventListener('showModalRequest', handleShowModal);
|
|
12
|
+
};
|
|
13
|
+
}, []);
|
|
14
|
+
const closeModal = () => {
|
|
15
|
+
setIsModalOpen(false);
|
|
16
|
+
if (window.Fleetbo && window.Fleetbo.setNavbarVisible) {
|
|
17
|
+
Fleetbo.setNavbarVisible();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return { isModalOpen, closeModal };
|
|
21
|
+
};
|
|
22
|
+
export default useModalLauncher;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
export const useStartupEffect = () => {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
|
|
7
|
+
const isReadyRef = useRef(false);
|
|
8
|
+
const [isEngineReady, setIsEngineReady] = useState(false);
|
|
9
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
10
|
+
const [sessionData, setSessionData] = useState(null);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let timer = null;
|
|
15
|
+
let requester = null;
|
|
16
|
+
|
|
17
|
+
const cleanup = () => {
|
|
18
|
+
if (timer) clearTimeout(timer);
|
|
19
|
+
if (requester) clearInterval(requester);
|
|
20
|
+
window.removeEventListener('message', handleMessage);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const onFleetboReady = async () => {
|
|
24
|
+
if (isReadyRef.current) return;
|
|
25
|
+
isReadyRef.current = true;
|
|
26
|
+
cleanup();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const authData = await window.Fleetbo.checkAuthStatusAndRedirect();
|
|
30
|
+
if (authData) localStorage.setItem('fleetbo_session', JSON.stringify(authData));
|
|
31
|
+
|
|
32
|
+
setIsAuthenticated(!!(authData && authData.isLoggedIn));
|
|
33
|
+
setSessionData(authData);
|
|
34
|
+
setIsEngineReady(true);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
setError(err.message || "Authentication failed");
|
|
37
|
+
setIsEngineReady(true);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleMessage = (event) => {
|
|
42
|
+
if (event.data && event.data.type === 'FLEETBO_FORCE_LOGOUT') {
|
|
43
|
+
setIsAuthenticated(false);
|
|
44
|
+
setSessionData(null);
|
|
45
|
+
navigate('/login', { replace: true });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (event.data && event.data.type === 'FLEETBO_DELIVER_ENGINE') {
|
|
50
|
+
try {
|
|
51
|
+
if (!document.getElementById('fleetbo-native-engine')) {
|
|
52
|
+
const scriptEl = document.createElement('script');
|
|
53
|
+
scriptEl.id = 'fleetbo-native-engine';
|
|
54
|
+
scriptEl.innerHTML = event.data.code;
|
|
55
|
+
document.head.appendChild(scriptEl);
|
|
56
|
+
}
|
|
57
|
+
setTimeout(() => { if (window.Fleetbo) onFleetboReady(); }, 50);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
setError("Failed to execute the fleetbo engine.");
|
|
60
|
+
cleanup();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (window.Fleetbo && typeof window.Fleetbo.fleetboLog === 'function') {
|
|
66
|
+
if (window.Fleetbo) onFleetboReady();
|
|
67
|
+
else requester = setInterval(() => { if (window.Fleetbo) onFleetboReady(); }, 100);
|
|
68
|
+
}
|
|
69
|
+
else if (window.self !== window.top) {
|
|
70
|
+
window.addEventListener('message', handleMessage);
|
|
71
|
+
requester = setInterval(() => {
|
|
72
|
+
if (isReadyRef.current) { cleanup(); return; }
|
|
73
|
+
if (window.Fleetbo) { onFleetboReady(); return; }
|
|
74
|
+
if (window.parent) window.parent.postMessage({ type: 'FLEETBO_REQUEST_ENGINE' }, '*');
|
|
75
|
+
}, 500);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
if (!isReadyRef.current) setError("Running outside of Fleetbo Environment.");
|
|
80
|
+
}, 2000);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
timer = setTimeout(() => {
|
|
84
|
+
if (!isReadyRef.current) {
|
|
85
|
+
setError("Connection Timeout.");
|
|
86
|
+
cleanup();
|
|
87
|
+
}
|
|
88
|
+
}, 15000);
|
|
89
|
+
|
|
90
|
+
window.navigateToTab = (route) => navigate(route);
|
|
91
|
+
|
|
92
|
+
// Listener logout
|
|
93
|
+
const handleLogout = (event) => {
|
|
94
|
+
if (event.data?.type === 'FLEETBO_FORCE_LOGOUT') {
|
|
95
|
+
setIsAuthenticated(false);
|
|
96
|
+
setSessionData(null);
|
|
97
|
+
navigate('/login', { replace: true });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
window.addEventListener('message', handleLogout);
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
cleanup();
|
|
105
|
+
// Clear listener
|
|
106
|
+
window.removeEventListener('message', handleLogout);
|
|
107
|
+
};
|
|
108
|
+
}, [navigate]);
|
|
109
|
+
|
|
110
|
+
return { isEngineReady, isAuthenticated, sessionData, error };
|
|
111
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/@fleetbo/index.js
|
|
2
|
+
export { fleetboDB } from './config/fleetboConfig';
|
|
3
|
+
export { useLoadingTimeout } from './hooks/useLoadingTimeout';
|
|
4
|
+
export { useStartupEffect } from './hooks/useStartupEffect';
|
|
5
|
+
export { default as Loader } from './components/common/Loader';
|
|
6
|
+
export { default as PageConfig } from './components/common/PageConfig';
|
|
7
|
+
export { formatFirestoreDate } from './utils/FormatDate';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats various date structures, including Firestore Timestamps,
|
|
3
|
+
* into a readable English date string.
|
|
4
|
+
* @param {object|string|number} firestoreDate - The date object to format.
|
|
5
|
+
* @returns {string} The formatted date (e.g., "September 15, 2023") or an empty string on error.
|
|
6
|
+
*/
|
|
7
|
+
export const formatFirestoreDate = (firestoreDate) => {
|
|
8
|
+
try {
|
|
9
|
+
// Case 1: Firestore Timestamp with seconds and nanoseconds
|
|
10
|
+
if (firestoreDate && typeof firestoreDate === 'object' && firestoreDate.seconds) {
|
|
11
|
+
const date = new Date(firestoreDate.seconds * 1000);
|
|
12
|
+
const options = {
|
|
13
|
+
year: 'numeric',
|
|
14
|
+
month: 'long',
|
|
15
|
+
day: 'numeric'
|
|
16
|
+
};
|
|
17
|
+
return date.toLocaleDateString('en-US', options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Case 2: Firestore Timestamp processed on the Android side (already in milliseconds, often with a _ prefix)
|
|
21
|
+
if (firestoreDate && typeof firestoreDate === 'object' && firestoreDate._seconds) {
|
|
22
|
+
const date = new Date(firestoreDate._seconds * 1000);
|
|
23
|
+
return date.toLocaleDateString('en-US');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Case 3: Timestamp with nanoseconds property (alternative structure)
|
|
27
|
+
if (firestoreDate && typeof firestoreDate === 'object' && firestoreDate.nanoseconds !== undefined && firestoreDate.seconds !== undefined) {
|
|
28
|
+
const date = new Date(firestoreDate.seconds * 1000);
|
|
29
|
+
return date.toLocaleDateString('en-US');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Case 4: Date string
|
|
33
|
+
if (typeof firestoreDate === 'string') {
|
|
34
|
+
const date = new Date(firestoreDate);
|
|
35
|
+
// Check if the date is valid before formatting
|
|
36
|
+
return isNaN(date) ? "" : date.toLocaleDateString('en-US');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Case 5: Number (timestamp in milliseconds)
|
|
40
|
+
if (typeof firestoreDate === 'number') {
|
|
41
|
+
const date = new Date(firestoreDate);
|
|
42
|
+
return date.toLocaleDateString('en-US');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// If none of the cases match, return an empty string
|
|
46
|
+
return "";
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error formatting date:", error);
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles fetching the token from Fleetbo and displays it in an alert.
|
|
3
|
+
*/
|
|
4
|
+
export const handleGetToken = async () => {
|
|
5
|
+
try {
|
|
6
|
+
// Use the Fleetbo API Promise
|
|
7
|
+
const tokenResult = await Fleetbo.getToken();
|
|
8
|
+
alert("Token reçu : " + tokenResult.token);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
alert("Erreur lors de la récupération du token.");
|
|
11
|
+
}
|
|
12
|
+
};
|
package/src/App.jsx
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Routes, Route, Navigate, useLocation } from "react-router-dom";
|
|
3
|
+
import { useStartupEffect } from '@fleetbo/hooks/useStartupEffect';
|
|
4
|
+
|
|
5
|
+
// Application views
|
|
6
|
+
import Tab1 from "./app/tabs/Tab1";
|
|
7
|
+
import Tab2 from "./app/tabs/Tab2";
|
|
8
|
+
import Tab3 from "./app/tabs/Tab3";
|
|
9
|
+
import Welcome from "./app/tabs/Welcome";
|
|
10
|
+
import AuthRouter from "./app/core/AuthRouter";
|
|
11
|
+
import NotFound from './app/core/NotFound';
|
|
12
|
+
|
|
13
|
+
// FLEETBO_IMPORTS
|
|
14
|
+
import Login from "./app/mocks/Login";
|
|
15
|
+
import SampleTab1 from './app/mocks/SampleTab1';
|
|
16
|
+
import SampleTab2 from './app/mocks/SampleTab2';
|
|
17
|
+
import SampleTab3 from './app/mocks/SampleTab3';
|
|
18
|
+
// FLEETBO_MORE_IMPORTS
|
|
19
|
+
|
|
20
|
+
const TabRedirector = ({ children }) => {
|
|
21
|
+
const saved = localStorage.getItem("activeTab") || "Tab1";
|
|
22
|
+
if (saved === "Tab2") return <Navigate to="/tab2" replace />;
|
|
23
|
+
if (saved === "Tab3") return <Navigate to="/tab3" replace />;
|
|
24
|
+
return children;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const InitializingScreen = ({ error }) => (
|
|
28
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', flexDirection: 'column', padding: '20px', textAlign: 'center' }}>
|
|
29
|
+
{error ? (
|
|
30
|
+
<>
|
|
31
|
+
<i className="fa-solid fa-triangle-exclamation fa-3x text-danger mb-3"></i>
|
|
32
|
+
<h5 className="text-danger">System Halt</h5>
|
|
33
|
+
<p style={{ marginTop: '1rem', color: '#6c757d' }}>{error}</p>
|
|
34
|
+
<button className="btn btn-outline-secondary mt-3" onClick={() => window.location.reload()}>Reboot</button>
|
|
35
|
+
</>
|
|
36
|
+
) : (
|
|
37
|
+
<div className="spinner-border text-success" role="status">
|
|
38
|
+
<span className="visually-hidden">Booting OS...</span>
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function App() {
|
|
45
|
+
const { isEngineReady, isAuthenticated, sessionData, error } = useStartupEffect();
|
|
46
|
+
const [iframeAuthOverride, setIframeAuthOverride] = useState(false);
|
|
47
|
+
|
|
48
|
+
const location = useLocation();
|
|
49
|
+
const isNavbarRoute = location.pathname.includes('navbar') || location.hash.includes('navbar') || window.location.href.includes('navbar');
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (isNavbarRoute) {
|
|
53
|
+
const handleStorage = () => {
|
|
54
|
+
setIframeAuthOverride(true);
|
|
55
|
+
};
|
|
56
|
+
window.addEventListener('storage', handleStorage);
|
|
57
|
+
return () => window.removeEventListener('storage', handleStorage);
|
|
58
|
+
}
|
|
59
|
+
}, [isNavbarRoute]);
|
|
60
|
+
|
|
61
|
+
if (error) return <InitializingScreen error={error} />;
|
|
62
|
+
|
|
63
|
+
const isSimulator = !!window.__FLEETBO_SIMULATOR
|
|
64
|
+
|| window.location.hash.includes('simulator=1')
|
|
65
|
+
|| new URLSearchParams(window.location.search).get('simulator') === '1';
|
|
66
|
+
|
|
67
|
+
if (!isEngineReady && !isNavbarRoute && !isSimulator) {
|
|
68
|
+
return <InitializingScreen />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const isActuallyAuthenticated = isAuthenticated || iframeAuthOverride;
|
|
72
|
+
|
|
73
|
+
if (isNavbarRoute) {
|
|
74
|
+
return isActuallyAuthenticated ? <Welcome /> : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<div className="app-container">
|
|
79
|
+
<main className="main-content">
|
|
80
|
+
<Routes>
|
|
81
|
+
{!isAuthenticated ? (
|
|
82
|
+
<>
|
|
83
|
+
<Route path="/" element={<Login />} />
|
|
84
|
+
<Route path="/auth/route" element={<AuthRouter />} />
|
|
85
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
86
|
+
</>
|
|
87
|
+
) : (
|
|
88
|
+
<>
|
|
89
|
+
<Route path="/" element={
|
|
90
|
+
<TabRedirector>
|
|
91
|
+
<Tab1 sessionData={sessionData} />
|
|
92
|
+
</TabRedirector>
|
|
93
|
+
} />
|
|
94
|
+
|
|
95
|
+
<Route path="/tab1" element={
|
|
96
|
+
<TabRedirector>
|
|
97
|
+
<Tab1 sessionData={sessionData} />
|
|
98
|
+
</TabRedirector>
|
|
99
|
+
} />
|
|
100
|
+
|
|
101
|
+
<Route path="/tab2" element={<Tab2 />} />
|
|
102
|
+
<Route path="/tab3" element={<Tab3 />} />
|
|
103
|
+
|
|
104
|
+
{/* FLEETBO_ROUTES */}
|
|
105
|
+
<Route path="/mocks/sampletab1" element={<SampleTab1 />} />
|
|
106
|
+
<Route path="/mocks/sampletab2" element={<SampleTab2 />} />
|
|
107
|
+
<Route path="/mocks/sampletab3" element={<SampleTab3 />} />
|
|
108
|
+
{/* FLEETBO_DYNAMIC ROUTES */}
|
|
109
|
+
|
|
110
|
+
<Route path="*" element={<NotFound />} />
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</Routes>
|
|
114
|
+
</main>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export default App;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
html, body, #root {
|
|
3
|
+
height: 100vh;
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
border: none;
|
|
7
|
+
overflow-y: auto;
|
|
8
|
+
-webkit-overflow-scrolling: touch;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
* {
|
|
12
|
+
cursor: inherit !important;
|
|
13
|
+
-webkit-user-select: none;
|
|
14
|
+
-moz-user-select: none;
|
|
15
|
+
-ms-user-select: none;
|
|
16
|
+
user-select: none;
|
|
17
|
+
-webkit-tap-highlight-color: transparent;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
::-webkit-scrollbar { width: 0px; background: black; }
|
|
21
|
+
|
|
22
|
+
@media (hover: hover) {
|
|
23
|
+
.btn:hover, a:hover {
|
|
24
|
+
background-color: inherit !important;
|
|
25
|
+
color: inherit !important;
|
|
26
|
+
border-color: inherit !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.btn:active, a:active { opacity: 0.7; transform: scale(0.98); }
|
|
31
|
+
header { background: #fff; color: #0E904D; text-align: left; }
|
|
32
|
+
|
|
33
|
+
/* ============================================================
|
|
34
|
+
FLEETBO NATIVE RESET
|
|
35
|
+
============================================================ */
|
|
36
|
+
.btn {
|
|
37
|
+
--bs-btn-hover-color: var(--bs-btn-color);
|
|
38
|
+
--bs-btn-active-color: var(--bs-btn-color);
|
|
39
|
+
--bs-btn-hover-bg: var(--bs-btn-bg);
|
|
40
|
+
--bs-btn-active-bg: var(--bs-btn-bg);
|
|
41
|
+
--bs-btn-hover-border-color: var(--bs-btn-border-color);
|
|
42
|
+
--bs-btn-active-border-color: var(--bs-btn-border-color);
|
|
43
|
+
box-shadow: none !important;
|
|
44
|
+
text-decoration: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.btn:hover,
|
|
48
|
+
.btn:focus,
|
|
49
|
+
a:hover,
|
|
50
|
+
a:focus {
|
|
51
|
+
color: var(--bs-btn-color) !important;
|
|
52
|
+
background-color: var(--bs-btn-bg) !important;
|
|
53
|
+
border-color: var(--bs-btn-border-color) !important;
|
|
54
|
+
box-shadow: none !important;
|
|
55
|
+
outline: none !important;
|
|
56
|
+
text-decoration: none !important;
|
|
57
|
+
opacity: 1 !important;
|
|
58
|
+
transform: none !important;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.btn-link:hover,
|
|
62
|
+
.btn-link:focus,
|
|
63
|
+
.fb-button:hover,
|
|
64
|
+
.fb-button:focus {
|
|
65
|
+
background-color: transparent !important;
|
|
66
|
+
border-color: transparent !important;
|
|
67
|
+
color: inherit !important;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.btn:active,
|
|
71
|
+
a:active,
|
|
72
|
+
.card:active {
|
|
73
|
+
opacity: 0.6 !important;
|
|
74
|
+
transform: scale(0.97) !important;
|
|
75
|
+
transition: transform 0.05s ease, opacity 0.05s ease !important;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.form-control:focus,
|
|
79
|
+
input:focus,
|
|
80
|
+
textarea:focus {
|
|
81
|
+
box-shadow: none !important;
|
|
82
|
+
outline: none !important;
|
|
83
|
+
border-color: #86b7fe !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.read {
|
|
87
|
+
background: #86b7fe;
|
|
88
|
+
}
|
|
89
|
+
.btn:active, a:active { opacity: 0.7; transform: scale(0.98); }
|
|
90
|
+
.btn-header, .btn-delete { background: none; border: none; font-size: 18px; }
|
|
91
|
+
.fb-button-logout { background-color: transparent; color: #0E904D; border-color: transparent; margin-top: 1.2rem; }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* CSS for the Login Gateway (Login.jsx)
|
|
2
|
+
"Really clean" version: no card, no shadow, just centered elements.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/* 1. Main Container (Handles centering, white background) */
|
|
6
|
+
.login-passerelle-container {
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center; /* Center vertically */
|
|
9
|
+
justify-content: center; /* Center horizontally */
|
|
10
|
+
min-height: 100vh; /* Takes up 100% of screen height */
|
|
11
|
+
background-color: #ffffff; /* Native white background */
|
|
12
|
+
padding: 2.5rem; /* Padding applied to the container */
|
|
13
|
+
text-align: center; /* Centers logo and text */
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* 2. The Login "Box" (Now invisible) */
|
|
17
|
+
.login-passerelle-box {
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: 420px; /* Keeps content centered and readable */
|
|
20
|
+
/* This is now just a fixed-width container */
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* 4. Main Button Style */
|
|
24
|
+
.login-passerelle-box .btn-success {
|
|
25
|
+
font-weight: 550;
|
|
26
|
+
font-size: 1.1rem;
|
|
27
|
+
padding: 0.85rem 1rem;
|
|
28
|
+
border-radius: 8px; /* Rounded corners for the button */
|
|
29
|
+
border: none;
|
|
30
|
+
transition: all 0.3s ease;
|
|
31
|
+
width: 100%; /* Button takes full width */
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Hover effect for the button (subtler) */
|
|
35
|
+
.login-passerelle-box .btn-success:hover {
|
|
36
|
+
transform: translateY(-2px);
|
|
37
|
+
/* Use subtle box-shadow for flat look */
|
|
38
|
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* 5. Typography */
|
|
42
|
+
.login-passerelle-box h3 {
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
color: #212529;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.login-passerelle-box p.text-muted {
|
|
48
|
+
font-size: 1rem;
|
|
49
|
+
line-height: 1.6;
|
|
50
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* Form Container */
|
|
2
|
+
|
|
3
|
+
.form-container {
|
|
4
|
+
display: flex;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
align-items: center;
|
|
7
|
+
text-align: center;
|
|
8
|
+
height: 90vh;
|
|
9
|
+
overflow-x: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.form {
|
|
13
|
+
width: 100%;
|
|
14
|
+
padding: 3%;
|
|
15
|
+
background-color: #fff;
|
|
16
|
+
border-radius: 10px;
|
|
17
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
18
|
+
text-align: center;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Form Title */
|
|
22
|
+
.form-container h1 {
|
|
23
|
+
margin-bottom: 20px;
|
|
24
|
+
font-size: 24px;
|
|
25
|
+
color: #999f9e;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Form Group */
|
|
29
|
+
.form-group {
|
|
30
|
+
margin-bottom: 15px;
|
|
31
|
+
text-align: left;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Labels */
|
|
35
|
+
.form-group label {
|
|
36
|
+
display: block;
|
|
37
|
+
margin-bottom: 5px;
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
color: #666;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Input Fields */
|
|
43
|
+
.form-group input {
|
|
44
|
+
width: 100%;
|
|
45
|
+
padding: 10px;
|
|
46
|
+
font-size: 16px;
|
|
47
|
+
border: 1px solid #ddd;
|
|
48
|
+
border-radius: 5px;
|
|
49
|
+
outline: none;
|
|
50
|
+
transition: border-color 0.3s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.form-group input:focus {
|
|
54
|
+
border-color: #007bff;
|
|
55
|
+
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Submit Button */
|
|
59
|
+
button {
|
|
60
|
+
width: 100%;
|
|
61
|
+
padding: 12px;
|
|
62
|
+
font-size: 16px;
|
|
63
|
+
color: #fff;
|
|
64
|
+
background-color: #999f9e;
|
|
65
|
+
border: none;
|
|
66
|
+
border-radius: 5px;
|
|
67
|
+
cursor: pointer;
|
|
68
|
+
transition: background-color 0.3s ease;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
/* Footer Text */
|
|
73
|
+
.form-footer {
|
|
74
|
+
margin-top: 15px;
|
|
75
|
+
font-size: 14px;
|
|
76
|
+
color: #333;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.form-footer a {
|
|
80
|
+
color: #999f9e;
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.form-footer a:hover {
|
|
85
|
+
text-decoration: underline;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.label {
|
|
89
|
+
color: #414141;
|
|
90
|
+
float: left;
|
|
91
|
+
font-size: 17px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Mobile Optimization */
|
|
95
|
+
@media (max-width: 480px) {
|
|
96
|
+
.form-container {
|
|
97
|
+
padding: 15px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.form-container h1 {
|
|
101
|
+
font-size: 20px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.form-group input {
|
|
105
|
+
font-size: 14px;
|
|
106
|
+
padding: 8px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
button {
|
|
110
|
+
font-size: 14px;
|
|
111
|
+
padding: 10px;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import Loader from '@fleetbo/components/common/Loader';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This component acts as a router/switcher.
|
|
7
|
+
* It reads the state ('login' or 'welcome') stored by the native code,
|
|
8
|
+
* ensures that the navigation bar is hidden,
|
|
9
|
+
* and redirects the user to the appropriate page using React Router.
|
|
10
|
+
*/
|
|
11
|
+
const RouteAuth = () => {
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
|
|
16
|
+
const data = localStorage.getItem('AppInfo');
|
|
17
|
+
if (data) {
|
|
18
|
+
try {
|
|
19
|
+
const parsedData = JSON.parse(data);
|
|
20
|
+
if (parsedData.state === "welcome") {
|
|
21
|
+
navigate('/tab1', { replace: true });
|
|
22
|
+
} else {
|
|
23
|
+
navigate('/login', { replace: true });
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
navigate('/login', { replace: true });
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
navigate('/login', { replace: true });
|
|
30
|
+
}
|
|
31
|
+
}, [navigate]);
|
|
32
|
+
|
|
33
|
+
return <Loader />;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default RouteAuth;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/app/NotFound.jsx
|
|
2
|
+
import React from 'react';
|
|
3
|
+
const NotFound = () => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="d-flex justify-content-center align-items-center text-center vh-100 bg-light">
|
|
6
|
+
<div>
|
|
7
|
+
<h1 className="display-1 fw-bold text-secondary">404</h1>
|
|
8
|
+
<p className="lead text-muted">
|
|
9
|
+
Page not found.
|
|
10
|
+
</p>
|
|
11
|
+
<button onClick={() => Fleetbo.back()} className="btn btn-success w-100 mt-3">
|
|
12
|
+
Go Back
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
export default NotFound;
|