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,72 @@
import { Notification, NotificationContent, NotificationRequest, NotificationResponse } from '../Notifications.types';
/**
* @hidden
*
* Does any required processing of a notification response from native code
* before it is passed to a notification response listener, or to the
* last notification response hook.
*
* @param response The raw response passed in from native code
* @returns the mapped response.
*/
export declare const mapNotificationResponse: (response: NotificationResponse) => {
notification: {
request: {
content: NotificationContent & {
dataString?: string | undefined;
};
identifier: string;
trigger: import("../Notifications.types").NotificationTrigger;
};
date: number;
};
actionIdentifier: string;
userText?: string | undefined;
};
/**
* @hidden
*
* Does any required processing of a notification from native code
* before it is passed to a notification listener.
*
* @param notification The raw notification passed in from native code
* @returns the mapped notification.
*/
export declare const mapNotification: (notification: Notification) => {
request: {
content: NotificationContent & {
dataString?: string | undefined;
};
identifier: string;
trigger: import("../Notifications.types").NotificationTrigger;
};
date: number;
};
/**
* @hidden
*
* Does any required processing of a notification request from native code
* before it is passed to other JS code.
*
* @param request The raw request passed in from native code
* @returns the mapped request.
*/
export declare const mapNotificationRequest: (request: NotificationRequest) => {
content: NotificationContent & {
dataString?: string | undefined;
};
identifier: string;
trigger: import("../Notifications.types").NotificationTrigger;
};
/**
* @hidden
* Does any required processing of notification content from native code
* before being passed to other JS code.
*
* @param content The raw content passed in from native code
* @returns the mapped content.
*/
export declare const mapNotificationContent: (content: NotificationContent) => NotificationContent & {
dataString?: string | undefined;
};
//# sourceMappingURL=mapNotificationResponse.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mapNotificationResponse.d.ts","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,aAAc,oBAAoB;;;;;;;;;;;;;CAKrE,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,iBAAkB,YAAY;;;;;;;;;CAGxD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,YAAa,mBAAmB;;;;;;CAGjE,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,YAAa,mBAAmB;;CAYlE,CAAC"}

View File

@@ -0,0 +1,65 @@
/**
* @hidden
*
* Does any required processing of a notification response from native code
* before it is passed to a notification response listener, or to the
* last notification response hook.
*
* @param response The raw response passed in from native code
* @returns the mapped response.
*/
export const mapNotificationResponse = (response) => {
return {
...response,
notification: mapNotification(response.notification),
};
};
/**
* @hidden
*
* Does any required processing of a notification from native code
* before it is passed to a notification listener.
*
* @param notification The raw notification passed in from native code
* @returns the mapped notification.
*/
export const mapNotification = (notification) => ({
...notification,
request: mapNotificationRequest(notification.request),
});
/**
* @hidden
*
* Does any required processing of a notification request from native code
* before it is passed to other JS code.
*
* @param request The raw request passed in from native code
* @returns the mapped request.
*/
export const mapNotificationRequest = (request) => ({
...request,
content: mapNotificationContent(request.content),
});
/**
* @hidden
* Does any required processing of notification content from native code
* before being passed to other JS code.
*
* @param content The raw content passed in from native code
* @returns the mapped content.
*/
export const mapNotificationContent = (content) => {
const mappedContent = { ...content };
try {
const dataString = mappedContent['dataString'];
if (typeof dataString === 'string') {
mappedContent.data = JSON.parse(dataString);
delete mappedContent.dataString;
}
}
catch (e) {
console.log(`Error in notification: ${e}`);
}
return mappedContent;
};
//# sourceMappingURL=mapNotificationResponse.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mapNotificationResponse.js","sourceRoot":"","sources":["../../src/utils/mapNotificationResponse.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,QAA8B,EAAE,EAAE;IACxE,OAAO;QACL,GAAG,QAAQ;QACX,YAAY,EAAE,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC;KACrD,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,YAA0B,EAAE,EAAE,CAAC,CAAC;IAC9D,GAAG,YAAY;IACf,OAAO,EAAE,sBAAsB,CAAC,YAAY,CAAC,OAAO,CAAC;CACtD,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAA4B,EAAE,EAAE,CAAC,CAAC;IACvE,GAAG,OAAO;IACV,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,OAAO,CAAC;CACjD,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,OAA4B,EAAE,EAAE;IACrE,MAAM,aAAa,GAAkD,EAAE,GAAG,OAAO,EAAE,CAAC;IACpF,IAAI;QACF,MAAM,UAAU,GAAG,aAAa,CAAC,YAAY,CAAC,CAAC;QAC/C,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE;YAClC,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC5C,OAAO,aAAa,CAAC,UAAU,CAAC;SACjC;KACF;IAAC,OAAO,CAAM,EAAE;QACf,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC;KAC5C;IACD,OAAO,aAAa,CAAC;AACvB,CAAC,CAAC","sourcesContent":["import {\n Notification,\n NotificationContent,\n NotificationRequest,\n NotificationResponse,\n} from '../Notifications.types';\n\n/**\n * @hidden\n *\n * Does any required processing of a notification response from native code\n * before it is passed to a notification response listener, or to the\n * last notification response hook.\n *\n * @param response The raw response passed in from native code\n * @returns the mapped response.\n */\nexport const mapNotificationResponse = (response: NotificationResponse) => {\n return {\n ...response,\n notification: mapNotification(response.notification),\n };\n};\n\n/**\n * @hidden\n *\n * Does any required processing of a notification from native code\n * before it is passed to a notification listener.\n *\n * @param notification The raw notification passed in from native code\n * @returns the mapped notification.\n */\nexport const mapNotification = (notification: Notification) => ({\n ...notification,\n request: mapNotificationRequest(notification.request),\n});\n\n/**\n * @hidden\n *\n * Does any required processing of a notification request from native code\n * before it is passed to other JS code.\n *\n * @param request The raw request passed in from native code\n * @returns the mapped request.\n */\nexport const mapNotificationRequest = (request: NotificationRequest) => ({\n ...request,\n content: mapNotificationContent(request.content),\n});\n\n/**\n * @hidden\n * Does any required processing of notification content from native code\n * before being passed to other JS code.\n *\n * @param content The raw content passed in from native code\n * @returns the mapped content.\n */\nexport const mapNotificationContent = (content: NotificationContent) => {\n const mappedContent: NotificationContent & { dataString?: string } = { ...content };\n try {\n const dataString = mappedContent['dataString'];\n if (typeof dataString === 'string') {\n mappedContent.data = JSON.parse(dataString);\n delete mappedContent.dataString;\n }\n } catch (e: any) {\n console.log(`Error in notification: ${e}`);\n }\n return mappedContent;\n};\n"]}

View File

@@ -0,0 +1,3 @@
import { DevicePushToken } from '../Tokens.types';
export declare function updateDevicePushTokenAsync(signal: AbortSignal, token: DevicePushToken): Promise<void>;
//# sourceMappingURL=updateDevicePushTokenAsync.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"updateDevicePushTokenAsync.d.ts","sourceRoot":"","sources":["../../src/utils/updateDevicePushTokenAsync.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,iBA2F3F"}

View File

@@ -0,0 +1,115 @@
import { computeNextBackoffInterval } from '@ide/backoff';
import * as Application from 'expo-application';
import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';
import ServerRegistrationModule from '../ServerRegistrationModule';
const updateDevicePushTokenUrl = 'https://exp.host/--/api/v2/push/updateDeviceToken';
export async function updateDevicePushTokenAsync(signal, token) {
const doUpdateDevicePushTokenAsync = async (retry) => {
const [development, deviceId] = await Promise.all([
shouldUseDevelopmentNotificationService(),
getDeviceIdAsync(),
]);
const body = {
deviceId: deviceId.toLowerCase(),
development,
deviceToken: token.data,
appId: Application.applicationId,
type: getTypeOfToken(token),
};
try {
const response = await fetch(updateDevicePushTokenUrl, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(body),
signal,
});
// Help debug erroring servers
if (!response.ok) {
console.debug('[expo-notifications] Error encountered while updating the device push token with the server:', await response.text());
}
// Retry if request failed
if (!response.ok) {
retry();
}
}
catch (e) {
// Error returned if the request is aborted should be an 'AbortError'. In
// React Native fetch is polyfilled using `whatwg-fetch` which:
// - creates `AbortError`s like this
// https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L505
// - which creates exceptions like
// https://github.com/github/fetch/blob/75d9455d380f365701151f3ac85c5bda4bbbde76/fetch.js#L490-L494
if (e.name === 'AbortError') {
// We don't consider AbortError a failure, it's a sign somewhere else the
// request is expected to succeed and we don't need this one, so let's
// just return.
return;
}
console.warn('[expo-notifications] Error thrown while updating the device push token with the server:', e);
retry();
}
};
let shouldTry = true;
const retry = () => {
shouldTry = true;
};
let retriesCount = 0;
const initialBackoff = 500; // 0.5 s
const backoffOptions = {
maxBackoff: 2 * 60 * 1000, // 2 minutes
};
let nextBackoffInterval = computeNextBackoffInterval(initialBackoff, retriesCount, backoffOptions);
while (shouldTry && !signal.aborted) {
// Will be set to true by `retry` if it's called
shouldTry = false;
await doUpdateDevicePushTokenAsync(retry);
// Do not wait if we won't retry
if (shouldTry && !signal.aborted) {
nextBackoffInterval = computeNextBackoffInterval(initialBackoff, retriesCount, backoffOptions);
retriesCount += 1;
await new Promise((resolve) => setTimeout(resolve, nextBackoffInterval));
}
}
}
// Same as in getExpoPushTokenAsync
async function getDeviceIdAsync() {
try {
if (!ServerRegistrationModule.getInstallationIdAsync) {
throw new UnavailabilityError('ExpoServerRegistrationModule', 'getInstallationIdAsync');
}
return await ServerRegistrationModule.getInstallationIdAsync();
}
catch (e) {
throw new CodedError('ERR_NOTIFICATIONS_DEVICE_ID', `Could not fetch the installation ID of the application: ${e}.`);
}
}
// Same as in getExpoPushTokenAsync
function getTypeOfToken(devicePushToken) {
switch (devicePushToken.type) {
case 'ios':
return 'apns';
case 'android':
return 'fcm';
// This probably will error on server, but let's make this function future-safe.
default:
return devicePushToken.type;
}
}
// Same as in getExpoPushTokenAsync
async function shouldUseDevelopmentNotificationService() {
if (Platform.OS === 'ios') {
try {
const notificationServiceEnvironment = await Application.getIosPushNotificationServiceEnvironmentAsync();
if (notificationServiceEnvironment === 'development') {
return true;
}
}
catch {
// We can't do anything here, we'll fallback to false then.
}
}
return false;
}
//# sourceMappingURL=updateDevicePushTokenAsync.js.map

File diff suppressed because one or more lines are too long