@salifbiaye/create-boutique 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/dist/code.js +110 -0
- package/cli/dist/commands.js +241 -17
- package/cli/dist/create.js +108 -15
- package/cli/dist/generate.js +108 -39
- package/cli/dist/guide.js +12 -4
- package/cli/dist/index.js +38 -7
- package/cli/dist/payments.js +150 -0
- package/cli/dist/project.js +42 -3
- package/docs/fiche-client.en.html +2 -2
- package/docs/fiche-client.html +2 -2
- package/docs/guide-complet.fr.html +681 -256
- package/docs/guide-images/categories.jpg +0 -0
- package/docs/guide-images/clients.jpg +0 -0
- package/docs/guide-images/collections.jpg +0 -0
- package/docs/guide-images/commande-detail.jpg +0 -0
- package/docs/guide-images/commande-expedier.jpg +0 -0
- package/docs/guide-images/commande-menu.jpg +0 -0
- package/docs/guide-images/commande-paiement.jpg +0 -0
- package/docs/guide-images/commandes-liste.jpg +0 -0
- package/docs/guide-images/connexion.jpg +0 -0
- package/docs/guide-images/groupes-clients.jpg +0 -0
- package/docs/guide-images/inventaire.jpg +0 -0
- package/docs/guide-images/listes-prix.jpg +0 -0
- package/docs/guide-images/menu.jpg +0 -0
- package/docs/guide-images/mes-boutiques-fermer.jpg +0 -0
- package/docs/guide-images/mes-boutiques-panneau.jpg +0 -0
- package/docs/guide-images/mes-boutiques.jpg +0 -0
- package/docs/guide-images/parametres-boutique.jpg +0 -0
- package/docs/guide-images/parametres-canaux.jpg +0 -0
- package/docs/guide-images/parametres-emplacements.jpg +0 -0
- package/docs/guide-images/parametres-profil.jpg +0 -0
- package/docs/guide-images/parametres-regions.jpg +0 -0
- package/docs/guide-images/parametres-utilisateurs.jpg +0 -0
- package/docs/guide-images/produit-creer-details.jpg +0 -0
- package/docs/guide-images/produit-detail.jpg +0 -0
- package/docs/guide-images/produit-variantes-menu.jpg +0 -0
- package/docs/guide-images/produit-variantes.jpg +0 -0
- package/docs/guide-images/produits-liste.jpg +0 -0
- package/docs/guide-images/promotion-details.jpg +0 -0
- package/docs/guide-images/promotion-type.jpg +0 -0
- package/docs/guide-images/promotions-liste.jpg +0 -0
- package/docs/guide-images/site-boutique.jpg +0 -0
- package/docs/guide-images/site-fermee.png +0 -0
- package/docs/guide-images/vue-ensemble.jpg +0 -0
- package/package.json +3 -2
- package/templates/backend/medusa-config.ts +31 -25
- package/templates/backend/package-lock.json +21 -0
- package/templates/backend/package.json +2 -0
- package/templates/backend/src/admin/i18n/json/en.json +63 -5
- package/templates/backend/src/admin/i18n/json/fr.json +63 -5
- package/templates/backend/src/admin/routes/boutique-campaigns/campaign-drawer.tsx +29 -17
- package/templates/backend/src/admin/routes/boutique-campaigns/page.tsx +84 -6
- package/templates/backend/src/admin/routes/reviews/page.tsx +259 -0
- package/templates/backend/src/api/admin/active-boutique/route.ts +25 -0
- package/templates/backend/src/api/admin/boutique-campaigns/[salesChannelId]/route.ts +7 -1
- package/templates/backend/src/api/admin/boutique-campaigns/route.ts +9 -1
- package/templates/backend/src/api/admin/boutique-online/[salesChannelId]/route.ts +22 -0
- package/templates/backend/src/api/admin/reviews/[id]/route.ts +15 -0
- package/templates/backend/src/api/admin/reviews/route.ts +47 -0
- package/templates/backend/src/api/middlewares.ts +9 -0
- package/templates/backend/src/api/store/campaign/route.ts +5 -1
- package/templates/backend/src/api/store/reviews/latest/route.ts +39 -0
- package/templates/backend/src/api/store/reviews/order/route.ts +36 -0
- package/templates/backend/src/api/store/reviews/route.ts +43 -0
- package/templates/backend/src/api/store/reviews/summary/route.ts +28 -0
- package/templates/backend/src/emails/en.json +3 -1
- package/templates/backend/src/emails/fr.json +3 -1
- package/templates/backend/src/lib/active.ts +41 -0
- package/templates/backend/src/lib/email-config.ts +14 -0
- package/templates/backend/src/lib/emails.ts +9 -0
- package/templates/backend/src/lib/notify.ts +14 -8
- package/templates/backend/src/lib/payment-config.ts +66 -0
- package/templates/backend/src/lib/project.ts +38 -1
- package/templates/backend/src/lib/redirect-payment.ts +264 -0
- package/templates/backend/src/lib/review-token.ts +40 -0
- package/templates/backend/src/lib/reviews.ts +25 -0
- package/templates/backend/src/modules/naboopay/index.ts +6 -0
- package/templates/backend/src/modules/naboopay/service.ts +92 -0
- package/templates/backend/src/modules/paydunya/index.ts +6 -0
- package/templates/backend/src/modules/paydunya/service.ts +89 -0
- package/templates/backend/src/modules/paytech/index.ts +6 -0
- package/templates/backend/src/modules/paytech/service.ts +114 -0
- package/templates/backend/src/modules/product-review/index.ts +8 -0
- package/templates/backend/src/modules/product-review/migrations/Migration20261001090000.ts +18 -0
- package/templates/backend/src/modules/product-review/models/review.ts +25 -0
- package/templates/backend/src/modules/product-review/service.ts +6 -0
- package/templates/backend/src/modules/product-review/validators.ts +27 -0
- package/templates/backend/src/modules/smtp/index.ts +6 -0
- package/templates/backend/src/modules/smtp/service.ts +73 -0
- package/templates/backend/src/scripts/rewrite-file-urls.ts +56 -0
- package/templates/backend/src/scripts/seed.ts +60 -36
- package/templates/backend/src/subscribers/fulfillment-emails.ts +1 -1
- package/templates/backend/src/workflows/reviews.ts +21 -0
- package/templates/backend/src/workflows/steps/create-review.ts +52 -0
- package/templates/backend/src/workflows/steps/update-review.ts +36 -0
- package/templates/storefront/PERSONNALISER.md +134 -0
- package/templates/storefront/locales/en.json +198 -11
- package/templates/storefront/locales/fr.json +198 -11
- package/templates/storefront/public/custom/LISEZMOI.txt +2 -0
- package/templates/storefront/src/app/(checkout)/layout.tsx +1 -1
- package/templates/storefront/src/app/(main)/layout.tsx +8 -1
- package/templates/storefront/src/app/(main)/legal/[page]/page.tsx +42 -7
- package/templates/storefront/src/app/(main)/page.tsx +149 -4
- package/templates/storefront/src/app/(main)/products/[handle]/page.tsx +4 -1
- package/templates/storefront/src/app/(main)/review/page.tsx +60 -0
- package/templates/storefront/src/app/(main)/store/page.tsx +3 -1
- package/templates/storefront/src/app/(main)/template.tsx +17 -2
- package/templates/storefront/src/app/layout.tsx +13 -1
- package/templates/storefront/src/lib/boutique/campaign.ts +5 -1
- package/templates/storefront/src/lib/boutique/config.ts +71 -23
- package/templates/storefront/src/lib/boutique/page-name.ts +25 -0
- package/templates/storefront/src/lib/boutique/schema.ts +7 -2
- package/templates/storefront/src/lib/config.ts +37 -8
- package/templates/storefront/src/lib/constants.tsx +17 -0
- package/templates/storefront/src/lib/data/cart.ts +4 -1
- package/templates/storefront/src/lib/data/cookies.ts +14 -2
- package/templates/storefront/src/lib/data/review-actions.ts +28 -0
- package/templates/storefront/src/lib/data/reviews.ts +64 -0
- package/templates/storefront/src/modules/checkout/components/payment/index.tsx +26 -4
- package/templates/storefront/src/modules/checkout/components/payment-button/index.tsx +85 -2
- package/templates/storefront/src/modules/checkout/components/shipping-address/index.tsx +6 -1
- package/templates/storefront/src/modules/products/templates/index.tsx +5 -0
- package/templates/storefront/src/modules/store/components/shop-search.tsx +85 -0
- package/templates/storefront/src/modules/store/templates/index.tsx +16 -8
- package/templates/storefront/src/modules/store/templates/paginated-products.tsx +15 -3
- package/templates/storefront/src/modules/ub/album.tsx +2 -0
- package/templates/storefront/src/modules/ub/arena.tsx +182 -0
- package/templates/storefront/src/modules/ub/campaign-blocks.tsx +3 -2
- package/templates/storefront/src/modules/ub/campaign-ended.tsx +27 -13
- package/templates/storefront/src/modules/ub/cinema.tsx +2 -0
- package/templates/storefront/src/modules/ub/cookie-banner.tsx +62 -0
- package/templates/storefront/src/modules/ub/gala.tsx +2 -0
- package/templates/storefront/src/modules/ub/grimoire.tsx +35 -8
- package/templates/storefront/src/modules/ub/harvest.tsx +2 -1
- package/templates/storefront/src/modules/ub/hero-image.tsx +14 -0
- package/templates/storefront/src/modules/ub/home-kit.ts +45 -0
- package/templates/storefront/src/modules/ub/lantern.tsx +244 -0
- package/templates/storefront/src/modules/ub/locker.tsx +180 -0
- package/templates/storefront/src/modules/ub/mosaic.tsx +189 -0
- package/templates/storefront/src/modules/ub/nav.tsx +8 -5
- package/templates/storefront/src/modules/ub/review-form.tsx +62 -0
- package/templates/storefront/src/modules/ub/reviews.tsx +131 -0
- package/templates/storefront/src/modules/ub/riviera.tsx +175 -0
- package/templates/storefront/src/modules/ub/souk.tsx +214 -0
- package/templates/storefront/src/modules/ub/sunset-checklist.tsx +95 -0
- package/templates/storefront/src/modules/ub/sunset.tsx +164 -0
- package/templates/storefront/src/modules/ub/tech.tsx +2 -2
- package/templates/storefront/src/modules/ub/training.tsx +196 -0
- package/templates/storefront/src/proxy.ts +4 -1
- package/templates/storefront/src/styles/custom.css +26 -0
- package/templates/storefront/src/styles/ub/album.css +1 -1
- package/templates/storefront/src/styles/ub/arena.css +120 -0
- package/templates/storefront/src/styles/ub/cinema.css +1 -1
- package/templates/storefront/src/styles/ub/gala.css +1 -1
- package/templates/storefront/src/styles/ub/grimoire.css +23 -7
- package/templates/storefront/src/styles/ub/harvest.css +7 -6
- package/templates/storefront/src/styles/ub/lantern.css +157 -0
- package/templates/storefront/src/styles/ub/locker.css +115 -0
- package/templates/storefront/src/styles/ub/media.css +10 -0
- package/templates/storefront/src/styles/ub/mosaic.css +118 -0
- package/templates/storefront/src/styles/ub/motion.css +0 -2
- package/templates/storefront/src/styles/ub/overrides.css +10 -0
- package/templates/storefront/src/styles/ub/pages.css +176 -0
- package/templates/storefront/src/styles/ub/reviews.css +85 -0
- package/templates/storefront/src/styles/ub/riviera.css +103 -0
- package/templates/storefront/src/styles/ub/souk.css +144 -0
- package/templates/storefront/src/styles/ub/sunset.css +123 -0
- package/templates/storefront/src/styles/ub/tech.css +5 -8
- package/templates/storefront/src/styles/ub/training.css +120 -0
- package/templates/storefront/themes/jeux/campaign.en.json +31 -0
- package/templates/storefront/themes/jeux/campaign.fr.json +31 -0
- package/templates/storefront/themes/jeux/theme.json +11 -0
- package/templates/storefront/themes/jeux-2/campaign.en.json +31 -0
- package/templates/storefront/themes/jeux-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/jeux-2/theme.json +11 -0
- package/templates/storefront/themes/mubarak/campaign.en.json +31 -0
- package/templates/storefront/themes/mubarak/campaign.fr.json +31 -0
- package/templates/storefront/themes/mubarak/theme.json +11 -0
- package/templates/storefront/themes/mubarak-2/campaign.en.json +31 -0
- package/templates/storefront/themes/mubarak-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/mubarak-2/theme.json +11 -0
- package/templates/storefront/themes/sport/campaign.en.json +31 -0
- package/templates/storefront/themes/sport/campaign.fr.json +31 -0
- package/templates/storefront/themes/sport/theme.json +11 -0
- package/templates/storefront/themes/sport-2/campaign.en.json +31 -0
- package/templates/storefront/themes/sport-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/sport-2/theme.json +11 -0
- package/templates/storefront/themes/summer/campaign.en.json +31 -0
- package/templates/storefront/themes/summer/campaign.fr.json +31 -0
- package/templates/storefront/themes/summer/theme.json +11 -0
- package/templates/storefront/themes/summer-2/campaign.en.json +31 -0
- package/templates/storefront/themes/summer-2/campaign.fr.json +31 -0
- package/templates/storefront/themes/summer-2/theme.json +11 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
export const MOBILE = {
|
|
4
|
+
paytech: {
|
|
5
|
+
name: "PayTech",
|
|
6
|
+
methods: "Wave, Orange Money, Free Money, Wizall, carte bancaire (Visa, Mastercard)",
|
|
7
|
+
currencies: ["xof", "eur", "usd", "cad", "gbp", "mad"],
|
|
8
|
+
test: "pas de simulation : chaque paiement test débite vraiment 100 à 150 FCFA (quel que soit le panier) sur le Wave / Orange Money du testeur ; l'argent arrive sur le solde PayTech de la boutique (moins les frais)",
|
|
9
|
+
needs: "compte PayTech + 2 clés (API_KEY, API_SECRET) ; mode prod après activation du compte",
|
|
10
|
+
refund: "bouton Remboursement de l'admin : l'argent repart vers le client (remboursement total uniquement)",
|
|
11
|
+
site: "paytech.sn",
|
|
12
|
+
},
|
|
13
|
+
paydunya: {
|
|
14
|
+
name: "PayDunya",
|
|
15
|
+
methods: "Wave, Orange Money, Free Money, carte bancaire…",
|
|
16
|
+
currencies: ["xof"],
|
|
17
|
+
test: "sandbox gratuite (aucun vrai paiement), clés de test séparées",
|
|
18
|
+
needs: "compte PayDunya + application + 3 clés (master key, private key, token)",
|
|
19
|
+
refund: "dans le tableau de bord PayDunya (pas d'API), puis enregistré dans l'admin",
|
|
20
|
+
site: "paydunya.com",
|
|
21
|
+
},
|
|
22
|
+
naboopay: {
|
|
23
|
+
name: "NabooPay",
|
|
24
|
+
methods: "Wave, Orange Money (carte annoncée)",
|
|
25
|
+
currencies: ["xof"],
|
|
26
|
+
test: "pas de mode test documenté : tester avec de petits montants réels",
|
|
27
|
+
needs: "compte NabooPay + 1 clé API + secret du webhook (tableau de bord → Paramètres → Intégration)",
|
|
28
|
+
refund: "bouton Remboursement de l'admin : l'argent repart vers le client (total uniquement, Wave et Orange Money)",
|
|
29
|
+
site: "naboopay.com",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
const stop = (v) => {
|
|
33
|
+
if (p.isCancel(v)) {
|
|
34
|
+
p.cancel("Création annulée.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
return v;
|
|
38
|
+
};
|
|
39
|
+
/** Devises des boutiques pour lesquelles l'agrégateur ne s'affichera pas */
|
|
40
|
+
const unsupported = (id, currencies) => currencies.filter((c) => !MOBILE[id].currencies.includes(c));
|
|
41
|
+
/** Explication affichée avant le choix : critères de chaque agrégateur */
|
|
42
|
+
export function mobileNote(currencies) {
|
|
43
|
+
const lines = Object.keys(MOBILE).flatMap((id) => {
|
|
44
|
+
const m = MOBILE[id];
|
|
45
|
+
const off = unsupported(id, currencies);
|
|
46
|
+
return [
|
|
47
|
+
pc.bold(`${m.name}`) + pc.dim(` (${m.site})`),
|
|
48
|
+
` Moyens : ${m.methods}`,
|
|
49
|
+
` Devises : ${m.currencies.map((c) => c.toUpperCase()).join(", ")}` + (off.length ? pc.yellow(` → pas pour vos boutiques en ${off.join(", ").toUpperCase()}`) : ""),
|
|
50
|
+
` Test : ${m.test}`,
|
|
51
|
+
` Il faut : ${m.needs}`,
|
|
52
|
+
` Rembourser : ${m.refund}`,
|
|
53
|
+
"",
|
|
54
|
+
];
|
|
55
|
+
});
|
|
56
|
+
return [
|
|
57
|
+
"Le client choisit « Wave, Orange Money… », paie sur la page sécurisée de l'agrégateur,",
|
|
58
|
+
"puis revient sur la boutique ; le montant est toujours calculé par le backend.",
|
|
59
|
+
"Confirmation automatique (même si le client ferme la page) : backend en ligne en HTTPS.",
|
|
60
|
+
"",
|
|
61
|
+
...lines,
|
|
62
|
+
"Plusieurs possibles (le client voit un choix par agrégateur). Clés : maintenant (saisie masquée)",
|
|
63
|
+
"ou plus tard dans .env, puis « docker compose restart backend ».",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
/** Questions : agrégateurs, clés (facultatives maintenant), mode test / prod */
|
|
67
|
+
export async function askMobile(a, currencies) {
|
|
68
|
+
if (a.mobile)
|
|
69
|
+
return a;
|
|
70
|
+
p.note(mobileNote(currencies), "Paiement mobile : Wave, Orange Money, carte (Afrique de l'Ouest)");
|
|
71
|
+
const mobile = stop(await p.multiselect({
|
|
72
|
+
message: "Agrégateurs de paiement mobile (espace = cocher, Entrée sans rien = aucun)",
|
|
73
|
+
options: Object.keys(MOBILE).map((id) => {
|
|
74
|
+
const off = unsupported(id, currencies);
|
|
75
|
+
return {
|
|
76
|
+
value: id,
|
|
77
|
+
label: MOBILE[id].name,
|
|
78
|
+
hint: `${MOBILE[id].currencies.length > 1 ? "XOF, EUR, USD…" : "XOF uniquement"}${off.length ? ` · inactif en ${off.join(", ").toUpperCase()}` : ""}`,
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
81
|
+
required: false,
|
|
82
|
+
}));
|
|
83
|
+
for (const id of mobile) {
|
|
84
|
+
const off = unsupported(id, currencies);
|
|
85
|
+
if (off.length === currencies.length) {
|
|
86
|
+
p.log.warn(`${MOBILE[id].name} n'acceptera aucune de vos boutiques (${currencies.join(", ").toUpperCase()}) : il faut une boutique en XOF.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const out = { mobile };
|
|
90
|
+
const secret = async (message) => stop(await p.password({ message: `${message} (vide = plus tard dans .env)` })) || undefined;
|
|
91
|
+
const env = async (name, test) => stop(await p.select({
|
|
92
|
+
message: `${name} : mode`,
|
|
93
|
+
options: [
|
|
94
|
+
{ value: "test", label: "test", hint: test },
|
|
95
|
+
{ value: "prod", label: "production", hint: "vrais paiements, compte activé" },
|
|
96
|
+
],
|
|
97
|
+
initialValue: "test",
|
|
98
|
+
}));
|
|
99
|
+
if (mobile.includes("paytech")) {
|
|
100
|
+
out.paytechKey = await secret("PayTech : API_KEY");
|
|
101
|
+
out.paytechSecret = out.paytechKey ? await secret("PayTech : API_SECRET") : undefined;
|
|
102
|
+
out.paytechEnv = await env("PayTech", "vrais débits de 100 à 150 FCFA par paiement test, reçus sur le solde PayTech");
|
|
103
|
+
}
|
|
104
|
+
if (mobile.includes("paydunya")) {
|
|
105
|
+
out.paydunyaMaster = await secret("PayDunya : clé principale (master key)");
|
|
106
|
+
out.paydunyaPrivate = out.paydunyaMaster ? await secret("PayDunya : clé privée") : undefined;
|
|
107
|
+
out.paydunyaToken = out.paydunyaMaster ? await secret("PayDunya : token") : undefined;
|
|
108
|
+
out.paydunyaEnv = await env("PayDunya", "sandbox, aucun vrai paiement");
|
|
109
|
+
}
|
|
110
|
+
if (mobile.includes("naboopay")) {
|
|
111
|
+
out.naboopayKey = await secret("NabooPay : clé API");
|
|
112
|
+
p.log.info("NabooPay : le secret du webhook se crée après la mise en ligne (tableau de bord → Paramètres → Intégration) ; à mettre dans NABOOPAY_WEBHOOK_SECRET.");
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** Ligne du récapitulatif */
|
|
117
|
+
export function mobileSummary(a) {
|
|
118
|
+
if (!a.mobile?.length)
|
|
119
|
+
return "Wave / Orange Money : non (possible plus tard : clés dans .env, voir README)";
|
|
120
|
+
const one = (id) => {
|
|
121
|
+
const keys = id === "paytech" ? a.paytechKey && a.paytechSecret : id === "paydunya" ? a.paydunyaMaster && a.paydunyaPrivate && a.paydunyaToken : a.naboopayKey;
|
|
122
|
+
const mode = id === "paytech" ? a.paytechEnv : id === "paydunya" ? a.paydunyaEnv : undefined;
|
|
123
|
+
return `${MOBILE[id].name}${mode ? ` (${mode})` : ""}${keys ? "" : " — clés à mettre dans .env"}`;
|
|
124
|
+
};
|
|
125
|
+
return `Wave / Orange Money : ${a.mobile.map(one).join(", ")}`;
|
|
126
|
+
}
|
|
127
|
+
/** E-mails : Resend, SMTP (Gmail…) ou aucun */
|
|
128
|
+
export async function askMail() {
|
|
129
|
+
const kind = stop(await p.select({
|
|
130
|
+
message: "E-mails aux clients (confirmation, expédition, mot de passe oublié…)",
|
|
131
|
+
options: [
|
|
132
|
+
{ value: "smtp", label: "SMTP (Gmail, OVH, Brevo…)", hint: "Gmail : mot de passe d'application, ~500 e-mails/jour" },
|
|
133
|
+
{ value: "resend", label: "Resend", hint: "clé re_…, domaine vérifié ; 3 000 e-mails/mois gratuits" },
|
|
134
|
+
{ value: "none", label: "Aucun pour l'instant", hint: "la boutique marche, sans e-mails" },
|
|
135
|
+
],
|
|
136
|
+
initialValue: "smtp",
|
|
137
|
+
}));
|
|
138
|
+
if (kind !== "smtp")
|
|
139
|
+
return { kind };
|
|
140
|
+
p.note([
|
|
141
|
+
"Gmail : Compte Google → Sécurité → Validation en deux étapes (activée)",
|
|
142
|
+
"→ Mots de passe des applications → créer (16 caractères). Jamais le mot de passe du compte.",
|
|
143
|
+
"Les e-mails partent de l'adresse Gmail, avec le nom de la boutique ; les réponses vont au marchand.",
|
|
144
|
+
].join("\n"), "SMTP");
|
|
145
|
+
const host = stop(await p.text({ message: "Serveur SMTP", initialValue: "smtp.gmail.com" }));
|
|
146
|
+
const port = Number(stop(await p.text({ message: "Port (465 = SSL, 587 = STARTTLS)", initialValue: "465", validate: (v) => (/^\d+$/.test(v ?? "") ? undefined : "un nombre") })));
|
|
147
|
+
const user = stop(await p.text({ message: "Identifiant (adresse e-mail du compte)", placeholder: "boutique@gmail.com" }));
|
|
148
|
+
const pass = stop(await p.password({ message: "Mot de passe d'application (vide = plus tard dans .env)" })) || undefined;
|
|
149
|
+
return { kind, smtp: { smtpHost: host.trim(), smtpPort: port, smtpUser: user.trim() || undefined, smtpPass: pass } };
|
|
150
|
+
}
|
package/cli/dist/project.js
CHANGED
|
@@ -16,8 +16,14 @@ export const projectSchema = z.object({
|
|
|
16
16
|
templateVersion: z.string().optional(),
|
|
17
17
|
apiPort: z.number().int().min(1).max(65535).default(9000),
|
|
18
18
|
storagePort: z.number().int().min(1).max(65535).default(9100),
|
|
19
|
-
/** Production : adresses publiques (https) du backend/admin et des images. Présent =
|
|
19
|
+
/** Production : adresses publiques (https) du backend/admin et des images. Présent = ports sur 127.0.0.1 (ton reverse proxy les expose), Caddyfile.exemple fourni. */
|
|
20
20
|
urls: z.strictObject({ api: z.url(), files: z.url() }).optional(),
|
|
21
|
+
/**
|
|
22
|
+
* multi (défaut) : un site par boutique, toutes en ligne (1 container, 1 port, 1 domaine chacune).
|
|
23
|
+
* single : un seul site (site.port, site.url) qui affiche la boutique active, choisie dans admin → Campagnes.
|
|
24
|
+
*/
|
|
25
|
+
mode: z.enum(["multi", "single"]).default("multi"),
|
|
26
|
+
site: z.strictObject({ port: z.number().int().min(1).max(65535), url: z.url().optional() }).optional(),
|
|
21
27
|
defaults: z.looseObject({
|
|
22
28
|
storeName: z.string().min(1).max(60),
|
|
23
29
|
currency: code3,
|
|
@@ -31,7 +37,8 @@ export const projectSchema = z.object({
|
|
|
31
37
|
.array(z.strictObject({
|
|
32
38
|
slug: z.string().regex(SLUG, "slug : minuscules, chiffres et tirets"),
|
|
33
39
|
theme: z.string(),
|
|
34
|
-
|
|
40
|
+
/** Mode multi : port du site de la boutique (inutile en mode single) */
|
|
41
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
35
42
|
url: z.url().optional(),
|
|
36
43
|
storeName: z.string().min(1).max(60).optional(),
|
|
37
44
|
currency: code3.optional(),
|
|
@@ -59,7 +66,13 @@ export function validateProject(raw) {
|
|
|
59
66
|
for (const b of p.boutiques)
|
|
60
67
|
if (!themes.includes(b.theme))
|
|
61
68
|
errors.push(`${b.slug} : thème inconnu « ${b.theme} » (disponibles : ${themes.join(", ")})`);
|
|
62
|
-
|
|
69
|
+
if (p.mode === "single" && !p.site)
|
|
70
|
+
errors.push("mode single : bloc site { port } obligatoire");
|
|
71
|
+
if (p.mode === "multi")
|
|
72
|
+
for (const b of p.boutiques)
|
|
73
|
+
if (!b.port)
|
|
74
|
+
errors.push(`${b.slug} : port obligatoire (mode multi)`);
|
|
75
|
+
const ports = [p.apiPort, p.storagePort, ...storePorts(p)];
|
|
63
76
|
for (const port of new Set(ports))
|
|
64
77
|
if (ports.filter((x) => x === port).length > 1)
|
|
65
78
|
errors.push(`port utilisé deux fois : ${port}`);
|
|
@@ -119,3 +132,29 @@ export function writeJsonSchema(dir) {
|
|
|
119
132
|
const schema = z.toJSONSchema(projectSchema, { io: "input", unrepresentable: "any" });
|
|
120
133
|
fs.writeFileSync(path.join(dir, "config", "project.schema.json"), JSON.stringify(schema, null, 2) + "\n");
|
|
121
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Adresse publique saisie par l'utilisateur → origine seule (https://images.marque.com).
|
|
137
|
+
* Accepte « images.marque.com », une barre finale ou « /medusa » (ajouté ensuite par le CLI pour S3_FILE_URL).
|
|
138
|
+
*/
|
|
139
|
+
export function normalizeUrl(v, what) {
|
|
140
|
+
const raw = /^https?:\/\//.test(v.trim()) ? v.trim() : `https://${v.trim()}`;
|
|
141
|
+
let u;
|
|
142
|
+
try {
|
|
143
|
+
u = new URL(raw);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new ProjectError(`${what} : adresse invalide « ${v} » (ex. https://images.marque.com)`);
|
|
147
|
+
}
|
|
148
|
+
const path = u.pathname.replace(/\/+$/, "").replace(/\/medusa$/, "");
|
|
149
|
+
if (path)
|
|
150
|
+
throw new ProjectError(`${what} : donnez seulement le domaine (ex. https://images.marque.com), sans chemin « ${u.pathname} »`);
|
|
151
|
+
return u.origin;
|
|
152
|
+
}
|
|
153
|
+
export function fronts(p) {
|
|
154
|
+
if (p.mode === "single")
|
|
155
|
+
return p.site ? [{ service: "storefront", boutique: "auto", port: p.site.port, url: p.site.url }] : [];
|
|
156
|
+
return p.boutiques.filter((b) => b.port).map((b) => ({ service: `storefront-${b.slug}`, boutique: b.slug, port: b.port, url: b.url }));
|
|
157
|
+
}
|
|
158
|
+
export const storePorts = (p) => fronts(p).map((f) => f.port);
|
|
159
|
+
/** Adresse publique d'une boutique (en mode single : celle du site unique). */
|
|
160
|
+
export const boutiqueUrl = (p, b) => p.mode === "single" ? (p.site?.url ?? `http://localhost:${p.site?.port}`) : (b.url ?? `http://localhost:${b.port}`);
|
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
<div class="screen">
|
|
99
99
|
<div class="num">3</div>
|
|
100
100
|
<div>
|
|
101
|
-
<h2>
|
|
102
|
-
<div class="where">Menu:
|
|
101
|
+
<h2>My stores <small>texts and images of each store</small></h2>
|
|
102
|
+
<div class="where">Menu: My stores</div>
|
|
103
103
|
<ol>
|
|
104
104
|
<li>Pick the store (Halloween, Black Friday, Christmas…) and click <b>Edit</b>.</li>
|
|
105
105
|
<li>Change the title, text, button, hero image, logo and the offer dates.</li>
|
package/docs/fiche-client.html
CHANGED
|
@@ -98,8 +98,8 @@
|
|
|
98
98
|
<div class="screen">
|
|
99
99
|
<div class="num">3</div>
|
|
100
100
|
<div>
|
|
101
|
-
<h2>
|
|
102
|
-
<div class="where">Menu :
|
|
101
|
+
<h2>Mes boutiques <small>site en ligne ou fermé, textes et images</small></h2>
|
|
102
|
+
<div class="where">Menu : Mes boutiques</div>
|
|
103
103
|
<ol>
|
|
104
104
|
<li>Choisissez la boutique (Halloween, Black Friday, Noël…) et cliquez sur <b>Modifier</b>.</li>
|
|
105
105
|
<li>Changez le titre, le texte, le bouton, l'image principale, le logo et les dates de l'offre.</li>
|