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,19 @@
import ExpoModulesCore
internal class UrlDocumentDirectoryException: Exception {
override var reason: String {
"Unable to get url for document directory"
}
}
internal class InstallationTimeException: Exception {
override var reason: String {
"Unable to get installation time of this application"
}
}
internal class DateCastException: Exception {
override var reason: String {
"Invalid date format"
}
}

View File

@@ -0,0 +1,49 @@
// Copyright 2018-present 650 Industries. All rights reserved.
import ExpoModulesCore
public class ApplicationModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoApplication")
Constants {
let infoPlist = Bundle.main.infoDictionary
return [
"applicationName": infoPlist?["CFBundleDisplayName"],
"applicationId": infoPlist?["CFBundleIdentifier"],
"nativeApplicationVersion": infoPlist?["CFBundleShortVersionString"],
"nativeBuildVersion": infoPlist?["CFBundleVersion"]
]
}
AsyncFunction("getIosIdForVendorAsync") { () -> String? in
return UIDevice.current.identifierForVendor?.uuidString
}
AsyncFunction("getInstallationTimeAsync") { () -> Double in
guard let urlToDocumentsFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
throw UrlDocumentDirectoryException()
}
do {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: urlToDocumentsFolder.path)
// Uses required reason API based on the following reason: C617.1
if let installDate = fileAttributes[FileAttributeKey.creationDate] as? Date {
return installDate.timeIntervalSince1970 * 1000
}
throw DateCastException()
} catch {
throw InstallationTimeException()
}
}
AsyncFunction("getApplicationReleaseTypeAsync") { () -> Int in
let mainProvisioningProfile = EXProvisioningProfile.main()
return mainProvisioningProfile.appReleaseType().rawValue
}
AsyncFunction("getPushNotificationServiceEnvironmentAsync") { () -> String? in
let mainProvisioningProfile = EXProvisioningProfile.main()
return mainProvisioningProfile.notificationServiceEnvironment()
}
}
}

View File

@@ -0,0 +1,34 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'EXApplication'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = { :ios => '13.4', :tvos => '13.4'}
s.swift_version = '5.4'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
s.resource_bundles = {'ExpoApplication_privacy' => ['PrivacyInfo.xcprivacy']}
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "#{s.name}/**/*.h"
s.vendored_frameworks = "#{s.name}.xcframework"
else
s.source_files = "**/*.{h,m,swift}"
end
end

View File

@@ -0,0 +1,22 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import <Foundation/Foundation.h>
// Keep in sync with ApplicationReleaseType in JS
typedef NS_ENUM(NSInteger, EXAppReleaseType) {
EXAppReleaseTypeUnknown,
EXAppReleaseSimulator,
EXAppReleaseEnterprise,
EXAppReleaseDev,
EXAppReleaseAdHoc,
EXAppReleaseAppStore
};
@interface EXProvisioningProfile : NSObject
+ (nonnull instancetype)mainProvisioningProfile;
- (EXAppReleaseType)appReleaseType;
- (nullable NSString *)notificationServiceEnvironment;
@end

View File

@@ -0,0 +1,134 @@
// Copyright 2015-present 650 Industries. All rights reserved.
#import "EXProvisioningProfile.h"
@implementation EXProvisioningProfile {
NSDictionary *_plist;
}
+ (nonnull instancetype)mainProvisioningProfile
{
static EXProvisioningProfile *profile;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *plist = [self _readProvisioningProfilePlist];
profile = [[self alloc] initWithPlist:plist];
});
return profile;
}
- (instancetype)initWithPlist:(NSDictionary *)plist
{
if (self = [super init]) {
_plist = plist;
}
return self;
}
- (nullable NSString *)notificationServiceEnvironment
{
if (!_plist) {
return nil;
}
NSDictionary *entitlements = _plist[@"Entitlements"];
NSString *apsEnvironment = entitlements[@"aps-environment"];
return apsEnvironment;
}
- (EXAppReleaseType)appReleaseType {
NSString *provisioningPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
if (!provisioningPath) {
// provisioning profile does not exist
#if TARGET_IPHONE_SIMULATOR
return EXAppReleaseSimulator;
#else
return EXAppReleaseAppStore;
#endif
}
NSDictionary *mobileProvision = _plist;
if (!mobileProvision) {
// failure to read other than it simply not existing
return EXAppReleaseTypeUnknown;
} else if ([[mobileProvision objectForKey:@"ProvisionsAllDevices"] boolValue]) {
// enterprise distribution contains ProvisionsAllDevices - true
return EXAppReleaseEnterprise;
} else if ([mobileProvision objectForKey:@"ProvisionedDevices"] && [[mobileProvision objectForKey:@"ProvisionedDevices"] count] > 0) {
// development contains UDIDs and get-task-allow is true
// ad hoc contains UDIDs and get-task-allow is false
NSDictionary *entitlements = [mobileProvision objectForKey:@"Entitlements"];
if ([[entitlements objectForKey:@"get-task-allow"] boolValue]) {
return EXAppReleaseDev;
} else {
return EXAppReleaseAdHoc;
}
} else {
// app store contains no UDIDs (if the file exists at all?)
return EXAppReleaseAppStore;
}
}
/** embedded.mobileprovision plist format:
AppIDName, // string TextDetective
ApplicationIdentifierPrefix[], // [ string - 66PK3K3KEV ]
CreationData, // date 2013-01-17T14:18:05Z
DeveloperCertificates[], // [ data ]
Entitlements {
application-identifier // string - 66PK3K3KEV.com.blindsight.textdetective
get-task-allow // true or false
keychain-access-groups[] // [ string - 66PK3K3KEV.* ]
},
ExpirationDate, // date 2014-01-17T14:18:05Z
Name, // string Barrierefreikommunizieren (name assigned to the provisioning profile used)
ProvisionedDevices[], // [ string.... ]
TeamIdentifier[], // [string HHBT96X2EX ]
TeamName, // string The Blindsight Corporation
TimeToLive, // integer - 365
UUID, // string 79F37E8E-CC8D-4819-8C13-A678479211CE
Version, // integer 1
ProvisionsAllDevices // true or false ***NB: not sure if this is where this is
*/
+ (NSDictionary *)_readProvisioningProfilePlist
{
NSString *profilePath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
if (!profilePath) {
return nil;
}
NSError *error;
NSString *profileString = [NSString stringWithContentsOfFile:profilePath encoding:NSASCIIStringEncoding error:&error];
if (!profileString) {
NSLog(@"Error reading provisioning profile: %@", error.localizedDescription);
return nil;
}
NSScanner *scanner = [NSScanner scannerWithString:profileString];
BOOL readPrelude = [scanner scanUpToString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" intoString:nil];
if (!readPrelude) {
return nil;
}
NSString *plistString;
BOOL readPlist = [scanner scanUpToString:@"</plist>" intoString:&plistString];
if (!readPlist) {
return nil;
}
plistString = [plistString stringByAppendingString:@"</plist>"];
NSData *plistData = [plistString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *plistDictionary = [NSPropertyListSerialization propertyListWithData:plistData
options:NSPropertyListImmutable
format:NULL
error:&error];
if (!plistDictionary) {
NSLog(@"Error unserializing provisioning profile plist: %@", error.localizedDescription);
return nil;
}
return plistDictionary;
}
@end

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>