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,131 @@
var fs = require('fs'),
mm = require('minimatch'),
path = require('path');
/**
* merge two objects by extending target object with source object
* @param target object to merge
* @param source object to merge
* @param {Boolean} [modify] whether to modify the target
* @returns {Object} extended object
*/
function extend(target, source, modify) {
var result = target ? modify ? target : extend({}, target, true) : {};
if (!source) return result;
for (var key in source) {
if (source.hasOwnProperty(key) && source[key] !== undefined) {
result[key] = source[key];
}
}
return result;
}
/**
* determine if a string is contained within an array or matches a regular expression
* @param {String} str string to match
* @param {Array|Regex} match array or regular expression to match against
* @returns {Boolean} whether there is a match
*/
function matches(str, match) {
if (Array.isArray(match)) {
var l = match.length;
for( var s=0; s < l; s++) {
if ( mm(str,match[s])) {
return true;
}
}
return false;
}
return match.test(str);
}
/**
* read files and call a function with the contents of each file
* @param {String} dir path of dir containing the files to be read
* @param {String} encoding file encoding (default is 'utf8')
* @param {Object} options options hash for encoding, recursive, and match/exclude
* @param {Function(error, string)} callback callback for each files content
* @param {Function(error)} complete fn to call when finished
*/
function readFilesStream(dir, options, callback, complete) {
if (typeof options === 'function') {
complete = callback;
callback = options;
options = {};
}
if (typeof options === 'string') options = {
encoding: options
};
options = extend({
recursive: true,
encoding: 'utf8',
doneOnErr: true
}, options);
var files = [];
var done = function(err) {
if (typeof complete === 'function') {
if (err) return complete(err);
complete(null, files);
}
};
fs.readdir(dir, function(err, list) {
if (err)  {
if (options.doneOnErr === true) {
if (err.code === 'EACCES') return done();
return done(err);
}
}
var i = 0;
if (options.reverse === true ||
(typeof options.sort == 'string' &&
(/reverse|desc/i).test(options.sort))) {
list = list.reverse();
} else if (options.sort !== false) list = list.sort();
(function next() {
var filename = list[i++];
if (!filename) return done(null, files);
var file = path.join(dir, filename);
fs.stat(file, function(err, stat) {
if (err && options.doneOnErr === true) return done(err);
if (stat && stat.isDirectory()) {
if (options.recursive) {
if (options.matchDir && !matches(filename, options.matchDir)) return next();
if (options.excludeDir && matches(filename, options.excludeDir)) return next();
readFilesStream(file, options, callback, function(err, sfiles) {
if (err && options.doneOnErr === true) return done(err);
files = files.concat(sfiles);
next();
});
} else next();
} else if (stat && stat.isFile()) {
if (options.match && !matches(filename, options.match)) return next();
if (options.exclude && matches(filename, options.exclude)) return next();
if (options.filter && !options.filter(filename)) return next();
if (options.shortName) files.push(filename);
else files.push(file);
var stream = fs.createReadStream(file);
if (options.encoding !== null) {
stream.setEncoding(options.encoding);
}
stream.on('error',function(err) {
if (options.doneOnErr === true) return done(err);
next();
});
if (callback.length > 3)
if (options.shortName) callback(null, stream, filename, next);
else callback(null, stream, file, next);
else callback(null, stream, next);
}
else {
next();
}
});
})();
});
}
module.exports = readFilesStream;