Files
Eric FELIXINE e30ae8ed09 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
2026-06-01 18:00:35 -04:00

167 lines
3.4 KiB
JavaScript
Executable File

'use strict';
const Clone = require('@hapi/hoek/lib/clone');
const Reach = require('@hapi/hoek/lib/reach');
const Common = require('./common');
const internals = {
value: Symbol('value')
};
module.exports = internals.State = class {
constructor(path, ancestors, state) {
this.path = path;
this.ancestors = ancestors; // [parent, ..., root]
this.mainstay = state.mainstay;
this.schemas = state.schemas; // [current, ..., root]
this.debug = null;
}
localize(path, ancestors = null, schema = null) {
const state = new internals.State(path, ancestors, this);
if (schema &&
state.schemas) {
state.schemas = [internals.schemas(schema), ...state.schemas];
}
return state;
}
nest(schema, debug) {
const state = new internals.State(this.path, this.ancestors, this);
state.schemas = state.schemas && [internals.schemas(schema), ...state.schemas];
state.debug = debug;
return state;
}
shadow(value, reason) {
this.mainstay.shadow = this.mainstay.shadow || new internals.Shadow();
this.mainstay.shadow.set(this.path, value, reason);
}
snapshot() {
if (this.mainstay.shadow) {
this._snapshot = Clone(this.mainstay.shadow.node(this.path));
}
this.mainstay.snapshot();
}
restore() {
if (this.mainstay.shadow) {
this.mainstay.shadow.override(this.path, this._snapshot);
this._snapshot = undefined;
}
this.mainstay.restore();
}
commit() {
if (this.mainstay.shadow) {
this.mainstay.shadow.override(this.path, this._snapshot);
this._snapshot = undefined;
}
this.mainstay.commit();
}
};
internals.schemas = function (schema) {
if (Common.isSchema(schema)) {
return { schema };
}
return schema;
};
internals.Shadow = class {
constructor() {
this._values = null;
}
set(path, value, reason) {
if (!path.length) { // No need to store root value
return;
}
if (reason === 'strip' &&
typeof path[path.length - 1] === 'number') { // Cannot store stripped array values (due to shift)
return;
}
this._values = this._values || new Map();
let node = this._values;
for (let i = 0; i < path.length; ++i) {
const segment = path[i];
let next = node.get(segment);
if (!next) {
next = new Map();
node.set(segment, next);
}
node = next;
}
node[internals.value] = value;
}
get(path) {
const node = this.node(path);
if (node) {
return node[internals.value];
}
}
node(path) {
if (!this._values) {
return;
}
return Reach(this._values, path, { iterables: true });
}
override(path, node) {
if (!this._values) {
return;
}
const parents = path.slice(0, -1);
const own = path[path.length - 1];
const parent = Reach(this._values, parents, { iterables: true });
if (node) {
parent.set(own, node);
return;
}
if (parent) {
parent.delete(own);
}
}
};