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,5 @@
# Metro
🚇 This package contains core files for [Metro](https://metrobundler.dev/).
(TODO)

View File

@@ -0,0 +1,23 @@
{
"name": "metro-core",
"version": "0.80.12",
"description": "🚇 Metro's core package.",
"main": "src/index.js",
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"lodash.throttle": "^4.1.1",
"metro-resolver": "0.80.12"
},
"license": "MIT",
"engines": {
"node": ">=18"
}
}

View File

@@ -0,0 +1,71 @@
"use strict";
const VERSION = require("../package.json").version;
const { EventEmitter } = require("events");
const os = require("os");
const log_session = `${os.hostname()}-${Date.now()}`;
const eventEmitter = new EventEmitter();
function on(event, handler) {
eventEmitter.on(event, handler);
}
function createEntry(data) {
const logEntry =
typeof data === "string"
? {
log_entry_label: data,
}
: data;
return {
...logEntry,
log_session,
metro_bundler_version: VERSION,
};
}
function createActionStartEntry(data) {
const logEntry =
typeof data === "string"
? {
action_name: data,
}
: data;
const { action_name } = logEntry;
return createEntry({
log_entry_label: action_name,
...logEntry,
action_name,
action_phase: "start",
start_timestamp: process.hrtime(),
});
}
function createActionEndEntry(logEntry, error) {
const { action_name, action_phase, start_timestamp } = logEntry;
if (action_phase !== "start" || !Array.isArray(start_timestamp)) {
throw new Error("Action has not started or has already ended");
}
const timeDelta = process.hrtime(start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return createEntry({
log_entry_label: action_name,
...logEntry,
action_name,
action_phase: "end",
duration_ms,
...(error != null
? {
error_message: error.message,
error_stack: error.stack,
}
: null),
});
}
function log(logEntry) {
eventEmitter.emit("log", logEntry);
return logEntry;
}
module.exports = {
on,
createEntry,
createActionStartEntry,
createActionEndEntry,
log,
};

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
'use strict';
import type {BundleOptions} from 'metro/src/shared/types.flow';
const VERSION = require('../package.json').version;
const {EventEmitter} = require('events');
const os = require('os');
export type ActionLogEntryData = {
action_name: string,
log_entry_label?: string,
...
};
export type ActionStartLogEntry = {
action_name?: string,
action_phase?: string,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
...
};
export type LogEntry = {
action_name?: string,
action_phase?: string,
action_result?: string,
duration_ms?: number,
entry_point?: string,
log_entry_label: string,
log_session?: string,
start_timestamp?: [number, number],
outdated_modules?: number,
bundle_size?: number,
bundle_options?: BundleOptions,
bundle_hash?: string,
build_id?: string,
error_message?: string,
error_stack?: string,
...
};
const log_session = `${os.hostname()}-${Date.now()}`;
const eventEmitter = new EventEmitter();
function on(event: string, handler: (logEntry: LogEntry) => void): void {
eventEmitter.on(event, handler);
}
function createEntry(data: LogEntry | string): LogEntry {
const logEntry: LogEntry =
typeof data === 'string' ? {log_entry_label: data} : data;
return {
...logEntry,
log_session,
metro_bundler_version: VERSION,
};
}
function createActionStartEntry(data: ActionLogEntryData | string): LogEntry {
const logEntry = typeof data === 'string' ? {action_name: data} : data;
const {action_name} = logEntry;
return createEntry({
log_entry_label: action_name,
...logEntry,
action_name,
action_phase: 'start',
start_timestamp: process.hrtime(),
});
}
function createActionEndEntry(
logEntry: ActionStartLogEntry,
error?: ?Error,
): LogEntry {
const {action_name, action_phase, start_timestamp} = logEntry;
if (action_phase !== 'start' || !Array.isArray(start_timestamp)) {
throw new Error('Action has not started or has already ended');
}
const timeDelta = process.hrtime(start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return createEntry({
log_entry_label: (action_name: ?string),
...logEntry,
action_name,
action_phase: 'end',
duration_ms,
...(error != null
? {error_message: error.message, error_stack: error.stack}
: null),
});
}
function log(logEntry: LogEntry): LogEntry {
eventEmitter.emit('log', logEntry);
return logEntry;
}
module.exports = {
on,
createEntry,
createActionStartEntry,
createActionEndEntry,
log,
};

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import * as net from 'net';
import * as stream from 'stream';
export type UnderlyingStream = net.Socket | stream.Writable;
export class Terminal {
constructor(stream: UnderlyingStream);
/**
* Shows some text that is meant to be overriden later. Return the previous
* status that was shown and is no more. Calling `status()` with no argument
* removes the status altogether. The status is never shown in a
* non-interactive terminal: for example, if the output is redirected to a
* file, then we don't care too much about having a progress bar.
*/
status(format: string, ...args: unknown[]): string;
/**
* Similar to `console.log`, except it moves the status/progress text out of
* the way correctly. In non-interactive terminals this is the same as
* `console.log`.
*/
log(format: string, ...args: unknown[]): void;
/**
* Log the current status and start from scratch. This is useful if the last
* status was the last one of a series of updates.
*/
persistStatus(): void;
flush(): void;
}

View File

@@ -0,0 +1,81 @@
"use strict";
const throttle = require("lodash.throttle");
const readline = require("readline");
const tty = require("tty");
const util = require("util");
function clearStringBackwards(stream, str) {
readline.moveCursor(stream, -stream.columns, 0);
readline.clearLine(stream, 0);
let lineCount = (str.match(/\n/g) || []).length;
while (lineCount > 0) {
readline.moveCursor(stream, 0, -1);
readline.clearLine(stream, 0);
--lineCount;
}
}
function chunkString(str, size) {
const ANSI_COLOR = "\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?m";
const SKIP_ANSI = `(?:${ANSI_COLOR})*`;
return str.match(new RegExp(`(?:${SKIP_ANSI}.){1,${size}}`, "g")) || [];
}
function getTTYStream(stream) {
if (
stream instanceof tty.WriteStream &&
stream.isTTY &&
stream.columns >= 1
) {
return stream;
}
return null;
}
class Terminal {
constructor(stream) {
this._logLines = [];
this._nextStatusStr = "";
this._scheduleUpdate = throttle(this._update, 33);
this._statusStr = "";
this._stream = stream;
}
_update() {
const { _statusStr, _stream } = this;
const ttyStream = getTTYStream(_stream);
if (_statusStr === this._nextStatusStr && this._logLines.length === 0) {
return;
}
if (ttyStream != null) {
clearStringBackwards(ttyStream, _statusStr);
}
this._logLines.forEach((line) => {
_stream.write(line);
_stream.write("\n");
});
this._logLines = [];
if (ttyStream != null) {
this._nextStatusStr = chunkString(
this._nextStatusStr,
ttyStream.columns
).join("\n");
_stream.write(this._nextStatusStr);
}
this._statusStr = this._nextStatusStr;
}
status(format, ...args) {
const { _nextStatusStr } = this;
this._nextStatusStr = util.format(format, ...args);
this._scheduleUpdate();
return _nextStatusStr;
}
log(format, ...args) {
this._logLines.push(util.format(format, ...args));
this._scheduleUpdate();
}
persistStatus() {
this.log(this._nextStatusStr);
this._nextStatusStr = "";
}
flush() {
this._scheduleUpdate.flush();
}
}
module.exports = Terminal;

View File

@@ -0,0 +1,184 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
'use strict';
const throttle = require('lodash.throttle');
const readline = require('readline');
const tty = require('tty');
const util = require('util');
type UnderlyingStream = net$Socket | stream$Writable;
/**
* Clear some text that was previously printed on an interactive stream,
* without trailing newline character (so we have to move back to the
* beginning of the line).
*/
function clearStringBackwards(stream: tty.WriteStream, str: string): void {
readline.moveCursor(stream, -stream.columns, 0);
readline.clearLine(stream, 0);
let lineCount = (str.match(/\n/g) || []).length;
while (lineCount > 0) {
readline.moveCursor(stream, 0, -1);
readline.clearLine(stream, 0);
--lineCount;
}
}
/**
* Cut a string into an array of string of the specific maximum size. A newline
* ends a chunk immediately (it's not included in the "." RexExp operator), and
* is not included in the result.
* When counting we should ignore non-printable characters. In particular the
* ANSI escape sequences (regex: /\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?m/)
* (Not an exhaustive match, intended to match ANSI color escapes)
* https://en.wikipedia.org/wiki/ANSI_escape_code
*/
function chunkString(str: string, size: number): Array<string> {
const ANSI_COLOR = '\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?m';
const SKIP_ANSI = `(?:${ANSI_COLOR})*`;
return str.match(new RegExp(`(?:${SKIP_ANSI}.){1,${size}}`, 'g')) || [];
}
/**
* Get the stream as a TTY if it effectively looks like a valid TTY.
*/
function getTTYStream(stream: UnderlyingStream): ?tty.WriteStream {
if (
stream instanceof tty.WriteStream &&
stream.isTTY &&
stream.columns >= 1
) {
return stream;
}
return null;
}
/**
* We don't just print things to the console, sometimes we also want to show
* and update progress. This utility just ensures the output stays neat: no
* missing newlines, no mangled log lines.
*
* const terminal = Terminal.default;
* terminal.status('Updating... 38%');
* terminal.log('warning: Something happened.');
* terminal.status('Updating, done.');
* terminal.persistStatus();
*
* The final output:
*
* warning: Something happened.
* Updating, done.
*
* Without the status feature, we may get a mangled output:
*
* Updating... 38%warning: Something happened.
* Updating, done.
*
* This is meant to be user-readable and TTY-oriented. We use stdout by default
* because it's more about status information than diagnostics/errors (stderr).
*
* Do not add any higher-level functionality in this class such as "warning" and
* "error" printers, as it is not meant for formatting/reporting. It has the
* single responsibility of handling status messages.
*/
class Terminal {
_logLines: Array<string>;
_nextStatusStr: string;
_scheduleUpdate: () => void;
_statusStr: string;
_stream: UnderlyingStream;
constructor(stream: UnderlyingStream) {
this._logLines = [];
this._nextStatusStr = '';
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
this._scheduleUpdate = throttle(this._update, 33);
this._statusStr = '';
this._stream = stream;
}
/**
* Clear and write the new status, logging in bulk in-between. Doing this in a
* throttled way (in a different tick than the calls to `log()` and
* `status()`) prevents us from repeatedly rewriting the status in case
* `terminal.log()` is called several times.
*/
_update(): void {
const {_statusStr, _stream} = this;
const ttyStream = getTTYStream(_stream);
if (_statusStr === this._nextStatusStr && this._logLines.length === 0) {
return;
}
if (ttyStream != null) {
clearStringBackwards(ttyStream, _statusStr);
}
this._logLines.forEach(line => {
_stream.write(line);
_stream.write('\n');
});
this._logLines = [];
if (ttyStream != null) {
this._nextStatusStr = chunkString(
this._nextStatusStr,
ttyStream.columns,
).join('\n');
_stream.write(this._nextStatusStr);
}
this._statusStr = this._nextStatusStr;
}
/**
* Shows some text that is meant to be overriden later. Return the previous
* status that was shown and is no more. Calling `status()` with no argument
* removes the status altogether. The status is never shown in a
* non-interactive terminal: for example, if the output is redirected to a
* file, then we don't care too much about having a progress bar.
*/
status(format: string, ...args: Array<mixed>): string {
const {_nextStatusStr} = this;
this._nextStatusStr = util.format(format, ...args);
this._scheduleUpdate();
return _nextStatusStr;
}
/**
* Similar to `console.log`, except it moves the status/progress text out of
* the way correctly. In non-interactive terminals this is the same as
* `console.log`.
*/
log(format: string, ...args: Array<mixed>): void {
this._logLines.push(util.format(format, ...args));
this._scheduleUpdate();
}
/**
* Log the current status and start from scratch. This is useful if the last
* status was the last one of a series of updates.
*/
persistStatus(): void {
this.log(this._nextStatusStr);
this._nextStatusStr = '';
}
flush(): void {
// Useful if you're going to start calling console.log/console.error directly
// again; otherwise you could end up with mangled output when the queued
// update starts writing to stream after a delay.
/* $FlowFixMe(>=0.99.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.99 was deployed. To see the error, delete this
* comment and run Flow. */
this._scheduleUpdate.flush();
}
}
module.exports = Terminal;

View File

@@ -0,0 +1,15 @@
"use strict";
function canonicalize(key, value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
return value;
}
const keys = Object.keys(value).sort();
const length = keys.length;
const object = {};
for (let i = 0; i < length; i++) {
object[keys[i]] = value[keys[i]];
}
return object;
}
module.exports = canonicalize;

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
function canonicalize(key: string, value: mixed): mixed {
if (
// eslint-disable-next-line lint/strictly-null
value === null ||
typeof value !== 'object' ||
Array.isArray(value)
) {
return value;
}
const keys = Object.keys(value).sort();
const length = keys.length;
const object: {[string]: mixed} = {};
for (let i = 0; i < length; i++) {
object[keys[i]] = value[keys[i]];
}
return object;
}
module.exports = canonicalize;

View File

@@ -0,0 +1,8 @@
"use strict";
const AmbiguousModuleResolutionError = require("./errors/AmbiguousModuleResolutionError");
const PackageResolutionError = require("./errors/PackageResolutionError");
module.exports = {
AmbiguousModuleResolutionError,
PackageResolutionError,
};

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
const AmbiguousModuleResolutionError = require('./errors/AmbiguousModuleResolutionError');
const PackageResolutionError = require('./errors/PackageResolutionError');
module.exports = {
AmbiguousModuleResolutionError,
PackageResolutionError,
};

View File

@@ -0,0 +1,13 @@
"use strict";
class AmbiguousModuleResolutionError extends Error {
constructor(fromModulePath, hasteError) {
super(
`Ambiguous module resolution from \`${fromModulePath}\`: ` +
hasteError.message
);
this.fromModulePath = fromModulePath;
this.hasteError = hasteError;
}
}
module.exports = AmbiguousModuleResolutionError;

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
import type {DuplicateHasteCandidatesError} from 'metro-file-map';
class AmbiguousModuleResolutionError extends Error {
fromModulePath: string;
hasteError: DuplicateHasteCandidatesError;
constructor(
fromModulePath: string,
hasteError: DuplicateHasteCandidatesError,
) {
super(
`Ambiguous module resolution from \`${fromModulePath}\`: ` +
hasteError.message,
);
this.fromModulePath = fromModulePath;
this.hasteError = hasteError;
}
}
module.exports = AmbiguousModuleResolutionError;

View File

@@ -0,0 +1,20 @@
"use strict";
const { formatFileCandidates } = require("metro-resolver");
class PackageResolutionError extends Error {
constructor(opts) {
const perr = opts.packageError;
super(
`While trying to resolve module \`${opts.targetModuleName}\` from file ` +
`\`${opts.originModulePath}\`, the package ` +
`\`${perr.packageJsonPath}\` was successfully found. However, ` +
"this package itself specifies " +
"a `main` module field that could not be resolved (" +
`\`${perr.mainModulePath}\`. Indeed, none of these files exist:\n\n` +
` * ${formatFileCandidates(perr.fileCandidates)}\n` +
` * ${formatFileCandidates(perr.indexCandidates)}`
);
Object.assign(this, opts);
}
}
module.exports = PackageResolutionError;

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
import type {InvalidPackageError} from 'metro-resolver';
const {formatFileCandidates} = require('metro-resolver');
class PackageResolutionError extends Error {
originModulePath: string;
packageError: InvalidPackageError;
targetModuleName: string;
constructor(opts: {
+originModulePath: string,
+packageError: InvalidPackageError,
+targetModuleName: string,
}) {
const perr = opts.packageError;
super(
`While trying to resolve module \`${opts.targetModuleName}\` from file ` +
`\`${opts.originModulePath}\`, the package ` +
`\`${perr.packageJsonPath}\` was successfully found. However, ` +
'this package itself specifies ' +
'a `main` module field that could not be resolved (' +
`\`${perr.mainModulePath}\`. Indeed, none of these files exist:\n\n` +
` * ${formatFileCandidates(perr.fileCandidates)}\n` +
` * ${formatFileCandidates(perr.indexCandidates)}`,
);
Object.assign(this, opts);
}
}
module.exports = PackageResolutionError;

View File

@@ -0,0 +1,13 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
/// <reference types="node" />
export * from './Terminal';

View File

@@ -0,0 +1,12 @@
"use strict";
const AmbiguousModuleResolutionError = require("./errors/AmbiguousModuleResolutionError");
const PackageResolutionError = require("./errors/PackageResolutionError");
const Logger = require("./Logger");
const Terminal = require("./Terminal");
module.exports = {
AmbiguousModuleResolutionError,
Logger,
PackageResolutionError,
Terminal,
};

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
'use strict';
const AmbiguousModuleResolutionError = require('./errors/AmbiguousModuleResolutionError');
const PackageResolutionError = require('./errors/PackageResolutionError');
const Logger = require('./Logger');
const Terminal = require('./Terminal');
module.exports = {
AmbiguousModuleResolutionError,
Logger,
PackageResolutionError,
Terminal,
};