chore: build
Backend - Build & Lint / build (push) Canceled after 11m16s
Frontend Admin - EAS Build / build (push) Canceled after 0s
Frontend Client - EAS Build / build (push) Canceled after 0s
Frontend Web - Build & Lint / build (push) Canceled after 0s

This commit is contained in:
Xor290
2026-09-08 17:44:28 +02:00
parent 06abfee274
commit 82f9a2fae9
37 changed files with 2164 additions and 598 deletions
+1
View File
@@ -30,6 +30,7 @@ yarn-error.*
.DS_Store
*.pem
!certs/certificate.pem
!certs/certificate-preprod.pem
# local env files
.env*.local
+2 -2
View File
@@ -2,7 +2,7 @@
"expo": {
"name": "Milieu Nantais",
"slug": "frontend-client",
"version": "1.0.2",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "dark",
@@ -52,7 +52,7 @@
"router": {}
},
"owner": "xor290",
"runtimeVersion": "client-1.0.2",
"runtimeVersion": "client-1.0.0",
"updates": {
"url": "https://u.expo.dev/110d06c8-a8d5-4b3c-b262-0d4d7509ae9d",
"codeSigningCertificate": "./certs/certificate.pem",
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHTCCAgWgAwIBAgIUfteDb4QnVA4dHw75YlHAKlxv7e0wDQYJKoZIhvcNAQEL
BQAwIzEhMB8GA1UEAwwYVWJlciBTdHVwIENsaWVudCBQcmVwcm9kMB4XDTI2MDgy
NjEwMjI0NVoXDTM2MDgyMzEwMjI0NVowIzEhMB8GA1UEAwwYVWJlciBTdHVwIENs
aWVudCBQcmVwcm9kMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvc/P
p8uCt0SHb+tCaM/Pi7wm8QBKf6qnSnFr+peHmM3xWZkSEk7v4NelTlwcJ/A+Azfv
0Py7euIGdOU13bXZRSDP5wbXVOJJt1eftJsiWlOT6ehGrnZOHd+telnTnl/fWbjJ
qtDphpt3bm0DfxUypatG/NAnQ1SEiLMyUwiBTrIWoLFQ+XbC6ULnoKfhROqXj1h7
eR+xCJ28R+LuB+kJk8EhD8L4CZqlO/xVk93eN3oJuTHJYT7jWff2uT+1SczRVvv4
ZmnznU/gUPXJcQlISnmvaG/+8Ng8Z9ThDKDnnneJpFXAhFqU62Wjb/Y9rTb9FWZn
saBQuDZGp+nBVHcT5QIDAQABo0kwRzAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/
BAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFE54b6JtwnO9lw5asoSxEQ77B3FHMA0G
CSqGSIb3DQEBCwUAA4IBAQA/UDUlMYaYaArtYl/BEKSj7jZTC3gFRA8393XLFdUi
/roiIdd6suX+T957wgXRSTpGPfFVO+azJChosEKMRI477r0vWRX4J8B0GXNo+jcr
okMjt5cY6G1egTvl+slJANoevJAClgOVOZ/+HShB0k9i9sIJf/rViKj7OV19UEur
0m2gK/qdvxbeFuw2RUq5tFRgUZzL8TyZmbJVKu0iRX4wB1MuUezDlr5a/k1qd27V
24U0+IiAQTjTVZj1ab8k6oP6376p6ydKoL2JLR7A/f/B1y/TojJbDNsU5EMCMt64
TRZ0aeslLtcnynqlpzr3JNXugtRPaz2WxT8NmB8H6fQR
-----END CERTIFICATE-----
+19 -18
View File
@@ -1,18 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIJOs/S1ceI7Zq6MA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNV
BAMTGk1pbGlldSBOYW50YWlzIC8gVWJlciBTdHVwMB4XDTI2MDcxMTIwNDI1MloX
DTM2MDcxMTIwNDI1MlowJTEjMCEGA1UEAxMaTWlsaWV1IE5hbnRhaXMgLyBVYmVy
IFN0dXAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQClkcj0h9gBuq9z
FVT1UwhBto2sTZglO3iwsgyPWx7I7twyY+kUU57rgHzI7YGieX599xM4oGjau8r7
PjPK1160djTuRa9bWXnYBnotaU0Hp3rOicxbMygCGQoZtDqxRUMo4HxrBSYnZaVo
VYPqs/utSTA43El7SrzJddxBZK4WbJbfgdXDYrdeLz4Syrdx8DXBnCYmmmHhpQsE
orykYCUi7qd0CJi6kZGVOgR+Hq0B581DqnUA2H3iQWdk/0EZf6PN/gR0f9YlH3oN
N8tYLo7TOScSmUNJ5T2hFEuWuS/O6JKUI6a7MpIOv7XYxNDYWT/Ae9QNT8GcqVcW
39j2xSvvAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggr
BgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAj+SMnO7/IFajlg6Uo6aJotM8vdPp
4Dgi18DURZ0evUsxm4lLHWQ//6zF8jVaqPwsKA9IuPW+8O8gC8iwCY3YfjbaC5Ad
vPlvpzU6bvaA//utLVVUlfkk87vs5QotJkshoImJJoDPfO/Q1yv1qrMHXPnGyyzV
K0K3rYeXVYMDeJ9y2742D+MEg0Zse7xmNcde2z5aUuFlK7ORBs03FohD2U5zUqUg
jaia/wN4lIMCdJJmoPRUydbLJ8yVns9whFxXU1eGqaFf27jBdI/nPMVmO1YPsxnk
VvOiP6n1T+aZ7qaeOY9hsSmJ9FBeh3pOtRrdxmA8wBxD3zAORfLl89mjsw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDDTCCAfWgAwIBAgIUJrRf0VNrabHd9GaoW3iBxw8RzygwDQYJKoZIhvcNAQEL
BQAwGzEZMBcGA1UEAwwQVWJlciBTdHVwIENsaWVudDAeFw0yNjA4MjYxMDIyNDVa
Fw0zNjA4MjMxMDIyNDVaMBsxGTAXBgNVBAMMEFViZXIgU3R1cCBDbGllbnQwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2foB+W04MkXIyANrUeffDy1Vo
soZ6UgD/FWvvzYY0Pf29kWsatBTfuwfMQPg4VVl2OXci7oL01Steml2ZOMb+kF0f
n3da5dRFFPVEp7KI+y1bRSBcy3M79Y8oon2Q1TcooCeaKVXVx7Ykg6/GPDLL/+tI
0HVXdtQxMDr1EcCZuTo2g+91o84MLXJXipHkuS64UYAjlPrJFcq9jR4TJ7zYsL01
P9fPmokAm2Vc2B9dG+BjlSBXp7oyLIOtMwC1zACkWiU+81d59NMZAv04FJENB/P6
xeG3WqM/n/9INGy2Xvd9wkUuZEObZJiNh4CL6ZWzhJPCPq2cHcqrcHxXeRtxAgMB
AAGjSTBHMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAd
BgNVHQ4EFgQUcyo31IaveB8XCpMt/m0rLvC/kk4wDQYJKoZIhvcNAQELBQADggEB
ACVgBn04MtRN/VysKaus837+x5XtiXm+V6Bi57+JkqORKEgzV2PdmFCtpcG6ePef
0uUVkK7IF2tGm1AfNUkwvw/CoKNaFe9rtcNMLVYZSDbn6KOAyBSAxb2yQewJaSLN
/qpjkg45Jrcwyl0cQ6tQfQgWmliXE1AbgAN5j0foKA0b4ioLsI0dPFncYo5hzmOb
9FQ7QGbHwiAMmnQ3PHbpoby6DVpmEeuIj22FgAxt9TI7bYon/OHVO894jN0CCEOJ
8wrAUxgIGJKLFYSQdodaQ4WESyYnAcGTneqypC+l9yJQBWNUT+cJeNaQmu47Zbra
eLmJVdPXnnRe2UT7wSa02xg=
-----END CERTIFICATE-----
+2 -2
View File
@@ -23,7 +23,7 @@
},
"env": {
"EXPO_PUBLIC_API_URL": "https://uber-demo.club",
"EXPO_PUBLIC_UPDATE_URL": "https://ota.uber-stup.club/api/manifest"
"EXPO_PUBLIC_UPDATE_URL": "https://ota-mobile-preprod.uber-stup.club/api/manifest"
},
"channel": "pre-prod-client"
},
@@ -35,7 +35,7 @@
},
"env": {
"EXPO_PUBLIC_API_URL": "https://mln-uber.club",
"EXPO_PUBLIC_UPDATE_URL": "https://ota.uber-stup.club/api/manifest"
"EXPO_PUBLIC_UPDATE_URL": "https://ota-mobile-prod.uber-stup.club/api/manifest"
},
"channel": "production-client"
}
+8 -4
View File
@@ -529,7 +529,6 @@ export const getOrdersWithTracking = async () => {
// ============================================
// HISTORY
// ============================================
export const getMyCompletedOrders = async (): Promise<HistoryResponse> => {
try {
@@ -962,13 +961,18 @@ export const toggle2FA = async (
// 🏆 POINTS — RÉCOMPENSES
// ============================================
export type RewardConfigProduct = {
product_id: number;
product_name: string;
quantity: number;
};
export type RewardCategoryConfig = {
category: string;
type: "free_product" | "half_price_product";
all_products: boolean;
product_ids: number[];
product_names: string[];
amount: number;
products: RewardConfigProduct[];
quantity: number;
};
export type RewardItemConfig = {
+2
View File
@@ -355,6 +355,8 @@ export interface ProductPrice {
quantity: number;
price: number;
active_price?: boolean;
promo_price?: number; // prix réduit si une promotion couvre ce palier
promo_percent?: number; // pourcentage de réduction appliqué
}
export interface Product {
id: number;
-1
View File
@@ -32,7 +32,6 @@ export const getRole = () => AsyncStorage.getItem(ROLE_KEY);
export const setRole = (role: string) => AsyncStorage.setItem(ROLE_KEY, role);
export const removeRole = () => AsyncStorage.removeItem(ROLE_KEY);
// Clear all auth data
export const clearAllAuth = async () => {
await AsyncStorage.multiRemove([
TOKEN_KEY,
@@ -830,14 +830,14 @@ export default function OrderHistoryScreen() {
</View>,
]
: (
cfg.product_names ??
cfg.products ??
[]
).map(
(
name,
p,
) => (
<View
key={`${cfg.category}-${name}`}
key={`${cfg.category}-${p.product_id}`}
style={
styles.rewardAmountBadge
}
@@ -848,7 +848,7 @@ export default function OrderHistoryScreen() {
}
>
{
name
p.product_name
}
</Text>
</View>
@@ -63,12 +63,18 @@ export default function ProductDetailScreen() {
quantity: parseFloat(String(pr.quantity)),
price: parseFloat(String(pr.price)),
active_price: pr.active_price,
promo_price:
pr.promo_price != null
? parseFloat(String(pr.promo_price))
: undefined,
promo_percent: pr.promo_percent,
})) || [],
};
setProduct(fixedProduct);
if (fixedProduct.prices.length > 0) {
setSelectedGrams(fixedProduct.prices[0].quantity);
setSelectedPrice(fixedProduct.prices[0].price);
const first = fixedProduct.prices[0];
setSelectedGrams(first.quantity);
setSelectedPrice(first.promo_price ?? first.price);
}
const matched = categories.find(
(c) =>
@@ -90,7 +96,7 @@ export default function ProductDetailScreen() {
const handleGramsChange = (quantity: number) => {
setSelectedGrams(quantity);
const opt = product?.prices?.find((p) => p.quantity === quantity);
if (opt) setSelectedPrice(opt.price);
if (opt) setSelectedPrice(opt.promo_price ?? opt.price);
setShowQuantityPicker(false);
};
@@ -585,16 +591,45 @@ export default function ProductDetailScreen() {
<View style={styles.infoSection}>
<Text style={styles.productName}>{product.name}</Text>
{selectedPrice > 0 && (
<View style={styles.priceRow}>
<View style={styles.priceIndicator} />
<Text style={styles.priceText}>
{selectedPrice.toFixed(2)} {" "}
{selectedGrams &&
`pour ${selectedGrams}${product.unit || "g"}`}
</Text>
</View>
)}
{selectedPrice > 0 && (() => {
const selectedTier = product.prices?.find(
(p) => p.quantity === selectedGrams,
);
const hasPromo =
selectedTier?.promo_price != null &&
selectedTier.promo_price < selectedTier.price;
return (
<View style={styles.priceRow}>
<View style={styles.priceIndicator} />
{hasPromo && (
<Text
style={[
styles.priceText,
{
textDecorationLine: "line-through",
opacity: 0.6,
marginRight: 6,
},
]}
>
{selectedTier!.price.toFixed(2)}
</Text>
)}
<Text
style={[
styles.priceText,
hasPromo && { color: "#22c55e" },
]}
>
{selectedPrice.toFixed(2)} {" "}
{selectedGrams &&
`pour ${selectedGrams}${product.unit || "g"}`}
{hasPromo &&
` (-${selectedTier!.promo_percent}%)`}
</Text>
</View>
);
})()}
<View style={styles.descriptionCard}>
<Text style={styles.descriptionTitle}>Description</Text>
<Text style={styles.descriptionText}>
@@ -719,16 +754,32 @@ export default function ProductDetailScreen() {
{p.quantity}
{product.unit || "g"}
</Text>
<Text
style={[
styles.pickerOptionPrice,
selectedGrams === p.quantity && {
color: catColor,
},
]}
>
{p.price.toFixed(2)}
</Text>
{p.promo_price != null && p.promo_price < p.price ? (
<View style={{ flexDirection: "row", alignItems: "center", gap: 4 }}>
<Text
style={[
styles.pickerOptionPrice,
{ textDecorationLine: "line-through", opacity: 0.6 },
]}
>
{p.price.toFixed(2)}
</Text>
<Text style={[styles.pickerOptionPrice, { color: "#22c55e" }]}>
{p.promo_price.toFixed(2)} (-{p.promo_percent}%)
</Text>
</View>
) : (
<Text
style={[
styles.pickerOptionPrice,
selectedGrams === p.quantity && {
color: catColor,
},
]}
>
{p.price.toFixed(2)}
</Text>
)}
</View>
{selectedGrams === p.quantity && (
<View
@@ -34,7 +34,6 @@ const logoGrosSemi = require("../../../assets/logo-gros-semi.png");
const { width: SCREEN_WIDTH } = Dimensions.get("window");
const CARD_WIDTH = SCREEN_WIDTH - 48;
// Les catégories sont chargées dynamiquement depuis l'API
type Nav = NativeStackNavigationProp<ClientStackParamList>;