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,67 @@
import Recognizer from '../recognizerjs/recognizer-constructor';
import {
STATE_BEGAN,
STATE_CHANGED,
STATE_CANCELLED,
STATE_ENDED,
STATE_FAILED
} from '../recognizerjs/recognizer-consts';
import {
INPUT_CANCEL,
INPUT_END
} from '../inputjs/input-consts';
/**
* @private
* This recognizer is just used as a base for the simple attribute recognizers.
* @constructor
* @extends Recognizer
*/
export default class AttrRecognizer extends Recognizer {
constructor(options = {}) {
super({
pointers: 1,
...options,
});
}
/**
* @private
* Used to check if it the recognizer receives valid input, like input.distance > 10.
* @memberof AttrRecognizer
* @param {Object} input
* @returns {Boolean} recognized
*/
attrTest(input) {
let optionPointers = this.options.pointers;
return optionPointers === 0 || input.pointers.length === optionPointers;
}
/**
* @private
* Process the input and return the state for the recognizer
* @memberof AttrRecognizer
* @param {Object} input
* @returns {*} State
*/
process(input) {
let { state } = this;
let { eventType } = input;
let isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
let isValid = this.attrTest(input);
// on cancel input and we've recognized before, return STATE_CANCELLED
if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
return state | STATE_CANCELLED;
} else if (isRecognized || isValid) {
if (eventType & INPUT_END) {
return state | STATE_ENDED;
} else if (!(state & STATE_BEGAN)) {
return STATE_BEGAN;
}
return state | STATE_CHANGED;
}
return STATE_FAILED;
}
}

View File

@@ -0,0 +1,89 @@
import AttrRecognizer from './attribute';
import {
DIRECTION_ALL,
DIRECTION_HORIZONTAL,
DIRECTION_VERTICAL,
DIRECTION_NONE,
DIRECTION_UP,
DIRECTION_DOWN,
DIRECTION_LEFT,
DIRECTION_RIGHT
} from '../inputjs/input-consts';
import { STATE_BEGAN } from '../recognizerjs/recognizer-consts';
import { TOUCH_ACTION_PAN_X,TOUCH_ACTION_PAN_Y } from '../touchactionjs/touchaction-Consts';
import directionStr from '../recognizerjs/direction-str';
/**
* @private
* Pan
* Recognized when the pointer is down and moved in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/
export default class PanRecognizer extends AttrRecognizer {
constructor(options = {}) {
super({
event: 'pan',
threshold: 10,
pointers: 1,
direction: DIRECTION_ALL,
...options,
});
this.pX = null;
this.pY = null;
}
getTouchAction() {
let { options:{ direction } } = this;
let actions = [];
if (direction & DIRECTION_HORIZONTAL) {
actions.push(TOUCH_ACTION_PAN_Y);
}
if (direction & DIRECTION_VERTICAL) {
actions.push(TOUCH_ACTION_PAN_X);
}
return actions;
}
directionTest(input) {
let { options } = this;
let hasMoved = true;
let { distance } = input;
let { direction } = input;
let x = input.deltaX;
let y = input.deltaY;
// lock to axis?
if (!(direction & options.direction)) {
if (options.direction & DIRECTION_HORIZONTAL) {
direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
hasMoved = x !== this.pX;
distance = Math.abs(input.deltaX);
} else {
direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
hasMoved = y !== this.pY;
distance = Math.abs(input.deltaY);
}
}
input.direction = direction;
return hasMoved && distance > options.threshold && direction & options.direction;
}
attrTest(input) {
return AttrRecognizer.prototype.attrTest.call(this, input) && // replace with a super call
(this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
}
emit(input) {
this.pX = input.deltaX;
this.pY = input.deltaY;
let direction = directionStr(input.direction);
if (direction) {
input.additionalEvent = this.options.event + direction;
}
super.emit(input);
}
}

View File

@@ -0,0 +1,38 @@
import AttrRecognizer from './attribute';
import { TOUCH_ACTION_NONE } from '../touchactionjs/touchaction-Consts';
import { STATE_BEGAN } from '../recognizerjs/recognizer-consts';
/**
* @private
* Pinch
* Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
* @constructor
* @extends AttrRecognizer
*/
export default class PinchRecognizer extends AttrRecognizer {
constructor(options = {}) {
super({
event: 'pinch',
threshold: 0,
pointers: 2,
...options,
});
}
getTouchAction() {
return [TOUCH_ACTION_NONE];
}
attrTest(input) {
return super.attrTest(input) &&
(Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
}
emit(input) {
if (input.scale !== 1) {
let inOut = input.scale < 1 ? 'in' : 'out';
input.additionalEvent = this.options.event + inOut;
}
super.emit(input);
}
}

View File

@@ -0,0 +1,79 @@
import Recognizer from '../recognizerjs/recognizer-constructor';
import {
STATE_RECOGNIZED,
STATE_FAILED
} from '../recognizerjs/recognizer-consts';
import { now } from '../utils/utils-consts';
import { TOUCH_ACTION_AUTO } from '../touchactionjs/touchaction-Consts';
import {
INPUT_START,
INPUT_END,
INPUT_CANCEL
} from '../inputjs/input-consts';
/**
* @private
* Press
* Recognized when the pointer is down for x ms without any movement.
* @constructor
* @extends Recognizer
*/
export default class PressRecognizer extends Recognizer {
constructor(options = {}) {
super({
event: 'press',
pointers: 1,
time: 251, // minimal time of the pointer to be pressed
threshold: 9, // a minimal movement is ok, but keep it low
...options,
});
this._timer = null;
this._input = null;
}
getTouchAction() {
return [TOUCH_ACTION_AUTO];
}
process(input) {
let { options } = this;
let validPointers = input.pointers.length === options.pointers;
let validMovement = input.distance < options.threshold;
let validTime = input.deltaTime > options.time;
this._input = input;
// we only allow little movement
// and we've reached an end event, so a tap is possible
if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
this.reset();
} else if (input.eventType & INPUT_START) {
this.reset();
this._timer = setTimeout(() => {
this.state = STATE_RECOGNIZED;
this.tryEmit();
}, options.time);
} else if (input.eventType & INPUT_END) {
return STATE_RECOGNIZED;
}
return STATE_FAILED;
}
reset() {
clearTimeout(this._timer);
}
emit(input) {
if (this.state !== STATE_RECOGNIZED) {
return;
}
if (input && (input.eventType & INPUT_END)) {
this.manager.emit(`${this.options.event}up`, input);
} else {
this._input.timeStamp = now();
this.manager.emit(this.options.event, this._input);
}
}
}

View File

@@ -0,0 +1,30 @@
import AttrRecognizer from './attribute';
import { TOUCH_ACTION_NONE } from '../touchactionjs/touchaction-Consts';
import { STATE_BEGAN } from '../recognizerjs/recognizer-consts';
/**
* @private
* Rotate
* Recognized when two or more pointer are moving in a circular motion.
* @constructor
* @extends AttrRecognizer
*/
export default class RotateRecognizer extends AttrRecognizer {
constructor(options = {}) {
super( {
event: 'rotate',
threshold: 0,
pointers: 2,
...options,
});
}
getTouchAction() {
return [TOUCH_ACTION_NONE];
}
attrTest(input) {
return super.attrTest(input) &&
(Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
}
}

View File

@@ -0,0 +1,58 @@
import AttrRecognizer from '../recognizers/attribute';
import { abs } from '../utils/utils-consts';
import { DIRECTION_HORIZONTAL,DIRECTION_VERTICAL } from '../inputjs/input-consts';
import PanRecognizer from './pan';
import { INPUT_END } from '../inputjs/input-consts';
import directionStr from '../recognizerjs/direction-str';
/**
* @private
* Swipe
* Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
* @constructor
* @extends AttrRecognizer
*/
export default class SwipeRecognizer extends AttrRecognizer {
constructor(options = {}) {
super({
event: 'swipe',
threshold: 10,
velocity: 0.3,
direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
pointers: 1,
...options,
});
}
getTouchAction() {
return PanRecognizer.prototype.getTouchAction.call(this);
}
attrTest(input) {
let { direction } = this.options;
let velocity;
if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
velocity = input.overallVelocity;
} else if (direction & DIRECTION_HORIZONTAL) {
velocity = input.overallVelocityX;
} else if (direction & DIRECTION_VERTICAL) {
velocity = input.overallVelocityY;
}
return super.attrTest(input) &&
direction & input.offsetDirection &&
input.distance > this.options.threshold &&
input.maxPointers === this.options.pointers &&
abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
}
emit(input) {
let direction = directionStr(input.offsetDirection);
if (direction) {
this.manager.emit(this.options.event + direction, input);
}
this.manager.emit(this.options.event, input);
}
}

View File

@@ -0,0 +1,120 @@
import Recognizer from '../recognizerjs/recognizer-constructor';
import { TOUCH_ACTION_MANIPULATION } from '../touchactionjs/touchaction-Consts';
import {INPUT_START,INPUT_END } from '../inputjs/input-consts';
import {
STATE_RECOGNIZED,
STATE_BEGAN,
STATE_FAILED
} from '../recognizerjs/recognizer-consts';
import getDistance from '../inputjs/get-distance';
/**
* @private
* A tap is recognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
* between the given interval and position. The delay option can be used to recognize multi-taps without firing
* a single tap.
*
* The eventData from the emitted event contains the property `tapCount`, which contains the amount of
* multi-taps being recognized.
* @constructor
* @extends Recognizer
*/
export default class TapRecognizer extends Recognizer {
constructor(options = {}) {
super({
event: 'tap',
pointers: 1,
taps: 1,
interval: 300, // max time between the multi-tap taps
time: 250, // max time of the pointer to be down (like finger on the screen)
threshold: 9, // a minimal movement is ok, but keep it low
posThreshold: 10, // a multi-tap can be a bit off the initial position
...options,
});
// previous time and center,
// used for tap counting
this.pTime = false;
this.pCenter = false;
this._timer = null;
this._input = null;
this.count = 0;
}
getTouchAction() {
return [TOUCH_ACTION_MANIPULATION];
}
process(input) {
let { options } = this;
let validPointers = input.pointers.length === options.pointers;
let validMovement = input.distance < options.threshold;
let validTouchTime = input.deltaTime < options.time;
this.reset();
if ((input.eventType & INPUT_START) && (this.count === 0)) {
return this.failTimeout();
}
// we only allow little movement
// and we've reached an end event, so a tap is possible
if (validMovement && validTouchTime && validPointers) {
if (input.eventType !== INPUT_END) {
return this.failTimeout();
}
let validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
let validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
this.pTime = input.timeStamp;
this.pCenter = input.center;
if (!validMultiTap || !validInterval) {
this.count = 1;
} else {
this.count += 1;
}
this._input = input;
// if tap count matches we have recognized it,
// else it has began recognizing...
let tapCount = this.count % options.taps;
if (tapCount === 0) {
// no failing requirements, immediately trigger the tap event
// or wait as long as the multitap interval to trigger
if (!this.hasRequireFailures()) {
return STATE_RECOGNIZED;
} else {
this._timer = setTimeout(() => {
this.state = STATE_RECOGNIZED;
this.tryEmit();
}, options.interval);
return STATE_BEGAN;
}
}
}
return STATE_FAILED;
}
failTimeout() {
this._timer = setTimeout(() => {
this.state = STATE_FAILED;
}, this.options.interval);
return STATE_FAILED;
}
reset() {
clearTimeout(this._timer);
}
emit() {
if (this.state === STATE_RECOGNIZED) {
this._input.tapCount = this.count;
this.manager.emit(this.options.event, this._input);
}
}
}