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,228 @@
'use strict';
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import { isChromeDebugger, isJest, isWeb, isWindowAvailable } from '../PlatformChecker';
import { SensorType } from '../commonTypes';
import { mockedRequestAnimationFrame } from '../mockedRequestAnimationFrame';
// In Node.js environments (like when static rendering with Expo Router)
// requestAnimationFrame is unavailable, so we use our mock.
// It also has to be mocked for Jest purposes (see `initializeUIRuntime`).
const requestAnimationFrameImpl = isJest() || !globalThis.requestAnimationFrame ? mockedRequestAnimationFrame : globalThis.requestAnimationFrame;
export default class JSReanimated {
constructor() {
_defineProperty(this, "nextSensorId", 0);
_defineProperty(this, "sensors", new Map());
_defineProperty(this, "platform", undefined);
_defineProperty(this, "getSensorCallback", (sensor, sensorType, eventHandler) => {
switch (sensorType) {
case SensorType.ACCELEROMETER:
case SensorType.GRAVITY:
return () => {
let {
x,
y,
z
} = sensor;
// Web Android sensors have a different coordinate system than iOS
if (this.platform === Platform.WEB_ANDROID) {
[x, y, z] = [-x, -y, -z];
}
// TODO TYPESCRIPT on web ShareableRef is the value itself so we call it directly
eventHandler({
x,
y,
z,
interfaceOrientation: 0
});
};
case SensorType.GYROSCOPE:
case SensorType.MAGNETIC_FIELD:
return () => {
const {
x,
y,
z
} = sensor;
// TODO TYPESCRIPT on web ShareableRef is the value itself so we call it directly
eventHandler({
x,
y,
z,
interfaceOrientation: 0
});
};
case SensorType.ROTATION:
return () => {
let [qw, qx, qy, qz] = sensor.quaternion;
// Android sensors have a different coordinate system than iOS
if (this.platform === Platform.WEB_ANDROID) {
[qy, qz] = [qz, -qy];
}
// reference: https://stackoverflow.com/questions/5782658/extracting-yaw-from-a-quaternion
const yaw = -Math.atan2(2.0 * (qy * qz + qw * qx), qw * qw - qx * qx - qy * qy + qz * qz);
const pitch = Math.sin(-2.0 * (qx * qz - qw * qy));
const roll = -Math.atan2(2.0 * (qx * qy + qw * qz), qw * qw + qx * qx - qy * qy - qz * qz);
// TODO TYPESCRIPT on web ShareableRef is the value itself so we call it directly
eventHandler({
qw,
qx,
qy,
qz,
yaw,
pitch,
roll,
interfaceOrientation: 0
});
};
}
});
}
makeShareableClone() {
throw new Error('[Reanimated] makeShareableClone should never be called in JSReanimated.');
}
scheduleOnUI(worklet) {
// @ts-ignore web implementation has still not been updated after the rewrite, this will be addressed once the web implementation updates are ready
requestAnimationFrameImpl(worklet);
}
createWorkletRuntime(_name, _initializer) {
throw new Error('[Reanimated] createWorkletRuntime is not available in JSReanimated.');
}
scheduleOnRuntime() {
throw new Error('[Reanimated] scheduleOnRuntime is not available in JSReanimated.');
}
registerEventHandler(_eventHandler, _eventName, _emitterReactTag) {
throw new Error('[Reanimated] registerEventHandler is not available in JSReanimated.');
}
unregisterEventHandler(_) {
throw new Error('[Reanimated] unregisterEventHandler is not available in JSReanimated.');
}
enableLayoutAnimations() {
if (isWeb()) {
console.warn('[Reanimated] Layout Animations are not supported on web yet.');
} else if (isJest()) {
console.warn('[Reanimated] Layout Animations are no-ops when using Jest.');
} else if (isChromeDebugger()) {
console.warn('[Reanimated] Layout Animations are no-ops when using Chrome Debugger.');
} else {
console.warn('[Reanimated] Layout Animations are not supported on this configuration.');
}
}
configureLayoutAnimationBatch() {
// no-op
}
setShouldAnimateExitingForTag() {
// no-op
}
registerSensor(sensorType, interval, _iosReferenceFrame, eventHandler) {
if (!isWindowAvailable()) {
// the window object is unavailable when building the server portion of a site that uses SSG
// this check is here to ensure that the server build won't fail
return -1;
}
if (this.platform === undefined) {
this.detectPlatform();
}
if (!(this.getSensorName(sensorType) in window)) {
// https://w3c.github.io/sensors/#secure-context
console.warn('[Reanimated] Sensor is not available.' + (isWeb() && location.protocol !== 'https:' ? ' Make sure you use secure origin with `npx expo start --web --https`.' : '') + (this.platform === Platform.WEB_IOS ? ' For iOS web, you will also have to also grant permission in the browser: https://dev.to/li/how-to-requestpermission-for-devicemotion-and-deviceorientation-events-in-ios-13-46g2.' : ''));
return -1;
}
if (this.platform === undefined) {
this.detectPlatform();
}
const sensor = this.initializeSensor(sensorType, interval);
sensor.addEventListener('reading', this.getSensorCallback(sensor, sensorType, eventHandler));
sensor.start();
this.sensors.set(this.nextSensorId, sensor);
return this.nextSensorId++;
}
unregisterSensor(id) {
const sensor = this.sensors.get(id);
if (sensor !== undefined) {
sensor.stop();
this.sensors.delete(id);
}
}
subscribeForKeyboardEvents(_) {
if (isWeb()) {
console.warn('[Reanimated] useAnimatedKeyboard is not available on web yet.');
} else if (isJest()) {
console.warn('[Reanimated] useAnimatedKeyboard is not available when using Jest.');
} else if (isChromeDebugger()) {
console.warn('[Reanimated] useAnimatedKeyboard is not available when using Chrome Debugger.');
} else {
console.warn('[Reanimated] useAnimatedKeyboard is not available on this configuration.');
}
return -1;
}
unsubscribeFromKeyboardEvents(_) {
// noop
}
initializeSensor(sensorType, interval) {
const config = interval <= 0 ? {
referenceFrame: 'device'
} : {
frequency: 1000 / interval
};
switch (sensorType) {
case SensorType.ACCELEROMETER:
return new window.Accelerometer(config);
case SensorType.GYROSCOPE:
return new window.Gyroscope(config);
case SensorType.GRAVITY:
return new window.GravitySensor(config);
case SensorType.MAGNETIC_FIELD:
return new window.Magnetometer(config);
case SensorType.ROTATION:
return new window.AbsoluteOrientationSensor(config);
}
}
getSensorName(sensorType) {
switch (sensorType) {
case SensorType.ACCELEROMETER:
return 'Accelerometer';
case SensorType.GRAVITY:
return 'GravitySensor';
case SensorType.GYROSCOPE:
return 'Gyroscope';
case SensorType.MAGNETIC_FIELD:
return 'Magnetometer';
case SensorType.ROTATION:
return 'AbsoluteOrientationSensor';
}
}
detectPlatform() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (userAgent === undefined) {
this.platform = Platform.UNKNOWN;
} else if (/iPad|iPhone|iPod/.test(userAgent)) {
this.platform = Platform.WEB_IOS;
} else if (/android/i.test(userAgent)) {
this.platform = Platform.WEB_ANDROID;
} else {
this.platform = Platform.WEB;
}
}
getViewProp(_viewTag, _propName, _component, _callback) {
throw new Error('[Reanimated] getViewProp is not available in JSReanimated.');
}
configureProps() {
throw new Error('[Reanimated] configureProps is not available in JSReanimated.');
}
executeOnUIRuntimeSync(_shareable) {
throw new Error('[Reanimated] `executeOnUIRuntimeSync` is not available in JSReanimated.');
}
}
var Platform = /*#__PURE__*/function (Platform) {
Platform["WEB_IOS"] = "web iOS";
Platform["WEB_ANDROID"] = "web Android";
Platform["WEB"] = "web";
Platform["UNKNOWN"] = "unknown";
return Platform;
}(Platform || {});
//# sourceMappingURL=JSReanimated.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
'use strict';
//# sourceMappingURL=WebSensor.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":[],"sources":["WebSensor.ts"],"sourcesContent":["'use strict';\nexport declare class WebSensor {\n start: () => void;\n stop: () => void;\n addEventListener: (eventType: string, eventHandler: () => void) => void;\n quaternion: [number, number, number, number];\n x: number;\n y: number;\n z: number;\n}\n\ntype configOptions =\n | {\n referenceFrame: string;\n frequency?: undefined;\n }\n | {\n frequency: number;\n referenceFrame?: undefined;\n };\n\ninterface Constructable<T> {\n new (config: configOptions): T;\n}\n\ndeclare global {\n interface Window {\n Accelerometer: Constructable<WebSensor>;\n GravitySensor: Constructable<WebSensor>;\n Gyroscope: Constructable<WebSensor>;\n Magnetometer: Constructable<WebSensor>;\n AbsoluteOrientationSensor: Constructable<WebSensor>;\n Sensor: Constructable<WebSensor>;\n opera?: string;\n }\n}\n"],"mappings":"AAAA,YAAY","ignoreList":[]}

View File

@@ -0,0 +1,127 @@
'use strict';
import JSReanimated from './JSReanimated';
import { isWeb } from '../PlatformChecker';
import { PropsAllowlists } from '../../propsAllowlists';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createReactDOMStyle;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createTransformValue;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let createTextShadowValue;
if (isWeb()) {
try {
createReactDOMStyle =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/compiler/createReactDOMStyle').default;
} catch (e) {}
try {
// React Native Web 0.19+
createTransformValue =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/preprocess').createTransformValue;
} catch (e) {}
try {
createTextShadowValue =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('react-native-web/dist/exports/StyleSheet/preprocess').createTextShadowValue;
} catch (e) {}
}
const reanimatedJS = new JSReanimated();
global._makeShareableClone = () => {
throw new Error('[Reanimated] _makeShareableClone should never be called in JSReanimated.');
};
global._scheduleOnJS = () => {
throw new Error('[Reanimated] _scheduleOnJS should never be called in JSReanimated.');
};
global._scheduleOnRuntime = () => {
throw new Error('[Reanimated] _scheduleOnRuntime should never be called in JSReanimated.');
};
export const _updatePropsJS = (updates, viewRef, isAnimatedProps) => {
if (viewRef._component) {
const component = viewRef._component;
const [rawStyles] = Object.keys(updates).reduce((acc, key) => {
const value = updates[key];
const index = typeof value === 'function' ? 1 : 0;
acc[index][key] = value;
return acc;
}, [{}, {}]);
if (typeof component.setNativeProps === 'function') {
// This is the legacy way to update props on React Native Web <= 0.18.
// Also, some components (e.g. from react-native-svg) don't have styles
// and always provide setNativeProps function instead (even on React Native Web 0.19+).
setNativeProps(component, rawStyles, isAnimatedProps);
} else if (createReactDOMStyle !== undefined && component.style !== undefined) {
// React Native Web 0.19+ no longer provides setNativeProps function,
// so we need to update DOM nodes directly.
updatePropsDOM(component, rawStyles, isAnimatedProps);
} else if (Object.keys(component.props).length > 0) {
Object.keys(component.props).forEach(key => {
if (!rawStyles[key]) {
return;
}
const dashedKey = key.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
component._touchableNode.setAttribute(dashedKey, rawStyles[key]);
});
} else {
const componentName = 'className' in component ? component === null || component === void 0 ? void 0 : component.className : '';
console.warn(`[Reanimated] It's not possible to manipulate the component ${componentName}`);
}
}
};
const setNativeProps = (component, newProps, isAnimatedProps) => {
var _component$setNativeP2;
if (isAnimatedProps) {
var _component$setNativeP;
const uiProps = {};
for (const key in newProps) {
if (isNativeProp(key)) {
uiProps[key] = newProps[key];
}
}
// Only update UI props directly on the component,
// other props can be updated as standard style props.
(_component$setNativeP = component.setNativeProps) === null || _component$setNativeP === void 0 || _component$setNativeP.call(component, uiProps);
}
const previousStyle = component.previousStyle ? component.previousStyle : {};
const currentStyle = {
...previousStyle,
...newProps
};
component.previousStyle = currentStyle;
(_component$setNativeP2 = component.setNativeProps) === null || _component$setNativeP2 === void 0 || _component$setNativeP2.call(component, {
style: currentStyle
});
};
const updatePropsDOM = (component, style, isAnimatedProps) => {
const previousStyle = component.previousStyle ? component.previousStyle : {};
const currentStyle = {
...previousStyle,
...style
};
component.previousStyle = currentStyle;
const domStyle = createReactDOMStyle(currentStyle);
if (Array.isArray(domStyle.transform) && createTransformValue !== undefined) {
domStyle.transform = createTransformValue(domStyle.transform);
}
if (createTextShadowValue !== undefined && (domStyle.textShadowColor || domStyle.textShadowRadius || domStyle.textShadowOffset)) {
domStyle.textShadow = createTextShadowValue({
textShadowColor: domStyle.textShadowColor,
textShadowOffset: domStyle.textShadowOffset,
textShadowRadius: domStyle.textShadowRadius
});
}
for (const key in domStyle) {
if (isAnimatedProps) {
component.setAttribute(key, domStyle[key]);
} else {
component.style[key] = domStyle[key];
}
}
};
function isNativeProp(propName) {
return !!PropsAllowlists.NATIVE_THREAD_PROPS_WHITELIST[propName];
}
export default reanimatedJS;
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
'use strict';
//# sourceMappingURL=react-native-web.d.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":[],"sources":["react-native-web.d.ts"],"sourcesContent":["'use strict';\ndeclare module 'react-native-web/dist/exports/StyleSheet/compiler/createReactDOMStyle';\ndeclare module 'react-native-web/dist/exports/StyleSheet/preprocess';\n"],"mappings":"AAAA,YAAY","ignoreList":[]}