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,12 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXExportedModule.h>
#import <ExpoModulesCore/EXEventEmitter.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
#import <EXNotifications/EXNotificationsDelegate.h>
#import <EXNotifications/EXSingleNotificationHandlerTask.h>
@interface EXNotificationsHandlerModule : EXExportedModule <EXEventEmitter, EXModuleRegistryConsumer, EXNotificationsDelegate, EXSingleNotificationHandlerTaskDelegate>
@end

View File

@@ -0,0 +1,109 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <EXNotifications/EXNotificationsHandlerModule.h>
#import <EXNotifications/EXNotificationSerializer.h>
#import <EXNotifications/EXNotificationCenterDelegate.h>
#import <ExpoModulesCore/EXEventEmitterService.h>
@interface EXNotificationsHandlerModule ()
@property (nonatomic, weak) id<EXNotificationCenterDelegate> notificationCenterDelegate;
@property (nonatomic, assign) BOOL isListening;
@property (nonatomic, assign) BOOL isBeingObserved;
@property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
@property (nonatomic, strong) NSMutableDictionary<NSString *, EXSingleNotificationHandlerTask *> *tasksMap;
@end
@implementation EXNotificationsHandlerModule
EX_EXPORT_MODULE(ExpoNotificationsHandlerModule);
- (instancetype)init
{
if (self = [super init]) {
_tasksMap = [NSMutableDictionary dictionary];
}
return self;
}
# pragma mark - Exported methods
EX_EXPORT_METHOD_AS(handleNotificationAsync,
handleNotificationAsync:(NSString *)identifier withBehavior:(NSDictionary *)behavior resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject)
{
EXSingleNotificationHandlerTask *task = _tasksMap[identifier];
if (!task) {
NSString *message = [NSString stringWithFormat:@"Failed to handle notification %@, it has already been handled.", identifier];
return reject(@"ERR_NOTIFICATION_HANDLED", message, nil);
}
NSError *error = [task handleResponse:behavior];
if (error) {
return reject(error.userInfo[@"code"], error.userInfo[@"message"], error);
} else {
resolve(nil);
}
}
# pragma mark - EXModuleRegistryConsumer
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
{
_eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
_notificationCenterDelegate = [moduleRegistry getSingletonModuleForName:@"NotificationCenterDelegate"];
}
# pragma mark - EXEventEmitter
- (NSArray<NSString *> *)supportedEvents
{
return [EXSingleNotificationHandlerTask eventNames];
}
- (void)startObserving
{
[self setIsBeingObserved:YES];
}
- (void)stopObserving
{
[self setIsBeingObserved:NO];
}
- (void)setIsBeingObserved:(BOOL)isBeingObserved
{
_isBeingObserved = isBeingObserved;
BOOL shouldListen = _isBeingObserved;
if (shouldListen && !_isListening) {
[_notificationCenterDelegate addDelegate:self];
_isListening = YES;
} else if (!shouldListen && _isListening) {
[_notificationCenterDelegate removeDelegate:self];
_isListening = NO;
}
}
# pragma mark - EXNotificationsDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
EXSingleNotificationHandlerTask *task = [[EXSingleNotificationHandlerTask alloc] initWithEventEmitter:_eventEmitter
notification:notification
completionHandler:completionHandler
delegate:self];
[_tasksMap setObject:task forKey:task.identifier];
[task start];
}
# pragma mark - EXSingleNotificationHandlerTaskDelegate
- (void)taskDidFinish:(EXSingleNotificationHandlerTask *)task
{
[_tasksMap removeObjectForKey:task.identifier];
}
@end

View File

@@ -0,0 +1,33 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <ExpoModulesCore/EXEventEmitterService.h>
#import <UserNotifications/UserNotifications.h>
NS_ASSUME_NONNULL_BEGIN
@class EXSingleNotificationHandlerTask;
@protocol EXSingleNotificationHandlerTaskDelegate
- (void)taskDidFinish:(EXSingleNotificationHandlerTask *)task;
@end
@interface EXSingleNotificationHandlerTask : NSObject
+ (NSArray<NSString *> *)eventNames;
- (instancetype)initWithEventEmitter:(id<EXEventEmitterService>)eventEmitter
notification:(UNNotification *)notification
completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
delegate:(id<EXSingleNotificationHandlerTaskDelegate>)delegate;
- (NSString *)identifier;
- (void)start;
- (nullable NSError *)handleResponse:(NSDictionary *)response;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,124 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <EXNotifications/EXSingleNotificationHandlerTask.h>
#import <EXNotifications/EXNotificationSerializer.h>
static NSString * const onHandleNotification = @"onHandleNotification";
static NSString * const onHandleNotificationTimeout = @"onHandleNotificationTimeout";
static NSString * const shouldShowAlertKey = @"shouldShowAlert";
static NSString * const shouldPlaySoundKey = @"shouldPlaySound";
static NSString * const shouldSetBadgeKey = @"shouldSetBadge";
static NSTimeInterval const secondsToTimeout = 3;
static NSString * const EXNotificationHandlerErrorDomain = @"expo.notifications.handler";
@interface EXSingleNotificationHandlerTask ()
@property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
@property (nonatomic, strong) UNNotification *notification;
@property (nonatomic, copy) void (^completionHandler)(UNNotificationPresentationOptions);
@property (nonatomic, weak) id<EXSingleNotificationHandlerTaskDelegate> delegate;
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation EXSingleNotificationHandlerTask
+ (NSArray<NSString *> *)eventNames
{
return @[onHandleNotification, onHandleNotificationTimeout];
}
- (instancetype)initWithEventEmitter:(id<EXEventEmitterService>)eventEmitter
notification:(UNNotification *)notification
completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
delegate:(nonnull id<EXSingleNotificationHandlerTaskDelegate>)delegate
{
if (self = [super init]) {
_eventEmitter = eventEmitter;
_notification = notification;
_completionHandler = completionHandler;
_delegate = delegate;
}
return self;
}
- (NSString *)identifier
{
return _notification.request.identifier;
}
- (void)start
{
[_eventEmitter sendEventWithName:onHandleNotification body:@{
@"id": _notification.request.identifier,
@"notification": [EXNotificationSerializer serializedNotification:_notification]
}];
_timer = [NSTimer scheduledTimerWithTimeInterval:secondsToTimeout target:self selector:@selector(handleTimeout) userInfo:nil repeats:NO];
}
- (nullable NSError *)handleResponse:(NSDictionary *)response
{
@synchronized (self) {
NSError *maybeError = [self callCompletionHandlerWithOptions:[self presentationOptionsFromResponse:response]];
[self finish];
return maybeError;
}
}
- (void)handleTimeout
{
@synchronized (self) {
[_eventEmitter sendEventWithName:onHandleNotificationTimeout body:@{
@"id": _notification.request.identifier,
@"notification": [EXNotificationSerializer serializedNotification:_notification]
}];
[self callCompletionHandlerWithOptions:UNNotificationPresentationOptionNone];
[self finish];
}
}
- (nullable NSError *)callCompletionHandlerWithOptions:(UNNotificationPresentationOptions)options
{
if (_completionHandler) {
_completionHandler(options);
_completionHandler = nil;
return nil;
} else {
return [NSError errorWithDomain:EXNotificationHandlerErrorDomain code:-1 userInfo:@{
@"code": @"ERR_NOTIFICATION_RESPONSE_TIMEOUT",
@"message": @"Notification has already been handled. Most probably the request has timed out."
}];
}
}
- (void)finish
{
[_timer invalidate];
_timer = nil;
[_delegate taskDidFinish:self];
}
- (UNNotificationPresentationOptions)presentationOptionsFromResponse:(NSDictionary *)response
{
UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone;
// TODO(iOS 14): use UNNotificationPresentationOptionList and UNNotificationPresentationOptionBanner
if ([response[shouldShowAlertKey] boolValue]) {
options |= UNNotificationPresentationOptionAlert;
}
if ([response[shouldPlaySoundKey] boolValue]) {
options |= UNNotificationPresentationOptionSound;
}
if ([response[shouldSetBadgeKey] boolValue]) {
options |= UNNotificationPresentationOptionBadge;
}
return options;
}
@end