Files
Eric FELIXINE e30ae8ed09 feat(smart-app): implement complete mobile app MVP
- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
2026-06-01 18:00:35 -04:00

119 lines
3.7 KiB
JavaScript

import * as React from 'react';
import { captureImageData } from './WebCameraUtils';
const qrWorkerMethod = ({ data, width, height }) => {
// eslint-disable-next-line no-undef
const decoded = self.jsQR(data, width, height, {
inversionAttempts: 'attemptBoth',
});
let parsed;
try {
parsed = JSON.parse(decoded);
}
catch {
parsed = decoded;
}
if (parsed?.data) {
const nativeEvent = {
type: 'qr',
data: parsed.data,
cornerPoints: [],
bounds: { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } },
};
if (parsed.location) {
nativeEvent.cornerPoints = [
parsed.location.topLeftCorner,
parsed.location.bottomLeftCorner,
parsed.location.topRightCorner,
parsed.location.bottomRightCorner,
];
}
return nativeEvent;
}
return parsed;
};
const createWorkerAsyncFunction = (fn, deps) => {
if (typeof window === 'undefined') {
return async () => {
throw new Error('Cannot use createWorkerAsyncFunction in a non-browser environment');
};
}
const stringifiedFn = [
`self.func = ${fn.toString()};`,
'self.onmessage = (e) => {',
' const result = self.func(e.data);',
' self.postMessage(result);',
'};',
];
if (deps.length > 0) {
stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);
}
const blob = new Blob(stringifiedFn, { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
// First-In First-Out queue of promises
const promises = [];
worker.onmessage = (e) => promises.shift()?.resolve(e.data);
return (data) => {
return new Promise((resolve, reject) => {
promises.push({ resolve, reject });
worker.postMessage(data);
});
};
};
const decode = createWorkerAsyncFunction(qrWorkerMethod, [
'https://cdn.jsdelivr.net/npm/jsqr@1.2.0/dist/jsQR.min.js',
]);
export function useWebQRScanner(video, { isEnabled, captureOptions, interval, onScanned, onError, }) {
const isRunning = React.useRef(false);
const timeout = React.useRef(undefined);
async function scanAsync() {
// If interval is 0 then only scan once.
if (!isRunning.current || !onScanned) {
stop();
return;
}
try {
const data = captureImageData(video.current, captureOptions);
if (data) {
const nativeEvent = await decode(data);
if (nativeEvent?.data) {
onScanned({
nativeEvent,
});
}
}
}
catch (error) {
if (onError) {
onError({ nativeEvent: error });
}
}
finally {
// If interval is 0 then only scan once.
if (interval === 0) {
stop();
return;
}
const intervalToUse = !interval || interval < 0 ? 16 : interval;
// @ts-ignore: Type 'Timeout' is not assignable to type 'number'
timeout.current = setTimeout(() => {
scanAsync();
}, intervalToUse);
}
}
function stop() {
isRunning.current = false;
clearTimeout(timeout.current);
}
React.useEffect(() => {
if (isEnabled) {
isRunning.current = true;
scanAsync();
}
return () => {
if (isEnabled) {
stop();
}
};
}, [isEnabled]);
}
//# sourceMappingURL=useWebQRScanner.js.map