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,14 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol EXPushTokenListener
- (void)onDidRegisterWithDeviceToken:(NSData *)token;
- (void)onDidFailToRegisterWithError:(NSError *)error;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,24 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <ExpoModulesCore/EXSingletonModule.h>
#import <EXNotifications/EXPushTokenListener.h>
NS_ASSUME_NONNULL_BEGIN
@protocol EXPushTokenManager
- (void)addListener:(id<EXPushTokenListener>)listener;
- (void)removeListener:(id<EXPushTokenListener>)listener;
@end
@interface EXPushTokenManager : EXSingletonModule <UIApplicationDelegate, EXPushTokenManager>
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,63 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <EXNotifications/EXPushTokenManager.h>
#import <ExpoModulesCore/EXDefines.h>
@interface EXPushTokenManager ()
@property (nonatomic, strong) NSPointerArray *listeners;
@end
@implementation EXPushTokenManager
EX_REGISTER_SINGLETON_MODULE(PushTokenManager);
- (instancetype)init
{
if (self = [super init]) {
_listeners = [NSPointerArray weakObjectsPointerArray];
}
return self;
}
# pragma mark - UIApplicationDelegate
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
for (int i = 0; i < _listeners.count; i++) {
id pointer = [_listeners pointerAtIndex:i];
[pointer onDidRegisterWithDeviceToken:deviceToken];
}
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
for (int i = 0; i < _listeners.count; i++) {
id pointer = [_listeners pointerAtIndex:i];
[pointer onDidFailToRegisterWithError:error];
}
}
# pragma mark - Listeners
- (void)addListener:(id<EXPushTokenListener>)listener
{
[_listeners addPointer:(__bridge void * _Nullable)(listener)];
}
- (void)removeListener:(id<EXPushTokenListener>)listener
{
for (int i = 0; i < _listeners.count; i++) {
id pointer = [_listeners pointerAtIndex:i];
if (pointer == (__bridge void * _Nullable)(listener) || !pointer) {
[_listeners removePointerAtIndex:i];
i--;
}
}
// compact doesn't work, that's why we need the `|| !pointer` above
// http://www.openradar.me/15396578
[_listeners compact];
}
@end

View File

@@ -0,0 +1,9 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <ExpoModulesCore/EXExportedModule.h>
#import <ExpoModulesCore/EXEventEmitter.h>
#import <ExpoModulesCore/EXModuleRegistryConsumer.h>
#import <EXNotifications/EXPushTokenListener.h>
@interface EXPushTokenModule : EXExportedModule <EXEventEmitter, EXModuleRegistryConsumer, EXPushTokenListener>
@end

View File

@@ -0,0 +1,154 @@
// Copyright 2018-present 650 Industries. All rights reserved.
#import <EXNotifications/EXPushTokenModule.h>
#import <EXNotifications/EXPushTokenManager.h>
#import <ExpoModulesCore/EXEventEmitterService.h>
static NSString * const onDevicePushTokenEventName = @"onDevicePushToken";
@interface EXPushTokenModule ()
@property (nonatomic, weak) id<EXPushTokenManager> pushTokenManager;
@property (nonatomic, assign) BOOL isListening;
@property (nonatomic, assign) BOOL isBeingObserved;
@property (nonatomic, assign) BOOL isSettlingPromise;
@property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
@property (nonatomic, strong) EXPromiseResolveBlock getDevicePushTokenResolver;
@property (nonatomic, strong) EXPromiseRejectBlock getDevicePushTokenRejecter;
@end
@implementation EXPushTokenModule
EX_EXPORT_MODULE(ExpoPushTokenManager);
# pragma mark - Exported methods
EX_EXPORT_METHOD_AS(getDevicePushTokenAsync,
getDevicePushTokenResolving:(EXPromiseResolveBlock)resolve rejecting:(EXPromiseRejectBlock)reject)
{
if (_getDevicePushTokenRejecter) {
reject(@"E_AWAIT_PROMISE", @"Another async call to this method is in progress. Await the first Promise.", nil);
return;
}
_getDevicePushTokenResolver = resolve;
_getDevicePushTokenRejecter = reject;
[self setIsSettlingPromise:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications];
});
}
EX_EXPORT_METHOD_AS(unregisterForNotificationsAsync,
unregisterForNotificationsAsync:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject)
{
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
resolve(nil);
}
# pragma mark - EXModuleRegistryConsumer
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
{
_eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
_pushTokenManager = [moduleRegistry getSingletonModuleForName:@"PushTokenManager"];
}
# pragma mark - EXEventEmitter
- (NSArray<NSString *> *)supportedEvents
{
return @[onDevicePushTokenEventName];
}
- (void)startObserving
{
[self setIsBeingObserved:YES];
}
- (void)stopObserving
{
[self setIsBeingObserved:NO];
}
- (BOOL)shouldListen
{
return _isBeingObserved || _isSettlingPromise;
}
- (void)updateListeningState
{
if ([self shouldListen] && !_isListening) {
[_pushTokenManager addListener:self];
_isListening = YES;
} else if (![self shouldListen] && _isListening) {
[_pushTokenManager removeListener:self];
_isListening = NO;
}
}
# pragma mark - EXPushTokenListener
- (void)onDidRegisterWithDeviceToken:(NSData *)devicePushToken
{
NSMutableString *stringToken = [NSMutableString string];
const char *bytes = [devicePushToken bytes];
for (int i = 0; i < [devicePushToken length]; i++) {
[stringToken appendFormat:@"%02.2hhx", bytes[i]];
}
if (_getDevicePushTokenResolver) {
_getDevicePushTokenResolver(stringToken);
[self onGetDevicePushTokenPromiseSettled];
}
if (_isBeingObserved) {
[_eventEmitter sendEventWithName:onDevicePushTokenEventName
body:@{ @"devicePushToken": stringToken }];
}
}
- (void)onDidFailToRegisterWithError:(NSError *)error
{
if (_getDevicePushTokenRejecter) {
NSString *message = @"Notification registration failed: ";
// A common error, localizedDescription may not be helpful.
if (error.code == 3000 && [NSCocoaErrorDomain isEqualToString:error.domain]) {
message = [message stringByAppendingString:@"\"Push Notifications\" capability hasn't been added to the app in current environment: "];
}
message = [message stringByAppendingFormat:@"%@", error.localizedDescription];
_getDevicePushTokenRejecter(@"E_REGISTRATION_FAILED", message, error);
[self onGetDevicePushTokenPromiseSettled];
}
}
- (void)onGetDevicePushTokenPromiseSettled
{
_getDevicePushTokenResolver = nil;
_getDevicePushTokenRejecter = nil;
[self setIsSettlingPromise:NO];
}
# pragma mark - Internal state
- (void)setIsBeingObserved:(BOOL)isBeingObserved
{
_isBeingObserved = isBeingObserved;
[self updateListeningState];
}
- (void)setIsSettlingPromise:(BOOL)isSettlingPromise
{
_isSettlingPromise = isSettlingPromise;
[self updateListeningState];
}
@end