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,63 @@
'use strict';
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { forwardRef, useRef } from 'react';
import { FlatList } from 'react-native';
import { AnimatedView } from './View';
import { createAnimatedComponent } from '../../createAnimatedComponent';
import { LayoutAnimationConfig } from './LayoutAnimationConfig';
const AnimatedFlatList = createAnimatedComponent(FlatList);
const createCellRendererComponent = itemLayoutAnimationRef => {
const CellRendererComponent = props => {
return /*#__PURE__*/React.createElement(AnimatedView
// TODO TYPESCRIPT This is temporary cast is to get rid of .d.ts file.
, {
layout: itemLayoutAnimationRef === null || itemLayoutAnimationRef === void 0 ? void 0 : itemLayoutAnimationRef.current,
onLayout: props.onLayout,
style: props.style
}, props.children);
};
return CellRendererComponent;
};
// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,
// but not things like NativeMethods, etc. we need to add them manually by extending the type.
// We need explicit any here, because this is the exact same type that is used in React Native types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FlatListForwardRefRender = function (props, ref) {
const {
itemLayoutAnimation,
skipEnteringExitingAnimations,
...restProps
} = props;
// Set default scrollEventThrottle, because user expects
// to have continuous scroll events and
// react-native defaults it to 50 for FlatLists.
// We set it to 1, so we have peace until
// there are 960 fps screens.
if (!('scrollEventThrottle' in restProps)) {
restProps.scrollEventThrottle = 1;
}
const itemLayoutAnimationRef = useRef(itemLayoutAnimation);
itemLayoutAnimationRef.current = itemLayoutAnimation;
const CellRendererComponent = React.useMemo(() => createCellRendererComponent(itemLayoutAnimationRef), [itemLayoutAnimationRef]);
const animatedFlatList =
/*#__PURE__*/
// @ts-expect-error In its current type state, createAnimatedComponent cannot create generic components.
React.createElement(AnimatedFlatList, _extends({
ref: ref
}, restProps, {
CellRendererComponent: CellRendererComponent
}));
if (skipEnteringExitingAnimations === undefined) {
return animatedFlatList;
}
return /*#__PURE__*/React.createElement(LayoutAnimationConfig, {
skipEntering: true,
skipExiting: true
}, animatedFlatList);
};
export const ReanimatedFlatList = /*#__PURE__*/forwardRef(FlatListForwardRefRender);
//# sourceMappingURL=FlatList.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
'use strict';
import { Image } from 'react-native';
import { createAnimatedComponent } from '../../createAnimatedComponent';
// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,
// but not things like NativeMethods, etc. we need to add them manually by extending the type.
export const AnimatedImage = createAnimatedComponent(Image);
//# sourceMappingURL=Image.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["Image","createAnimatedComponent","AnimatedImage"],"sources":["Image.ts"],"sourcesContent":["'use strict';\nimport { Image } from 'react-native';\nimport { createAnimatedComponent } from '../../createAnimatedComponent';\n\n// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,\n// but not things like NativeMethods, etc. we need to add them manually by extending the type.\ninterface AnimatedImageComplement extends Image {\n getNode(): Image;\n}\n\nexport const AnimatedImage = createAnimatedComponent(Image);\n\nexport type AnimatedImage = typeof AnimatedImage & AnimatedImageComplement;\n"],"mappings":"AAAA,YAAY;;AACZ,SAASA,KAAK,QAAQ,cAAc;AACpC,SAASC,uBAAuB,QAAQ,+BAA+B;;AAEvE;AACA;;AAKA,OAAO,MAAMC,aAAa,GAAGD,uBAAuB,CAACD,KAAK,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,64 @@
'use strict';
import React, { Children, Component, createContext, useEffect, useRef } from 'react';
import { setShouldAnimateExitingForTag } from '../core';
import { findNodeHandle } from 'react-native';
export const SkipEnteringContext = /*#__PURE__*/createContext(null);
// skipEntering - don't animate entering of children on wrapper mount
// skipExiting - don't animate exiting of children on wrapper unmount
function SkipEntering(props) {
const skipValueRef = useRef(props.shouldSkip);
useEffect(() => {
skipValueRef.current = false;
}, [skipValueRef]);
return /*#__PURE__*/React.createElement(SkipEnteringContext.Provider, {
value: skipValueRef
}, props.children);
}
// skipExiting (unlike skipEntering) cannot be done by conditionally
// configuring the animation in `createAnimatedComponent`, since at this stage
// we don't know if the wrapper is going to be unmounted or not.
// That's why we need to pass the skipExiting flag to the native side
// when the wrapper is unmounted to prevent the animation.
// Since `ReactNode` can be a list of nodes, we wrap every child with our wrapper
// so we are able to access its tag with `findNodeHandle`.
/**
* A component that lets you skip entering and exiting animations.
*
* @param skipEntering - A boolean indicating whether children's entering animations should be skipped when `LayoutAnimationConfig` is mounted.
* @param skipExiting - A boolean indicating whether children's exiting animations should be skipped when LayoutAnimationConfig is unmounted.
* @see https://docs.swmansion.com/react-native-reanimated/docs/layout-animations/layout-animation-config/
*/
export class LayoutAnimationConfig extends Component {
getMaybeWrappedChildren() {
return Children.count(this.props.children) > 1 && this.props.skipExiting ? Children.map(this.props.children, child => /*#__PURE__*/React.createElement(LayoutAnimationConfig, {
skipExiting: true
}, child)) : this.props.children;
}
setShouldAnimateExiting() {
if (Children.count(this.props.children) === 1) {
const tag = findNodeHandle(this);
if (tag) {
setShouldAnimateExitingForTag(tag, !this.props.skipExiting);
}
}
}
componentWillUnmount() {
if (this.props.skipExiting !== undefined) {
this.setShouldAnimateExiting();
}
}
render() {
const children = this.getMaybeWrappedChildren();
if (this.props.skipEntering === undefined) {
return children;
}
return /*#__PURE__*/React.createElement(SkipEntering, {
shouldSkip: this.props.skipEntering
}, children);
}
}
//# sourceMappingURL=LayoutAnimationConfig.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,169 @@
'use strict';
import React, { useEffect, useRef } from 'react';
import { TextInput, StyleSheet, View } from 'react-native';
import { useSharedValue, useAnimatedProps, useFrameCallback } from '../hook';
import { createAnimatedComponent } from '../../createAnimatedComponent';
import { addWhitelistedNativeProps } from '../../ConfigHelper';
function createCircularDoublesBuffer(size) {
'worklet';
return {
next: 0,
buffer: new Float32Array(size),
size,
count: 0,
push(value) {
const oldValue = this.buffer[this.next];
const oldCount = this.count;
this.buffer[this.next] = value;
this.next = (this.next + 1) % this.size;
this.count = Math.min(this.size, this.count + 1);
return oldCount === this.size ? oldValue : null;
},
front() {
const notEmpty = this.count > 0;
if (notEmpty) {
const current = this.next - 1;
const index = current < 0 ? this.size - 1 : current;
return this.buffer[index];
}
return null;
},
back() {
const notEmpty = this.count > 0;
return notEmpty ? this.buffer[this.next] : null;
}
};
}
const DEFAULT_BUFFER_SIZE = 60;
addWhitelistedNativeProps({
text: true
});
const AnimatedTextInput = createAnimatedComponent(TextInput);
function loopAnimationFrame(fn) {
let lastTime = 0;
function loop() {
requestAnimationFrame(time => {
if (lastTime > 0) {
fn(lastTime, time);
}
lastTime = time;
requestAnimationFrame(loop);
});
}
loop();
}
function getFps(renderTimeInMs) {
'worklet';
return 1000 / renderTimeInMs;
}
function getTimeDelta(timestamp, previousTimestamp) {
'worklet';
return previousTimestamp !== null ? timestamp - previousTimestamp : 0;
}
function completeBufferRoutine(buffer, timestamp, previousTimestamp, totalRenderTime) {
'worklet';
timestamp = Math.round(timestamp);
previousTimestamp = Math.round(previousTimestamp) ?? timestamp;
const droppedTimestamp = buffer.push(timestamp);
const nextToDrop = buffer.back();
const delta = getTimeDelta(timestamp, previousTimestamp);
const droppedDelta = getTimeDelta(nextToDrop, droppedTimestamp);
totalRenderTime.value += delta - droppedDelta;
return getFps(totalRenderTime.value / buffer.count);
}
function JsPerformance() {
const jsFps = useSharedValue(null);
const totalRenderTime = useSharedValue(0);
const circularBuffer = useRef(createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE));
useEffect(() => {
loopAnimationFrame((_, timestamp) => {
timestamp = Math.round(timestamp);
const previousTimestamp = circularBuffer.current.front() ?? timestamp;
const currentFps = completeBufferRoutine(circularBuffer.current, timestamp, previousTimestamp, totalRenderTime);
// JS fps have to be measured every 2nd frame,
// thus 2x multiplication has to occur here
jsFps.value = (currentFps * 2).toFixed(0);
});
}, []);
const animatedProps = useAnimatedProps(() => {
const text = 'JS: ' + jsFps.value ?? 'N/A';
return {
text,
defaultValue: text
};
});
return /*#__PURE__*/React.createElement(View, {
style: styles.container
}, /*#__PURE__*/React.createElement(AnimatedTextInput, {
style: styles.text,
animatedProps: animatedProps,
editable: false
}));
}
function UiPerformance() {
const uiFps = useSharedValue(null);
const totalRenderTime = useSharedValue(0);
const circularBuffer = useSharedValue(null);
useFrameCallback(({
timestamp
}) => {
if (circularBuffer.value === null) {
circularBuffer.value = createCircularDoublesBuffer(DEFAULT_BUFFER_SIZE);
}
timestamp = Math.round(timestamp);
const previousTimestamp = circularBuffer.value.front() ?? timestamp;
const currentFps = completeBufferRoutine(circularBuffer.value, timestamp, previousTimestamp, totalRenderTime);
uiFps.value = currentFps.toFixed(0);
});
const animatedProps = useAnimatedProps(() => {
const text = 'UI: ' + uiFps.value ?? 'N/A';
return {
text,
defaultValue: text
};
});
return /*#__PURE__*/React.createElement(View, {
style: styles.container
}, /*#__PURE__*/React.createElement(AnimatedTextInput, {
style: styles.text,
animatedProps: animatedProps,
editable: false
}));
}
export function PerformanceMonitor() {
return /*#__PURE__*/React.createElement(View, {
style: styles.monitor
}, /*#__PURE__*/React.createElement(JsPerformance, null), /*#__PURE__*/React.createElement(UiPerformance, null));
}
const styles = StyleSheet.create({
monitor: {
flexDirection: 'row',
position: 'absolute',
backgroundColor: '#0006',
zIndex: 1000
},
header: {
fontSize: 14,
color: '#ffff',
paddingHorizontal: 5
},
text: {
fontSize: 13,
color: '#ffff',
fontFamily: 'monospace',
paddingHorizontal: 3
},
container: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
flexWrap: 'wrap'
}
});
//# sourceMappingURL=PerformanceMonitor.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
'use strict';
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import React, { forwardRef } from 'react';
import { ScrollView } from 'react-native';
import { createAnimatedComponent } from '../../createAnimatedComponent';
import { useAnimatedRef, useScrollViewOffset } from '../hook';
// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,
// but not things like NativeMethods, etc. we need to add them manually by extending the type.
const AnimatedScrollViewComponent = createAnimatedComponent(ScrollView);
export const AnimatedScrollView = /*#__PURE__*/forwardRef((props, ref) => {
const {
scrollViewOffset,
...restProps
} = props;
const animatedRef = ref === null ?
// eslint-disable-next-line react-hooks/rules-of-hooks
useAnimatedRef() : ref;
if (scrollViewOffset) {
// eslint-disable-next-line react-hooks/rules-of-hooks
useScrollViewOffset(animatedRef, scrollViewOffset);
}
// Set default scrollEventThrottle, because user expects
// to have continuous scroll events.
// We set it to 1 so we have peace until
// there are 960 fps screens.
if (!('scrollEventThrottle' in restProps)) {
restProps.scrollEventThrottle = 1;
}
return /*#__PURE__*/React.createElement(AnimatedScrollViewComponent, _extends({
ref: animatedRef
}, restProps));
});
//# sourceMappingURL=ScrollView.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_extends","Object","assign","bind","target","i","arguments","length","source","key","prototype","hasOwnProperty","call","apply","React","forwardRef","ScrollView","createAnimatedComponent","useAnimatedRef","useScrollViewOffset","AnimatedScrollViewComponent","AnimatedScrollView","props","ref","scrollViewOffset","restProps","animatedRef","scrollEventThrottle","createElement"],"sources":["ScrollView.tsx"],"sourcesContent":["'use strict';\nimport type { ForwardedRef } from 'react';\nimport React, { forwardRef } from 'react';\nimport type { ScrollViewProps } from 'react-native';\nimport { ScrollView } from 'react-native';\nimport { createAnimatedComponent } from '../../createAnimatedComponent';\nimport type { SharedValue } from '../commonTypes';\nimport type { AnimatedRef } from '../hook';\nimport { useAnimatedRef, useScrollViewOffset } from '../hook';\nimport type { AnimatedProps } from '../helperTypes';\n\nexport interface AnimatedScrollViewProps\n extends AnimatedProps<ScrollViewProps> {\n scrollViewOffset?: SharedValue<number>;\n}\n\n// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,\n// but not things like NativeMethods, etc. we need to add them manually by extending the type.\ninterface AnimatedScrollViewComplement extends ScrollView {\n getNode(): ScrollView;\n}\n\nconst AnimatedScrollViewComponent = createAnimatedComponent(ScrollView);\n\nexport const AnimatedScrollView = forwardRef(\n (props: AnimatedScrollViewProps, ref: ForwardedRef<AnimatedScrollView>) => {\n const { scrollViewOffset, ...restProps } = props;\n const animatedRef = (\n ref === null\n ? // eslint-disable-next-line react-hooks/rules-of-hooks\n useAnimatedRef<ScrollView>()\n : ref\n ) as AnimatedRef<AnimatedScrollView>;\n\n if (scrollViewOffset) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useScrollViewOffset(animatedRef, scrollViewOffset);\n }\n\n // Set default scrollEventThrottle, because user expects\n // to have continuous scroll events.\n // We set it to 1 so we have peace until\n // there are 960 fps screens.\n if (!('scrollEventThrottle' in restProps)) {\n restProps.scrollEventThrottle = 1;\n }\n\n return <AnimatedScrollViewComponent ref={animatedRef} {...restProps} />;\n }\n);\n\nexport type AnimatedScrollView = AnimatedScrollViewComplement &\n typeof AnimatedScrollViewComponent;\n"],"mappings":"AAAA,YAAY;;AAAC,SAAAA,SAAA,IAAAA,QAAA,GAAAC,MAAA,CAAAC,MAAA,GAAAD,MAAA,CAAAC,MAAA,CAAAC,IAAA,eAAAC,MAAA,aAAAC,CAAA,MAAAA,CAAA,GAAAC,SAAA,CAAAC,MAAA,EAAAF,CAAA,UAAAG,MAAA,GAAAF,SAAA,CAAAD,CAAA,YAAAI,GAAA,IAAAD,MAAA,QAAAP,MAAA,CAAAS,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAJ,MAAA,EAAAC,GAAA,KAAAL,MAAA,CAAAK,GAAA,IAAAD,MAAA,CAAAC,GAAA,gBAAAL,MAAA,YAAAJ,QAAA,CAAAa,KAAA,OAAAP,SAAA;AAEb,OAAOQ,KAAK,IAAIC,UAAU,QAAQ,OAAO;AAEzC,SAASC,UAAU,QAAQ,cAAc;AACzC,SAASC,uBAAuB,QAAQ,+BAA+B;AAGvE,SAASC,cAAc,EAAEC,mBAAmB,QAAQ,SAAS;;AAQ7D;AACA;;AAKA,MAAMC,2BAA2B,GAAGH,uBAAuB,CAACD,UAAU,CAAC;AAEvE,OAAO,MAAMK,kBAAkB,gBAAGN,UAAU,CAC1C,CAACO,KAA8B,EAAEC,GAAqC,KAAK;EACzE,MAAM;IAAEC,gBAAgB;IAAE,GAAGC;EAAU,CAAC,GAAGH,KAAK;EAChD,MAAMI,WAAW,GACfH,GAAG,KAAK,IAAI;EACR;EACAL,cAAc,CAAa,CAAC,GAC5BK,GAC8B;EAEpC,IAAIC,gBAAgB,EAAE;IACpB;IACAL,mBAAmB,CAACO,WAAW,EAAEF,gBAAgB,CAAC;EACpD;;EAEA;EACA;EACA;EACA;EACA,IAAI,EAAE,qBAAqB,IAAIC,SAAS,CAAC,EAAE;IACzCA,SAAS,CAACE,mBAAmB,GAAG,CAAC;EACnC;EAEA,oBAAOb,KAAA,CAAAc,aAAA,CAACR,2BAA2B,EAAApB,QAAA;IAACuB,GAAG,EAAEG;EAAY,GAAKD,SAAS,CAAG,CAAC;AACzE,CACF,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,10 @@
'use strict';
import { Text } from 'react-native';
import { createAnimatedComponent } from '../../createAnimatedComponent';
// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,
// but not things like NativeMethods, etc. we need to add them manually by extending the type.
export const AnimatedText = createAnimatedComponent(Text);
//# sourceMappingURL=Text.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["Text","createAnimatedComponent","AnimatedText"],"sources":["Text.ts"],"sourcesContent":["'use strict';\nimport { Text } from 'react-native';\nimport { createAnimatedComponent } from '../../createAnimatedComponent';\n\n// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,\n// but not things like NativeMethods, etc. we need to add them manually by extending the type.\ninterface AnimatedTextComplement extends Text {\n getNode(): Text;\n}\n\nexport const AnimatedText = createAnimatedComponent(Text);\n\nexport type AnimatedText = typeof AnimatedText & AnimatedTextComplement;\n"],"mappings":"AAAA,YAAY;;AACZ,SAASA,IAAI,QAAQ,cAAc;AACnC,SAASC,uBAAuB,QAAQ,+BAA+B;;AAEvE;AACA;;AAKA,OAAO,MAAMC,YAAY,GAAGD,uBAAuB,CAACD,IAAI,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,10 @@
'use strict';
import { View } from 'react-native';
import { createAnimatedComponent } from '../../createAnimatedComponent';
// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,
// but not things like NativeMethods, etc. we need to add them manually by extending the type.
export const AnimatedView = createAnimatedComponent(View);
//# sourceMappingURL=View.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["View","createAnimatedComponent","AnimatedView"],"sources":["View.ts"],"sourcesContent":["'use strict';\nimport { View } from 'react-native';\nimport { createAnimatedComponent } from '../../createAnimatedComponent';\n\n// Since createAnimatedComponent return type is ComponentClass that has the props of the argument,\n// but not things like NativeMethods, etc. we need to add them manually by extending the type.\ninterface AnimatedViewComplement extends View {\n getNode(): View;\n}\n\nexport const AnimatedView = createAnimatedComponent(View);\n\nexport type AnimatedView = typeof AnimatedView & AnimatedViewComplement;\n"],"mappings":"AAAA,YAAY;;AACZ,SAASA,IAAI,QAAQ,cAAc;AACnC,SAASC,uBAAuB,QAAQ,+BAA+B;;AAEvE;AACA;;AAKA,OAAO,MAAMC,YAAY,GAAGD,uBAAuB,CAACD,IAAI,CAAC","ignoreList":[]}