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,2 @@
export default function DevLoadingView(): JSX.Element | null;
//# sourceMappingURL=DevLoadingView.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingView.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingView.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,cAAc,uBAiFrC"}

View File

@@ -0,0 +1,110 @@
// Prevent pulling in all of expo-modules-core on web
import { EventEmitter } from 'expo-modules-core/build/EventEmitter';
import React, { useEffect, useState, useRef, useMemo } from 'react';
import { Animated, StyleSheet, Text, Platform, View } from 'react-native';
import DevLoadingViewNativeModule from './DevLoadingViewNativeModule';
import { getInitialSafeArea } from './getInitialSafeArea';
export default function DevLoadingView() {
const [message, setMessage] = useState('Refreshing...');
const [isDevLoading, setIsDevLoading] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const translateY = useRef(new Animated.Value(0)).current;
const emitter = useMemo(() => {
try {
return new EventEmitter(DevLoadingViewNativeModule);
}
catch (error) {
throw new Error('Failed to instantiate native emitter in `DevLoadingView` because the native module `DevLoadingView` is undefined: ' +
error.message);
}
}, []);
useEffect(() => {
if (!emitter)
return;
function handleShowMessage(event) {
setMessage(event.message);
// TODO: if we show the refreshing banner and don't get a hide message
// for 3 seconds, warn the user that it's taking a while and suggest
// they reload
translateY.setValue(0);
setIsDevLoading(true);
}
function handleHide() {
// TODO: if we showed the 'refreshing' banner less than 250ms ago, delay
// switching to the 'finished' banner
setIsAnimating(true);
setIsDevLoading(false);
Animated.timing(translateY, {
toValue: 150,
delay: 1000,
duration: 350,
useNativeDriver: Platform.OS !== 'web',
}).start(({ finished }) => {
if (finished) {
setIsAnimating(false);
translateY.setValue(0);
}
});
}
const showMessageSubscription = emitter.addListener('devLoadingView:showMessage', handleShowMessage);
const hideSubscription = emitter.addListener('devLoadingView:hide', handleHide);
return function cleanup() {
showMessageSubscription.remove();
hideSubscription.remove();
};
}, [translateY, emitter]);
if (!isDevLoading && !isAnimating) {
return null;
}
return (<Animated.View style={[styles.animatedContainer, { transform: [{ translateY }] }]}>
<View style={styles.banner}>
<View style={styles.contentContainer}>
<View style={{ flexDirection: 'row' }}>
<Text style={styles.text}>{message}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.subtitle}>
{isDevLoading ? 'Using Fast Refresh' : "Don't see your changes? Reload the app"}
</Text>
</View>
</View>
</View>
</Animated.View>);
}
const styles = StyleSheet.create({
animatedContainer: {
// @ts-expect-error: fixed is not a valid value for position in Yoga but it is on web.
position: Platform.select({
web: 'fixed',
default: 'absolute',
}),
pointerEvents: 'none',
bottom: 0,
left: 0,
right: 0,
zIndex: 42, // arbitrary
},
banner: {
flex: 1,
overflow: 'visible',
backgroundColor: 'rgba(0,0,0,0.75)',
paddingBottom: getInitialSafeArea().bottom,
},
contentContainer: {
flex: 1,
paddingTop: 10,
paddingBottom: 5,
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
},
text: {
color: '#fff',
fontSize: 15,
},
subtitle: {
color: 'rgba(255,255,255,0.8)',
},
});
//# sourceMappingURL=DevLoadingView.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
declare const _default: Readonly<{
name: "DevLoadingView";
startObserving(): void;
stopObserving(): void;
addListener(): void;
removeListeners(): void;
}>;
export default _default;
//# sourceMappingURL=DevLoadingViewNativeModule.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.ts"],"names":[],"mappings":";;;;;;;AAAA,wBAMG"}

View File

@@ -0,0 +1,8 @@
export default Object.freeze({
name: 'DevLoadingView',
startObserving() { },
stopObserving() { },
addListener() { },
removeListeners() { },
});
//# sourceMappingURL=DevLoadingViewNativeModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.js","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.ts"],"names":[],"mappings":"AAAA,eAAe,MAAM,CAAC,MAAM,CAAC;IAC3B,IAAI,EAAE,gBAAgB;IACtB,cAAc,KAAI,CAAC;IACnB,aAAa,KAAI,CAAC;IAClB,WAAW,KAAI,CAAC;IAChB,eAAe,KAAI,CAAC;CACrB,CAAC,CAAC","sourcesContent":["export default Object.freeze({\n name: 'DevLoadingView',\n startObserving() {},\n stopObserving() {},\n addListener() {},\n removeListeners() {},\n});\n"]}

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=DevLoadingViewNativeModule.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.native.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.native.ts"],"names":[],"mappings":";AAEA,wBAA4C"}

View File

@@ -0,0 +1,3 @@
import { NativeModules } from 'react-native';
export default NativeModules.DevLoadingView;
//# sourceMappingURL=DevLoadingViewNativeModule.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.native.js","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,eAAe,aAAa,CAAC,cAAc,CAAC","sourcesContent":["import { NativeModules } from 'react-native';\n\nexport default NativeModules.DevLoadingView;\n"]}

View File

@@ -0,0 +1,30 @@
type ExpoGoProjectConfig = {
mainModuleName?: string;
debuggerHost?: string;
logUrl?: string;
developer?: {
tool?: string;
[key: string]: any;
};
packagerOpts?: ExpoGoPackagerOpts;
};
type ExpoGoPackagerOpts = {
hostType?: string;
dev?: boolean;
strict?: boolean;
minify?: boolean;
urlType?: string;
urlRandomness?: string;
lanType?: string;
[key: string]: any;
};
/**
* Returns a boolean value whether the app is running in Expo Go.
*/
export declare function isRunningInExpoGo(): boolean;
/**
* Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.
*/
export declare function getExpoGoProjectConfig(): ExpoGoProjectConfig | null;
export {};
//# sourceMappingURL=ExpoGo.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.d.ts","sourceRoot":"","sources":["../../src/environment/ExpoGo.ts"],"names":[],"mappings":"AAOA,KAAK,mBAAmB,GAAG;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAYF;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAEnE"}

View File

@@ -0,0 +1,24 @@
import { requireNativeModule } from 'expo-modules-core';
// ExpoGo module is available only when the app is run in Expo Go,
// otherwise we use `null` instead of throwing an error.
const NativeExpoGoModule = (() => {
try {
return requireNativeModule('ExpoGo');
}
catch {
return null;
}
})();
/**
* Returns a boolean value whether the app is running in Expo Go.
*/
export function isRunningInExpoGo() {
return NativeExpoGoModule != null;
}
/**
* Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.
*/
export function getExpoGoProjectConfig() {
return NativeExpoGoModule?.projectConfig ?? null;
}
//# sourceMappingURL=ExpoGo.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.js","sourceRoot":"","sources":["../../src/environment/ExpoGo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA6BxD,kEAAkE;AAClE,wDAAwD;AACxD,MAAM,kBAAkB,GAAG,CAAC,GAAwB,EAAE;IACpD,IAAI;QACF,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;KACtC;IAAC,MAAM;QACN,OAAO,IAAI,CAAC;KACb;AACH,CAAC,CAAC,EAAE,CAAC;AAEL;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,kBAAkB,IAAI,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,kBAAkB,EAAE,aAAa,IAAI,IAAI,CAAC;AACnD,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\ntype ExpoGoModule = {\n expoVersion: string;\n projectConfig: ExpoGoProjectConfig;\n};\n\ntype ExpoGoProjectConfig = {\n mainModuleName?: string;\n debuggerHost?: string;\n logUrl?: string;\n developer?: {\n tool?: string;\n [key: string]: any;\n };\n packagerOpts?: ExpoGoPackagerOpts;\n};\n\ntype ExpoGoPackagerOpts = {\n hostType?: string;\n dev?: boolean;\n strict?: boolean;\n minify?: boolean;\n urlType?: string;\n urlRandomness?: string;\n lanType?: string;\n [key: string]: any;\n};\n\n// ExpoGo module is available only when the app is run in Expo Go,\n// otherwise we use `null` instead of throwing an error.\nconst NativeExpoGoModule = ((): ExpoGoModule | null => {\n try {\n return requireNativeModule('ExpoGo');\n } catch {\n return null;\n }\n})();\n\n/**\n * Returns a boolean value whether the app is running in Expo Go.\n */\nexport function isRunningInExpoGo(): boolean {\n return NativeExpoGoModule != null;\n}\n\n/**\n * Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.\n */\nexport function getExpoGoProjectConfig(): ExpoGoProjectConfig | null {\n return NativeExpoGoModule?.projectConfig ?? null;\n}\n"]}

View File

@@ -0,0 +1,3 @@
export declare function isRunningInExpoGo(): boolean;
export declare function getExpoGoProjectConfig(): null;
//# sourceMappingURL=ExpoGo.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.web.d.ts","sourceRoot":"","sources":["../../src/environment/ExpoGo.web.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,YAEhC;AAED,wBAAgB,sBAAsB,SAErC"}

View File

@@ -0,0 +1,7 @@
export function isRunningInExpoGo() {
return false;
}
export function getExpoGoProjectConfig() {
return null;
}
//# sourceMappingURL=ExpoGo.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.web.js","sourceRoot":"","sources":["../../src/environment/ExpoGo.web.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB;IAC/B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["export function isRunningInExpoGo() {\n return false;\n}\n\nexport function getExpoGoProjectConfig() {\n return null;\n}\n"]}

View File

@@ -0,0 +1,7 @@
export declare function getInitialSafeArea(): {
top: number;
bottom: number;
left: number;
right: number;
};
//# sourceMappingURL=getInitialSafeArea.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInitialSafeArea.d.ts","sourceRoot":"","sources":["../../src/environment/getInitialSafeArea.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOjG"}

View File

@@ -0,0 +1,9 @@
export function getInitialSafeArea() {
return {
top: 0,
bottom: 0,
left: 0,
right: 0,
};
}
//# sourceMappingURL=getInitialSafeArea.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInitialSafeArea.js","sourceRoot":"","sources":["../../src/environment/getInitialSafeArea.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,GAAG,EAAE,CAAC;QACN,MAAM,EAAE,CAAC;QACT,IAAI,EAAE,CAAC;QACP,KAAK,EAAE,CAAC;KACT,CAAC;AACJ,CAAC","sourcesContent":["export function getInitialSafeArea(): { top: number; bottom: number; left: number; right: number } {\n return {\n top: 0,\n bottom: 0,\n left: 0,\n right: 0,\n };\n}\n"]}

View File

@@ -0,0 +1,11 @@
/**
* Get the best estimate safe area before native modules have fully loaded.
* This is a hack to get the safe area insets without explicitly depending on react-native-safe-area-context.
*/
export declare function getInitialSafeArea(): {
top: number;
bottom: number;
left: number;
right: number;
};
//# sourceMappingURL=getInitialSafeArea.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInitialSafeArea.native.d.ts","sourceRoot":"","sources":["../../src/environment/getInitialSafeArea.native.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOjG"}

View File

@@ -0,0 +1,13 @@
import { TurboModuleRegistry } from 'react-native';
const DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };
/**
* Get the best estimate safe area before native modules have fully loaded.
* This is a hack to get the safe area insets without explicitly depending on react-native-safe-area-context.
*/
export function getInitialSafeArea() {
const RNCSafeAreaContext = TurboModuleRegistry.get('RNCSafeAreaContext');
// @ts-ignore: we're not using the spec so the return type of getConstants() is {}
const initialWindowMetrics = RNCSafeAreaContext?.getConstants()?.initialWindowMetrics;
return initialWindowMetrics?.insets ?? DEFAULT_SAFE_AREA;
}
//# sourceMappingURL=getInitialSafeArea.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getInitialSafeArea.native.js","sourceRoot":"","sources":["../../src/environment/getInitialSafeArea.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAEnE;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAEzE,kFAAkF;IAClF,MAAM,oBAAoB,GAAG,kBAAkB,EAAE,YAAY,EAAE,EAAE,oBAAoB,CAAC;IAEtF,OAAO,oBAAoB,EAAE,MAAM,IAAI,iBAAiB,CAAC;AAC3D,CAAC","sourcesContent":["import { TurboModuleRegistry } from 'react-native';\n\nconst DEFAULT_SAFE_AREA = { top: 0, bottom: 0, left: 0, right: 0 };\n\n/**\n * Get the best estimate safe area before native modules have fully loaded.\n * This is a hack to get the safe area insets without explicitly depending on react-native-safe-area-context.\n */\nexport function getInitialSafeArea(): { top: number; bottom: number; left: number; right: number } {\n const RNCSafeAreaContext = TurboModuleRegistry.get('RNCSafeAreaContext');\n\n // @ts-ignore: we're not using the spec so the return type of getConstants() is {}\n const initialWindowMetrics = RNCSafeAreaContext?.getConstants()?.initialWindowMetrics;\n\n return initialWindowMetrics?.insets ?? DEFAULT_SAFE_AREA;\n}\n"]}