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
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,120 @@
/* eslint-env browser */
/**
* A web-only module for ponyfilling the UserMedia API.
*/
import { Platform } from 'expo-modules-core';
export const userMediaRequested = false;
export const mountedInstances = [];
async function requestLegacyUserMediaAsync(props) {
const optionalSource = (id) => ({ optional: [{ sourceId: id }] });
const constraintToSourceId = (constraint) => {
const { deviceId } = constraint;
if (typeof deviceId === 'string') {
return deviceId;
}
if (Array.isArray(deviceId) && deviceId.length > 0) {
return deviceId[0];
}
if (typeof deviceId === 'object' && deviceId.ideal) {
return deviceId.ideal;
}
return null;
};
const sources = await new Promise((resolve) =>
// @ts-ignore: https://caniuse.com/#search=getSources Chrome for Android (78) & Samsung Internet (10.1) use this
MediaStreamTrack.getSources((sources) => resolve(sources)));
let audioSource = null;
let videoSource = null;
sources.forEach((source) => {
if (source.kind === 'audio') {
audioSource = source.id;
}
else if (source.kind === 'video') {
videoSource = source.id;
}
});
const audioSourceId = constraintToSourceId(props.audioConstraints);
if (audioSourceId) {
audioSource = audioSourceId;
}
const videoSourceId = constraintToSourceId(props.videoConstraints);
if (videoSourceId) {
videoSource = videoSourceId;
}
return [optionalSource(audioSource), optionalSource(videoSource)];
}
async function sourceSelectedAsync(isMuted, audioConstraints, videoConstraints) {
const constraints = {
video: typeof videoConstraints !== 'undefined' ? videoConstraints : true,
};
if (!isMuted) {
constraints.audio = typeof audioConstraints !== 'undefined' ? audioConstraints : true;
}
return await getAnyUserMediaAsync(constraints);
}
export async function requestUserMediaAsync(props, isMuted = true) {
if (canGetUserMedia()) {
return await sourceSelectedAsync(isMuted, props.audio, props.video);
}
const [audio, video] = await requestLegacyUserMediaAsync(props);
return await sourceSelectedAsync(isMuted, audio, video);
}
export async function getAnyUserMediaAsync(constraints, ignoreConstraints = false) {
try {
return await getUserMediaAsync({
...constraints,
video: ignoreConstraints || constraints.video,
});
}
catch (error) {
if (!ignoreConstraints && error.name === 'ConstraintNotSatisfiedError') {
return await getAnyUserMediaAsync(constraints, true);
}
throw error;
}
}
export async function getUserMediaAsync(constraints) {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(constraints);
}
const _getUserMedia = navigator['mozGetUserMedia'] || navigator['webkitGetUserMedia'] || navigator['msGetUserMedia'];
return new Promise((resolve, reject) => _getUserMedia.call(navigator, constraints, resolve, reject));
}
export function canGetUserMedia() {
return (
// SSR
Platform.isDOMAvailable &&
// Has any form of media API
!!((navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ||
navigator['mozGetUserMedia'] ||
navigator['webkitGetUserMedia'] ||
navigator['msGetUserMedia']));
}
export async function isFrontCameraAvailableAsync(devices) {
return await supportsCameraType(['front', 'user', 'facetime'], 'user', devices);
}
export async function isBackCameraAvailableAsync(devices) {
return await supportsCameraType(['back', 'rear'], 'environment', devices);
}
async function supportsCameraType(labels, type, devices) {
if (!devices) {
if (!navigator.mediaDevices.enumerateDevices) {
return null;
}
devices = await navigator.mediaDevices.enumerateDevices();
}
const cameras = devices.filter((t) => t.kind === 'videoinput');
const [hasCamera] = cameras.filter((camera) => labels.some((label) => camera.label.toLowerCase().includes(label)));
const [isCapable] = cameras.filter((camera) => {
if (!('getCapabilities' in camera)) {
return null;
}
const capabilities = camera.getCapabilities();
if (!capabilities.facingMode) {
return null;
}
return capabilities.facingMode.find((_) => type);
});
return isCapable?.deviceId || hasCamera?.deviceId || null;
}
//# sourceMappingURL=WebUserMediaManager.js.map