diff --git a/frontend-admin/app.json b/frontend-admin/app.json
index 969ca7d5..48252ce8 100644
--- a/frontend-admin/app.json
+++ b/frontend-admin/app.json
@@ -2,7 +2,7 @@
"expo": {
"name": "Admin Panel",
"slug": "frontend-admin",
- "version": "1.0.0",
+ "version": "1.0.1",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "dark",
@@ -53,7 +53,7 @@
}
},
"owner": "xor290",
- "runtimeVersion": "admin-1.0.0",
+ "runtimeVersion": "admin-1.0.1",
"updates": {
"url": "https://u.expo.dev/fcb7a0bc-5b2f-453d-ba16-9f97d9f0e440",
"codeSigningCertificate": "./certs/certificate.pem",
diff --git a/frontend-admin/certs/certificate.pem b/frontend-admin/certs/certificate.pem
index 3eaf59ca..01597965 100644
--- a/frontend-admin/certs/certificate.pem
+++ b/frontend-admin/certs/certificate.pem
@@ -1,19 +1,19 @@
------BEGIN CERTIFICATE-----
-MIIDAzCCAeugAwIBAgIJR6I83gTOhZ0JMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV
-BAMTIE1pbGlldSBOYW50YWlzIC8gVWJlciBTdHVwIEFkbWluMB4XDTI2MDgxNjEy
-MTcwNFoXDTM2MDgxNjEyMTcwNFowKzEpMCcGA1UEAxMgTWlsaWV1IE5hbnRhaXMg
-LyBVYmVyIFN0dXAgQWRtaW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-AQDLGiJBJLWXr/qbjTpsLGK1++qjebpviWUAUp5f2nBEXBFohtrGsm7CE9bpfhWt
-2pSTLw6faASUSafAwT0JUaPTFb7kvwmhl6nteWs+L5doTSzuyVwNu4l/29JoAUOZ
-gDgxJJNBovR5X5raBG7Q18E7/KrSskw7iMuYCa50rlKD5BLko4IoWBVV4ik6IQN1
-LdlIIZNoMYc910rKq5NEGMs0m2Q3e4LUiP1sBXyZFAcRWaGY/cZ9yvN0OKk/q/bb
-fyD2EZ+/lcX+PqBmSKY0/D+k9YGXW8NRces/1LbVBs8daPGoc6tsCFaaJbw76aWT
-mCx5lFDiSwrkF9PSKCcnVFWLAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNV
-HSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAb4koejHwa86+
-rFaL+jsiAifV8KkgUde9QiK2/EVM5aUcyk+CAeAsUopftnOQpo1JlNCQ1nxMY183
-s1K4DMQxs5xeYl/JD71o+qYCB0mYcqubYLmsc9Vellrzrz5eh83PUijgQU30htdm
-w4qeUY5CP5ZlrlIlMa8P0Fab7327r2LEbUnrMlo3pHcSayLkqtKeQ//vk8M0TFIK
-Fh1hnM3WzvdrLUyqsKOKjGve3QYfSz4OdUiSx2fedcx8xJeMTebaxj9VD5Uc2r1U
-RFy4V6czONoAISFY5+AoqsNewtYoQ4UYuQlBFxNaNC+OIsENdhhFFrtWV9XylfDG
-QcU3SMKMJg==
------END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIJR6I83gTOhZ0JMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV
+BAMTIE1pbGlldSBOYW50YWlzIC8gVWJlciBTdHVwIEFkbWluMB4XDTI2MDgxNjEy
+MTcwNFoXDTM2MDgxNjEyMTcwNFowKzEpMCcGA1UEAxMgTWlsaWV1IE5hbnRhaXMg
+LyBVYmVyIFN0dXAgQWRtaW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDLGiJBJLWXr/qbjTpsLGK1++qjebpviWUAUp5f2nBEXBFohtrGsm7CE9bpfhWt
+2pSTLw6faASUSafAwT0JUaPTFb7kvwmhl6nteWs+L5doTSzuyVwNu4l/29JoAUOZ
+gDgxJJNBovR5X5raBG7Q18E7/KrSskw7iMuYCa50rlKD5BLko4IoWBVV4ik6IQN1
+LdlIIZNoMYc910rKq5NEGMs0m2Q3e4LUiP1sBXyZFAcRWaGY/cZ9yvN0OKk/q/bb
+fyD2EZ+/lcX+PqBmSKY0/D+k9YGXW8NRces/1LbVBs8daPGoc6tsCFaaJbw76aWT
+mCx5lFDiSwrkF9PSKCcnVFWLAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNV
+HSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAb4koejHwa86+
+rFaL+jsiAifV8KkgUde9QiK2/EVM5aUcyk+CAeAsUopftnOQpo1JlNCQ1nxMY183
+s1K4DMQxs5xeYl/JD71o+qYCB0mYcqubYLmsc9Vellrzrz5eh83PUijgQU30htdm
+w4qeUY5CP5ZlrlIlMa8P0Fab7327r2LEbUnrMlo3pHcSayLkqtKeQ//vk8M0TFIK
+Fh1hnM3WzvdrLUyqsKOKjGve3QYfSz4OdUiSx2fedcx8xJeMTebaxj9VD5Uc2r1U
+RFy4V6czONoAISFY5+AoqsNewtYoQ4UYuQlBFxNaNC+OIsENdhhFFrtWV9XylfDG
+QcU3SMKMJg==
+-----END CERTIFICATE-----
diff --git a/frontend-admin/eas.json b/frontend-admin/eas.json
index 815870c6..aab6d181 100644
--- a/frontend-admin/eas.json
+++ b/frontend-admin/eas.json
@@ -4,6 +4,18 @@
"appVersionSource": "local"
},
"build": {
+ "development": {
+ "developmentClient": true,
+ "distribution": "internal",
+ "android": {
+ "buildType": "apk",
+ "gradleCommand": ":app:assembleDebug"
+ },
+ "env": {
+ "EXPO_PUBLIC_API_URL": "http://localhost:8080"
+ },
+ "channel": "development"
+ },
"pre-prod": {
"distribution": "internal",
"android": {
@@ -11,7 +23,7 @@
},
"env": {
"EXPO_PUBLIC_API_URL": "https://uber-demo.club",
- "EXPO_PUBLIC_UPDATE_URL": "https://ota-preprod.uber-stup.club/api/manifest"
+ "EXPO_PUBLIC_UPDATE_URL": "https://ota.uber-stup.club/api/manifest"
},
"channel": "pre-prod-admin"
},
@@ -23,7 +35,7 @@
},
"env": {
"EXPO_PUBLIC_API_URL": "https://mln-uber.club",
- "EXPO_PUBLIC_UPDATE_URL": "https://ota-prod.uber-stup.club/api/manifest"
+ "EXPO_PUBLIC_UPDATE_URL": "https://ota.uber-stup.club/api/manifest"
},
"channel": "production-admin"
}
diff --git a/frontend-admin/src/api/api_admin.ts b/frontend-admin/src/api/api_admin.ts
index 3584518c..c442501c 100644
--- a/frontend-admin/src/api/api_admin.ts
+++ b/frontend-admin/src/api/api_admin.ts
@@ -1109,6 +1109,7 @@ export interface PointsTier {
export interface RewardCategoryConfig {
category: string;
+ type: "free_product" | "half_price_product";
all_products: boolean;
product_ids: number[];
}
@@ -1121,7 +1122,6 @@ export interface RewardItem {
export interface PointsReward {
threshold: number;
- type: "free_product" | "half_price_product" | "custom";
description: string;
category_configs: RewardCategoryConfig[];
reward_items: RewardItem[];
diff --git a/frontend-admin/src/screens/admin/SettingsScreen.tsx b/frontend-admin/src/screens/admin/SettingsScreen.tsx
index bca1526a..e58d03a3 100644
--- a/frontend-admin/src/screens/admin/SettingsScreen.tsx
+++ b/frontend-admin/src/screens/admin/SettingsScreen.tsx
@@ -686,14 +686,13 @@ function TiersSection({
const REWARD_ACCENT = "#f59e0b";
-const REWARD_TYPES: { value: PointsReward["type"]; label: string; icon: string }[] = [
+const REWARD_TYPES: { value: RewardCategoryConfig["type"]; label: string; icon: string }[] = [
{ value: "free_product", label: "Produit offert", icon: "gift-outline" },
{ value: "half_price_product", label: "Produit à -50%", icon: "pricetag-outline" },
];
const EMPTY_REWARD: PointsReward = {
threshold: 20,
- type: "free_product",
description: "",
category_configs: [],
reward_items: [],
@@ -822,7 +821,7 @@ function CentralRewardSection({
const getCatConfig = (catName: string): RewardCategoryConfig =>
r.category_configs.find((c) => c.category === catName) ??
- { category: catName, all_products: true, product_ids: [] };
+ { category: catName, type: "free_product", all_products: true, product_ids: [] };
const isCatSelected = (catName: string) =>
r.category_configs.some((c) => c.category === catName);
@@ -831,7 +830,7 @@ function CentralRewardSection({
if (isCatSelected(catName)) {
update({ category_configs: r.category_configs.filter((c) => c.category !== catName) });
} else {
- update({ category_configs: [...r.category_configs, { category: catName, all_products: true, product_ids: [] }] });
+ update({ category_configs: [...r.category_configs, { category: catName, type: "free_product", all_products: true, product_ids: [] }] });
}
};
@@ -894,34 +893,6 @@ function CentralRewardSection({
- {/* Type */}
-
- Type de récompense
-
- {REWARD_TYPES.map((rt) => {
- const sel = r.type === rt.value;
- return (
- update({ type: rt.value })}
- style={{
- flexDirection: "row", alignItems: "center", gap: spacing.xs,
- paddingHorizontal: spacing.m, paddingVertical: spacing.s,
- borderRadius: borderRadius.sm, borderWidth: 1.5,
- borderColor: sel ? REWARD_ACCENT : colors.border,
- backgroundColor: sel ? REWARD_ACCENT + "20" : "transparent",
- }}
- >
-
-
- {rt.label}
-
-
- );
- })}
-
-
-
{/* Description */}
Description affichée au client
@@ -935,12 +906,12 @@ function CentralRewardSection({
/>
- {/* Produits récompense — uniquement pour le type "Produit offert" */}
- {r.type === "free_product" && (
+ {/* Produits récompense — proposés au client selon le type choisi
+ pour la catégorie de chaque produit (voir "Catégories éligibles" ci-dessous) */}
Produits ajoutés au panier
- Quand le client réclame sa récompense, ces produits sont automatiquement ajoutés à son panier (gratuits). Il doit commander au moins un produit normal.
+ Quand le client réclame sa récompense, ces produits sont automatiquement ajoutés à son panier — gratuits ou à -50% selon le type configuré pour la catégorie du produit. Il doit commander au moins un produit normal.
{r.reward_items.map((item, idx) => {
@@ -1062,14 +1033,14 @@ function CentralRewardSection({
)}
- )}
- {/* Catégories éligibles — uniquement pour le type "Produit à -50%" */}
- {r.type === "half_price_product" && (
+ {/* Catégories éligibles — chaque catégorie choisit son propre type
+ (produit offert ou -50%), qui s'applique aux produits récompense
+ de cette catégorie configurés ci-dessus */}
- Catégories éligibles à -50%
+ Catégories éligibles
- Sélectionnez les catégories, puis pour chacune choisissez tous les produits ou une sélection.
+ Sélectionnez les catégories, choisissez le type de récompense pour chacune, puis tous les produits ou une sélection.
{allCategories.length === 0 ? (
Aucune catégorie disponible
@@ -1103,15 +1074,41 @@ function CentralRewardSection({
/>
- {/* Sélecteur produits (visible si catégorie sélectionnée) */}
+ {/* Type + sélecteur produits (visible si catégorie sélectionnée) */}
{selected && (
-
+
+
+ {REWARD_TYPES.map((rt) => {
+ const cfg = getCatConfig(cat.name);
+ const sel = (cfg.type || "free_product") === rt.value;
+ return (
+ updateCatConfig({ ...cfg, type: rt.value })}
+ style={{
+ flexDirection: "row", alignItems: "center", gap: 4,
+ paddingHorizontal: spacing.s, paddingVertical: 4,
+ borderRadius: borderRadius.sm, borderWidth: 1.5,
+ borderColor: sel ? REWARD_ACCENT : colors.border,
+ backgroundColor: sel ? REWARD_ACCENT + "22" : "transparent",
+ }}
+ >
+
+
+ {rt.label}
+
+
+ );
+ })}
+
+
+
)}
);
@@ -1119,25 +1116,23 @@ function CentralRewardSection({
)}
- )}
{/* Récapitulatif */}
{(r.category_configs.length > 0 || r.reward_items.filter((it) => it.product_id > 0).length > 0) && (
Récapitulatif
- Dès {r.threshold} pts par type →{" "}
- {REWARD_TYPES.find((x) => x.value === r.type)?.label}
+ Dès {r.threshold} pts par type de points → récompense débloquée
{r.description !== "" && (
"{r.description}"
)}
- {r.type === "half_price_product" && r.category_configs.map((cfg) => (
+ {r.category_configs.map((cfg) => (
- • Éligible à -50% : {cfg.category} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`}
+ • {REWARD_TYPES.find((x) => x.value === (cfg.type || "free_product"))?.label} : {cfg.category} — {cfg.all_products ? "tous les produits" : `${cfg.product_ids.length} produit(s)`}
))}
- {r.type === "free_product" && r.reward_items.filter((it) => it.product_id > 0).map((it, idx) => {
+ {r.reward_items.filter((it) => it.product_id > 0).map((it, idx) => {
const prod = Object.values(productsByCategory).flat().find((p) => p.id === it.product_id);
return (
diff --git a/frontend-admin/src/screens/delivery/DashboardScreen.tsx b/frontend-admin/src/screens/delivery/DashboardScreen.tsx
index 734ad1a5..626da74d 100644
--- a/frontend-admin/src/screens/delivery/DashboardScreen.tsx
+++ b/frontend-admin/src/screens/delivery/DashboardScreen.tsx
@@ -715,7 +715,7 @@ export default function DashboardScreen() {
{prod.is_reward && (
- Offert
+ Récompense
)}
@@ -724,7 +724,7 @@ export default function DashboardScreen() {
- {prod.is_reward ? "Offert" : `${(prod.prix ?? 0).toFixed(2)}€`}
+ {prod.is_reward && (prod.prix ?? 0) === 0 ? "Offert" : `${(prod.prix ?? 0).toFixed(2)}€`}
))}
@@ -2018,7 +2018,7 @@ export default function DashboardScreen() {
{prod.is_reward && (
- Offert
+ Récompense
)}
@@ -2027,7 +2027,7 @@ export default function DashboardScreen() {
- {prod.is_reward ? "Offert" : `${(prod.prix ?? 0).toFixed(2)}€`}
+ {prod.is_reward && (prod.prix ?? 0) === 0 ? "Offert" : `${(prod.prix ?? 0).toFixed(2)}€`}
))}
diff --git a/mobile/certs/certificate.pem b/mobile/certs/certificate.pem
index a53e9f6b..59a09761 100644
--- a/mobile/certs/certificate.pem
+++ b/mobile/certs/certificate.pem
@@ -1,18 +1,18 @@
-----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==
+MIIC9zCCAd+gAwIBAgIJdh2LLVj8WJMXMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNV
+BAMTGk1pbGlldSBOYW50YWlzIC8gVWJlciBTdHVwMB4XDTI2MDgxNjEyMTA0OVoX
+DTM2MDgxNjEyMTA0OVowJTEjMCEGA1UEAxMaTWlsaWV1IE5hbnRhaXMgLyBVYmVy
+IFN0dXAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsUjXhTk71qOWK
+XB+9jJ9FiP6+FXrPM9fj9a/75XjTNtIfS55m35rlkz4cfZvtjW1XziyMe+86/wGM
+fg3iG8Zvu4DOS4aONFBz8kWosi6p2AAIPUQVIEdCEjL0V5r0MYfHvFu+GCLVbPeQ
+hrd11Y6HwWEhCsKsK3OQXlN8PK9cpzeSeoCNdzA1iseFuAXoPu9Gm88lLThJgEvx
+z95/piTa3OavRaYraP+ytd3/f87XbVrbtRGJq6sPIV9FHBt4z4AXcGwUIqTQ1Gwi
+kMOaROMlQpzYlKS+yoZ72p6xs9dF0kBKsitlbavJ2E7ZrVoQh0Ufr4/6X26kKGwH
+BvaH3E9vAgMBAAGjKjAoMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggr
+BgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAE5sO2Nvgum66dib70UsbxTO7/8Os
+yHy/RT7zUsgeVVGK18nIbC/SiZVQ9FNLw3ps0bIrXX968Ym4KJ1di5gcw5nuiVpf
+1QNiN0t7vS6Gxaj2l9/TwoL409ud1kMZ2lXKyHnwVaNontNt7Y+e6HnOLeYNEepn
+5v8qfwOa0H3e6o2T71uCepvVLCbmIngnpF5APkDNUbGnJFpoyxrrleNboQ8CZYY4
+uEsrPBZSRlhWLMLsJQOgLiRlnAYgqxS9C3DlxpZbsvA9eIDJuPquzUOuNX1KTMQl
+My9rR/pXYvwd7xuOD+crkGRL6bLW9HRq9FWKT6JWEkNSagmyGxcPv/ilgQ==
-----END CERTIFICATE-----