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,164 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
/**
* EventSubscription represents a subscription to a particular event. It can
* remove its own subscription.
*/
interface EventSubscription {
eventType: string;
key: number;
subscriber: EventSubscriptionVendor;
/**
* @param subscriber the subscriber that controls
* this subscription.
*/
new (subscriber: EventSubscriptionVendor): EventSubscription;
/**
* Removes this subscription from the subscriber that controls it.
*/
remove(): void;
}
/**
* EventSubscriptionVendor stores a set of EventSubscriptions that are
* subscribed to a particular event type.
*/
declare class EventSubscriptionVendor {
constructor();
/**
* Adds a subscription keyed by an event type.
*
*/
addSubscription(
eventType: string,
subscription: EventSubscription,
): EventSubscription;
/**
* Removes a bulk set of the subscriptions.
*
* @param eventType - Optional name of the event type whose
* registered subscriptions to remove, if null remove all subscriptions.
*/
removeAllSubscriptions(eventType?: string): void;
/**
* Removes a specific subscription. Instead of calling this function, call
* `subscription.remove()` directly.
*
*/
removeSubscription(subscription: any): void;
/**
* Returns the array of subscriptions that are currently registered for the
* given event type.
*
* Note: This array can be potentially sparse as subscriptions are deleted
* from it when they are removed.
*
*/
getSubscriptionsForType(eventType: string): EventSubscription[];
}
/**
* EmitterSubscription represents a subscription with listener and context data.
*/
interface EmitterSubscription extends EventSubscription {
emitter: EventEmitter;
listener: () => any;
context: any;
/**
* @param emitter - The event emitter that registered this
* subscription
* @param subscriber - The subscriber that controls
* this subscription
* @param listener - Function to invoke when the specified event is
* emitted
* @param context - Optional context object to use when invoking the
* listener
*/
new (
emitter: EventEmitter,
subscriber: EventSubscriptionVendor,
listener: () => any,
context: any,
): EmitterSubscription;
/**
* Removes this subscription from the emitter that registered it.
* Note: we're overriding the `remove()` method of EventSubscription here
* but deliberately not calling `super.remove()` as the responsibility
* for removing the subscription lies with the EventEmitter.
*/
remove(): void;
}
export default class EventEmitter {
/**
*
* @param subscriber - Optional subscriber instance
* to use. If omitted, a new subscriber will be created for the emitter.
*/
constructor(subscriber?: EventSubscriptionVendor | null);
/**
* Adds a listener to be invoked when events of the specified type are
* emitted. An optional calling context may be provided. The data arguments
* emitted will be passed to the listener function.
*
* @param eventType - Name of the event to listen to
* @param listener - Function to invoke when the specified event is
* emitted
* @param context - Optional context object to use when invoking the
* listener
*/
addListener(
eventType: string,
listener: (...args: any[]) => any,
context?: any,
): EmitterSubscription;
/**
* Removes all of the registered listeners, including those registered as
* listener maps.
*
* @param eventType - Optional name of the event whose registered
* listeners to remove
*/
removeAllListeners(eventType?: string): void;
/**
* Returns the number of listeners that are currently registered for the given
* event.
*
* @param eventType - Name of the event to query
*/
listenerCount(eventType: string): number;
/**
* Emits an event of the given type with the given data. All handlers of that
* particular type will be notified.
*
* @param eventType - Name of the event to emit
* @param Arbitrary arguments to be passed to each registered listener
*
* @example
* emitter.addListener('someEvent', function(message) {
* console.log(message);
* });
*
* emitter.emit('someEvent', 'abc'); // logs 'abc'
*/
emit(eventType: string, ...params: any[]): void;
}

View File

@@ -0,0 +1,157 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
export interface EventSubscription {
remove(): void;
}
export interface IEventEmitter<TEventToArgsMap: {...}> {
addListener<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
listener: (...args: TEventToArgsMap[TEvent]) => mixed,
context?: mixed,
): EventSubscription;
emit<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
...args: TEventToArgsMap[TEvent]
): void;
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(eventType?: ?TEvent): void;
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number;
}
interface Registration<TArgs> {
+context: mixed;
+listener: (...args: TArgs) => mixed;
+remove: () => void;
}
// $FlowFixMe[deprecated-type]
type Registry<TEventToArgsMap: {...}> = $ObjMap<
TEventToArgsMap,
<TArgs>(TArgs) => Set<Registration<TArgs>>,
>;
/**
* EventEmitter manages listeners and publishes events to them.
*
* EventEmitter accepts a single type parameter that defines the valid events
* and associated listener argument(s).
*
* @example
*
* const emitter = new EventEmitter<{
* success: [number, string],
* error: [Error],
* }>();
*
* emitter.on('success', (statusCode, responseText) => {...});
* emitter.emit('success', 200, '...');
*
* emitter.on('error', error => {...});
* emitter.emit('error', new Error('Resource not found'));
*
*/
export default class EventEmitter<TEventToArgsMap: {...}>
implements IEventEmitter<TEventToArgsMap>
{
#registry: Registry<TEventToArgsMap> = {};
/**
* Registers a listener that is called when the supplied event is emitted.
* Returns a subscription that has a `remove` method to undo registration.
*/
addListener<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
listener: (...args: TEventToArgsMap[TEvent]) => mixed,
context: mixed,
): EventSubscription {
if (typeof listener !== 'function') {
throw new TypeError(
'EventEmitter.addListener(...): 2nd argument must be a function.',
);
}
const registrations = allocate<
TEventToArgsMap,
TEvent,
TEventToArgsMap[TEvent],
>(this.#registry, eventType);
const registration: Registration<TEventToArgsMap[TEvent]> = {
context,
listener,
remove(): void {
registrations.delete(registration);
},
};
registrations.add(registration);
return registration;
}
/**
* Emits the supplied event. Additional arguments supplied to `emit` will be
* passed through to each of the registered listeners.
*
* If a listener modifies the listeners registered for the same event, those
* changes will not be reflected in the current invocation of `emit`.
*/
emit<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
...args: TEventToArgsMap[TEvent]
): void {
const registrations: ?Set<Registration<TEventToArgsMap[TEvent]>> =
this.#registry[eventType];
if (registrations != null) {
// Copy `registrations` to take a snapshot when we invoke `emit`, in case
// registrations are added or removed when listeners are invoked.
for (const registration of Array.from(registrations)) {
registration.listener.apply(registration.context, args);
}
}
}
/**
* Removes all registered listeners.
*/
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(
eventType?: ?TEvent,
): void {
if (eventType == null) {
this.#registry = {};
} else {
delete this.#registry[eventType];
}
}
/**
* Returns the number of registered listeners for the supplied event.
*/
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number {
const registrations: ?Set<Registration<mixed>> = this.#registry[eventType];
return registrations == null ? 0 : registrations.size;
}
}
function allocate<
TEventToArgsMap: {...},
TEvent: $Keys<TEventToArgsMap>,
TEventArgs: TEventToArgsMap[TEvent],
>(
registry: Registry<TEventToArgsMap>,
eventType: TEvent,
): Set<Registration<TEventArgs>> {
let registrations: ?Set<Registration<TEventArgs>> = registry[eventType];
if (registrations == null) {
registrations = new Set();
registry[eventType] = registrations;
}
return registrations;
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import EventEmitter from '../EventEmitter';
const emitter = new EventEmitter<{
void: [],
string: [string],
strings: [string, string],
error: [Error],
}>();
const subscription = emitter.addListener('void', unknown => {
(unknown: void);
});
subscription.remove();
emitter.addListener('string', foo => {
(foo: string);
});
emitter.addListener('strings', (foo, bar) => {
(foo: string);
(bar: string);
});
emitter.addListener('error', error => {
(error: Error);
});
emitter.emit('void');
emitter.emit('string', 'foo');
emitter.emit('strings', 'foo', 'bar');
emitter.emit('error', new Error());
emitter.removeAllListeners('void');
emitter.removeAllListeners('string');
emitter.removeAllListeners('strings');
emitter.removeAllListeners('error');
emitter.removeAllListeners();
emitter.listenerCount('void');
emitter.listenerCount('string');
emitter.listenerCount('strings');
emitter.listenerCount('error');
// $FlowExpectedError[prop-missing]
emitter.addListener('does-not-exist', () => {
// ...
});
// $FlowExpectedError[prop-missing]
subscription.context;
// $FlowExpectedError[prop-missing]
subscription.listener;
// $FlowExpectedError[prop-missing]
subscription.once;
// $FlowExpectedError[invalid-tuple-arity]
emitter.emit('void', undefined);
// $FlowExpectedError[incompatible-call]
emitter.emit('string', 123);
// $FlowExpectedError[invalid-tuple-arity]
emitter.emit('strings', 'foo');
// $FlowExpectedError[invalid-tuple-arity]
emitter.emit('strings', 'foo', 'bar', 'baz');
// $FlowExpectedError[invalid-tuple-arity]
emitter.emit('error');
// $FlowExpectedError[prop-missing]
emitter.emit('does-not-exist');
// $FlowExpectedError[prop-missing]
emitter.removeAllListeners('does-not-exist');
// $FlowExpectedError[prop-missing]
emitter.listenerCount('does-not-exist');