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 @@
import { requireNativeModule } from 'expo-modules-core';
export default requireNativeModule('ExpoKeepAwake');

View File

@@ -0,0 +1,77 @@
import { CodedError, Platform, Subscription } from 'expo-modules-core';
import { KeepAwakeEventState, KeepAwakeListener } from './KeepAwake.types';
const wakeLockMap: Record<string, WakeLockSentinel> = {};
type WakeLockSentinel = {
onrelease: null | ((event: any) => void);
released: boolean;
type: 'screen';
release?: Function;
addEventListener?: (event: string, listener: (event: any) => void) => void;
removeEventListener?: (event: string, listener: (event: any) => void) => void;
};
declare const navigator: {
wakeLock: {
request(type: 'screen'): Promise<WakeLockSentinel>;
};
};
/** Wraps the webWakeLock API https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API */
export default {
async isAvailableAsync() {
if (Platform.isDOMAvailable) {
return 'wakeLock' in navigator;
}
return false;
},
async activate(tag: string) {
if (!Platform.isDOMAvailable) {
return;
}
const wakeLock = await navigator.wakeLock.request('screen');
wakeLockMap[tag] = wakeLock;
},
async deactivate(tag: string) {
if (!Platform.isDOMAvailable) {
return;
}
if (wakeLockMap[tag]) {
wakeLockMap[tag].release?.();
delete wakeLockMap[tag];
} else {
throw new CodedError(
'ERR_KEEP_AWAKE_TAG_INVALID',
`The wake lock with tag ${tag} has not activated yet`
);
}
},
addListenerForTag(tag: string, listener: KeepAwakeListener): Subscription {
const eventListener = () => {
listener({ state: KeepAwakeEventState.RELEASE });
};
const sentinel = wakeLockMap[tag];
if (sentinel) {
if ('addEventListener' in sentinel) {
sentinel.addEventListener?.('release', eventListener);
} else {
sentinel.onrelease = eventListener;
}
}
return {
remove: () => {
const sentinel = wakeLockMap[tag];
if (sentinel) {
if (sentinel.removeEventListener) {
sentinel.removeEventListener('release', eventListener);
} else {
sentinel.onrelease = null;
}
}
},
};
},
};

View File

@@ -0,0 +1,27 @@
// @needsAudit
export type KeepAwakeEvent = {
/** Keep awake state. */
state: KeepAwakeEventState;
};
// @needsAudit
export enum KeepAwakeEventState {
RELEASE = 'release',
}
// @needsAudit
export type KeepAwakeListener = (event: KeepAwakeEvent) => void;
export type KeepAwakeOptions = {
/**
* The call will throw an unhandled promise rejection on Android when the original Activity is dead or deactivated.
* Set the value to `true` for suppressing the uncaught exception.
*/
suppressDeactivateWarnings?: boolean;
/**
* A callback that is invoked when the keep-awake state changes.
* @platform web
*/
listener?: KeepAwakeListener;
};

View File

@@ -0,0 +1,122 @@
import { Subscription, UnavailabilityError } from 'expo-modules-core';
import { useEffect, useId } from 'react';
import ExpoKeepAwake from './ExpoKeepAwake';
import { KeepAwakeListener, KeepAwakeOptions } from './KeepAwake.types';
/** Default tag, used when no tag has been specified in keep awake method calls. */
export const ExpoKeepAwakeTag = 'ExpoKeepAwakeDefaultTag';
/** @returns `true` on all platforms except [unsupported web browsers](https://caniuse.com/wake-lock). */
export async function isAvailableAsync(): Promise<boolean> {
if (ExpoKeepAwake.isAvailableAsync) {
return await ExpoKeepAwake.isAvailableAsync();
}
return true;
}
/**
* A React hook to keep the screen awake for as long as the owner component is mounted.
* The optionally provided `tag` argument is used when activating and deactivating the keep-awake
* feature. If unspecified, an ID unique to the owner component is used. See the documentation for
* `activateKeepAwakeAsync` below to learn more about the `tag` argument.
*
* @param tag Tag to lock screen sleep prevention. If not provided, an ID unique to the owner component is used.
* @param options Additional options for the keep awake hook.
*/
export function useKeepAwake(tag?: string, options?: KeepAwakeOptions): void {
const defaultTag = useId();
const tagOrDefault = tag ?? defaultTag;
useEffect(() => {
let isMounted = true;
activateKeepAwakeAsync(tagOrDefault).then(() => {
if (isMounted && ExpoKeepAwake.addListenerForTag && options?.listener) {
addListener(tagOrDefault, options.listener);
}
});
return () => {
isMounted = false;
if (options?.suppressDeactivateWarnings) {
deactivateKeepAwake(tagOrDefault).catch(() => {});
} else {
deactivateKeepAwake(tagOrDefault);
}
};
}, [tagOrDefault]);
}
// @needsAudit
/**
* Prevents the screen from sleeping until `deactivateKeepAwake` is called with the same `tag` value.
*
* If the `tag` argument is specified, the screen will not sleep until you call `deactivateKeepAwake`
* with the same `tag` argument. When using multiple `tags` for activation you'll have to deactivate
* each one in order to re-enable screen sleep. If tag is unspecified, the default `tag` is used.
*
* Web support [is limited](https://caniuse.com/wake-lock).
*
* @param tag Tag to lock screen sleep prevention. If not provided, the default tag is used.
* @deprecated use `activateKeepAwakeAsync` instead.
*/
export function activateKeepAwake(tag: string = ExpoKeepAwakeTag): Promise<void> {
console.warn('`activateKeepAwake` is deprecated. Use `activateKeepAwakeAsync` instead.');
return activateKeepAwakeAsync(tag);
}
// @needsAudit
/**
* Prevents the screen from sleeping until `deactivateKeepAwake` is called with the same `tag` value.
*
* If the `tag` argument is specified, the screen will not sleep until you call `deactivateKeepAwake`
* with the same `tag` argument. When using multiple `tags` for activation you'll have to deactivate
* each one in order to re-enable screen sleep. If tag is unspecified, the default `tag` is used.
*
* Web support [is limited](https://caniuse.com/wake-lock).
*
* @param tag Tag to lock screen sleep prevention. If not provided, the default tag is used.
*/
export async function activateKeepAwakeAsync(tag: string = ExpoKeepAwakeTag): Promise<void> {
await ExpoKeepAwake.activate?.(tag);
}
// @needsAudit
/**
* Releases the lock on screen-sleep prevention associated with the given `tag` value. If `tag`
* is unspecified, it defaults to the same default tag that `activateKeepAwake` uses.
*
* @param tag Tag to release the lock on screen sleep prevention. If not provided,
* the default tag is used.
*/
export async function deactivateKeepAwake(tag: string = ExpoKeepAwakeTag): Promise<void> {
await ExpoKeepAwake.deactivate?.(tag);
}
/**
* Observe changes to the keep awake timer.
* On web, this changes when navigating away from the active window/tab. No-op on native.
* @platform web
*
* @example
* ```ts
* KeepAwake.addListener(({ state }) => {
* // ...
* });
* ```
*/
export function addListener(
tagOrListener: string | KeepAwakeListener,
listener?: KeepAwakeListener
): Subscription {
// Assert so the type is non-nullable.
if (!ExpoKeepAwake.addListenerForTag) {
throw new UnavailabilityError('ExpoKeepAwake', 'addListenerForTag');
}
const tag = typeof tagOrListener === 'string' ? tagOrListener : ExpoKeepAwakeTag;
const _listener = typeof tagOrListener === 'function' ? tagOrListener : listener;
return ExpoKeepAwake.addListenerForTag(tag, _listener);
}
export * from './KeepAwake.types';