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 @@
export { retryExchange } from './retryExchange';

View File

@@ -0,0 +1,12 @@
import { Exchange, Operation, CombinedError } from '@urql/core';
export interface RetryExchangeOptions {
initialDelayMs?: number;
maxDelayMs?: number;
randomDelay?: boolean;
maxNumberAttempts?: number;
/** Conditionally determine whether an error should be retried */
retryIf?: (error: CombinedError, operation: Operation) => boolean;
/** Conditionally update operations as they're retried (retryIf can be replaced with this) */
retryWith?: (error: CombinedError, operation: Operation) => Operation | null | undefined;
}
export declare const retryExchange: ({ initialDelayMs, maxDelayMs, randomDelay, maxNumberAttempts, retryIf, retryWith, }: RetryExchangeOptions) => Exchange;

View File

@@ -0,0 +1,83 @@
var r = require("wonka");
var e = require("@urql/core");
function _extends() {
return (_extends = Object.assign || function(r) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var a in t) {
if (Object.prototype.hasOwnProperty.call(t, a)) {
r[a] = t[a];
}
}
}
return r;
}).apply(this, arguments);
}
exports.retryExchange = function retryExchange(t) {
var a = t.retryIf;
var n = t.retryWith;
var o = t.initialDelayMs || 1e3;
var i = t.maxDelayMs || 15e3;
var u = t.maxNumberAttempts || 2;
var s = t.randomDelay || !0;
return function(t) {
var c = t.forward;
var y = t.dispatchDebug;
return function(t) {
var v = r.share(t);
var p = r.makeSubject();
var f = p.source;
var d = p.next;
var h = r.mergeMap((function(t) {
var a = t.key;
var n = t.context;
var c = (n.retryCount || 0) + 1;
var p = n.retryDelay || o;
var f = Math.random() + 1.5;
if (s && p * f < i) {
p *= f;
}
var d = r.filter((function(r) {
return ("query" === r.kind || "teardown" === r.kind) && r.key === a;
}))(v);
"production" !== process.env.NODE_ENV && y({
type: "retryAttempt",
message: "The operation has failed and a retry has been triggered (" + c + " / " + u + ")",
operation: t,
data: {
retryCount: c
},
source: "retryExchange"
});
return r.takeUntil(d)(r.delay(p)(r.fromValue(e.makeOperation(t.kind, t, _extends({}, t.context, {
retryDelay: p,
retryCount: c
})))));
}))(f);
return r.filter((function(r) {
if (!(r.error && (a ? a(r.error, r.operation) : n || r.error.networkError))) {
return !0;
}
if (!((r.operation.context.retryCount || 0) >= u - 1)) {
var e = n ? n(r.error, r.operation) : r.operation;
if (!e) {
return !0;
}
d(e);
return !1;
}
"production" !== process.env.NODE_ENV && y({
type: "retryExhausted",
message: "Maximum number of retries has been reached. No further retries will be performed.",
operation: r.operation,
source: "retryExchange"
});
return !0;
}))(r.share(c(r.merge([ v, h ]))));
};
};
};
//# sourceMappingURL=urql-exchange-retry.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
var r=require("wonka"),e=require("@urql/core");function t(){return(t=Object.assign||function(r){for(var e=1;e<arguments.length;e++){var t=arguments[e];for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(r[n]=t[n])}return r}).apply(this,arguments)}exports.retryExchange=function(n){var o=n.retryIf,a=n.retryWith,i=n.initialDelayMs||1e3,u=n.maxDelayMs||15e3,y=n.maxNumberAttempts||2,c=n.randomDelay||!0;return function(n){var f=n.forward;return function(n){var l=r.share(n),p=r.makeSubject(),m=p.source,s=p.next,k=r.mergeMap((function(n){var o=n.key,a=n.context,y=(a.retryCount||0)+1,f=a.retryDelay||i,p=Math.random()+1.5;c&&f*p<u&&(f*=p);var m=r.filter((function(r){return("query"===r.kind||"teardown"===r.kind)&&r.key===o}))(l);return r.takeUntil(m)(r.delay(f)(r.fromValue(e.makeOperation(n.kind,n,t({},n.context,{retryDelay:f,retryCount:y})))))}))(m);return r.filter((function(r){if(!r.error||!(o?o(r.error,r.operation):a||r.error.networkError))return!0;if(!((r.operation.context.retryCount||0)>=y-1)){var e=a?a(r.error,r.operation):r.operation;return!e||(s(e),!1)}return!0}))(r.share(f(r.merge([l,k]))))}}};
//# sourceMappingURL=urql-exchange-retry.min.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"urql-exchange-retry.min.js","sources":["../src/retryExchange.ts"],"sourcesContent":["import {\n makeSubject,\n share,\n pipe,\n merge,\n filter,\n fromValue,\n delay,\n mergeMap,\n takeUntil,\n} from 'wonka';\nimport {\n makeOperation,\n Exchange,\n Operation,\n CombinedError,\n OperationResult,\n} from '@urql/core';\nimport { sourceT } from 'wonka/dist/types/src/Wonka_types.gen';\n\nexport interface RetryExchangeOptions {\n initialDelayMs?: number;\n maxDelayMs?: number;\n randomDelay?: boolean;\n maxNumberAttempts?: number;\n /** Conditionally determine whether an error should be retried */\n retryIf?: (error: CombinedError, operation: Operation) => boolean;\n /** Conditionally update operations as they're retried (retryIf can be replaced with this) */\n retryWith?: (\n error: CombinedError,\n operation: Operation\n ) => Operation | null | undefined;\n}\n\nexport const retryExchange = ({\n initialDelayMs,\n maxDelayMs,\n randomDelay,\n maxNumberAttempts,\n retryIf,\n retryWith,\n}: RetryExchangeOptions): Exchange => {\n const MIN_DELAY = initialDelayMs || 1000;\n const MAX_DELAY = maxDelayMs || 15000;\n const MAX_ATTEMPTS = maxNumberAttempts || 2;\n const RANDOM_DELAY = randomDelay || true;\n\n return ({ forward, dispatchDebug }) => ops$ => {\n const sharedOps$ = pipe(ops$, share);\n const {\n source: retry$,\n next: nextRetryOperation,\n } = makeSubject<Operation>();\n\n const retryWithBackoff$ = pipe(\n retry$,\n mergeMap((op: Operation) => {\n const { key, context } = op;\n const retryCount = (context.retryCount || 0) + 1;\n let delayAmount = context.retryDelay || MIN_DELAY;\n\n const backoffFactor = Math.random() + 1.5;\n // if randomDelay is enabled and it won't exceed the max delay, apply a random\n // amount to the delay to avoid thundering herd problem\n if (RANDOM_DELAY && delayAmount * backoffFactor < MAX_DELAY) {\n delayAmount *= backoffFactor;\n }\n\n // We stop the retries if a teardown event for this operation comes in\n // But if this event comes through regularly we also stop the retries, since it's\n // basically the query retrying itself, no backoff should be added!\n const teardown$ = pipe(\n sharedOps$,\n filter(op => {\n return (\n (op.kind === 'query' || op.kind === 'teardown') && op.key === key\n );\n })\n );\n\n dispatchDebug({\n type: 'retryAttempt',\n message: `The operation has failed and a retry has been triggered (${retryCount} / ${MAX_ATTEMPTS})`,\n operation: op,\n data: {\n retryCount,\n },\n });\n\n // Add new retryDelay and retryCount to operation\n return pipe(\n fromValue(\n makeOperation(op.kind, op, {\n ...op.context,\n retryDelay: delayAmount,\n retryCount,\n })\n ),\n delay(delayAmount),\n // Stop retry if a teardown comes in\n takeUntil(teardown$)\n );\n })\n );\n\n const result$ = pipe(\n merge([sharedOps$, retryWithBackoff$]),\n forward,\n share,\n filter(res => {\n // Only retry if the error passes the conditional retryIf function (if passed)\n // or if the error contains a networkError\n if (\n !res.error ||\n (retryIf\n ? !retryIf(res.error, res.operation)\n : !retryWith && !res.error.networkError)\n ) {\n return true;\n }\n\n const maxNumberAttemptsExceeded =\n (res.operation.context.retryCount || 0) >= MAX_ATTEMPTS - 1;\n\n if (!maxNumberAttemptsExceeded) {\n const operation = retryWith\n ? retryWith(res.error, res.operation)\n : res.operation;\n if (!operation) return true;\n\n // Send failed responses to be retried by calling next on the retry$ subject\n // Exclude operations that have been retried more than the specified max\n nextRetryOperation(operation);\n return false;\n }\n\n dispatchDebug({\n type: 'retryExhausted',\n message:\n 'Maximum number of retries has been reached. No further retries will be performed.',\n operation: res.operation,\n });\n\n return true;\n })\n ) as sourceT<OperationResult>;\n\n return result$;\n };\n};\n"],"names":["MIN_DELAY","initialDelayMs","MAX_DELAY","MAX_ATTEMPTS","RANDOM_DELAY","retryWithBackoff$","retryCount","op","context","delayAmount","retryDelay","backoffFactor","filter","kind","error"],"mappings":"gTA0CQA,EAAYC,YACZC,wBACAC,qBACAC,iJASEC,+CAIIC,EAAUC,EAAGC,QACfC,GAAcD,EAAQE,YAAcV,KAElCW,+DAcaC,QAAZ,YAJQ,mBAAAC,6IAwBTP,EAHWA,4DA0BRQ,mLAbA"}

View File

@@ -0,0 +1,2 @@
import{share as r,makeSubject as t,mergeMap as e,filter as n,takeUntil as o,delay as a,fromValue as i,merge as u}from"wonka";import{makeOperation as y}from"@urql/core";function c(){return(c=Object.assign||function(r){for(var t=1;t<arguments.length;t++){var e=arguments[t];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(r[n]=e[n])}return r}).apply(this,arguments)}function f(f){var p=f.retryIf,l=f.retryWith,m=f.initialDelayMs||1e3,v=f.maxDelayMs||15e3,s=f.maxNumberAttempts||2,d=f.randomDelay||!0;return function(f){var k=f.forward;return function(f){var x=r(f),h=t(),w=h.source,D=h.next,b=e((function(r){var t=r.key,e=r.context,u=(e.retryCount||0)+1,f=e.retryDelay||m,p=Math.random()+1.5;d&&f*p<v&&(f*=p);var l=n((function(r){return("query"===r.kind||"teardown"===r.kind)&&r.key===t}))(x);return o(l)(a(f)(i(y(r.kind,r,c({},r.context,{retryDelay:f,retryCount:u})))))}))(w);return n((function(r){if(!r.error||!(p?p(r.error,r.operation):l||r.error.networkError))return!0;if(!((r.operation.context.retryCount||0)>=s-1)){var t=l?l(r.error,r.operation):r.operation;return!t||(D(t),!1)}return!0}))(r(k(u([x,b]))))}}}export{f as retryExchange};
//# sourceMappingURL=urql-exchange-retry.min.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,85 @@
import { share as r, makeSubject as e, mergeMap as t, filter as n, takeUntil as a, delay as o, fromValue as i, merge as u } from "wonka";
import { makeOperation as c } from "@urql/core";
function _extends() {
return (_extends = Object.assign || function(r) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var n in t) {
if (Object.prototype.hasOwnProperty.call(t, n)) {
r[n] = t[n];
}
}
}
return r;
}).apply(this, arguments);
}
function retryExchange(s) {
var y = s.retryIf;
var p = s.retryWith;
var v = s.initialDelayMs || 1e3;
var f = s.maxDelayMs || 15e3;
var d = s.maxNumberAttempts || 2;
var h = s.randomDelay || !0;
return function(s) {
var m = s.forward;
var x = s.dispatchDebug;
return function(s) {
var l = r(s);
var g = e();
var E = g.source;
var b = g.next;
var D = t((function(r) {
var e = r.key;
var t = r.context;
var u = (t.retryCount || 0) + 1;
var s = t.retryDelay || v;
var y = Math.random() + 1.5;
if (h && s * y < f) {
s *= y;
}
var p = n((function(r) {
return ("query" === r.kind || "teardown" === r.kind) && r.key === e;
}))(l);
"production" !== process.env.NODE_ENV && x({
type: "retryAttempt",
message: "The operation has failed and a retry has been triggered (" + u + " / " + d + ")",
operation: r,
data: {
retryCount: u
},
source: "retryExchange"
});
return a(p)(o(s)(i(c(r.kind, r, _extends({}, r.context, {
retryDelay: s,
retryCount: u
})))));
}))(E);
return n((function(r) {
if (!(r.error && (y ? y(r.error, r.operation) : p || r.error.networkError))) {
return !0;
}
if (!((r.operation.context.retryCount || 0) >= d - 1)) {
var e = p ? p(r.error, r.operation) : r.operation;
if (!e) {
return !0;
}
b(e);
return !1;
}
"production" !== process.env.NODE_ENV && x({
type: "retryExhausted",
message: "Maximum number of retries has been reached. No further retries will be performed.",
operation: r.operation,
source: "retryExchange"
});
return !0;
}))(r(m(u([ l, D ]))));
};
};
}
export { retryExchange };
//# sourceMappingURL=urql-exchange-retry.mjs.map

File diff suppressed because one or more lines are too long