Refactor Dockerfile, update README, and enhance Calculator component

- Refactored Dockerfile for improved multi-stage builds and added development and production configurations.
- Updated README with clearer project description, local development instructions, and added contact information.
- Enhanced Calculator component to manage theme and history using localStorage, improving user experience.
- Added new pages for legal mentions and privacy policy, including relevant metadata.
- Updated docker-compose.yml for better service management and added environment variables.
- Introduced a new LICENSE file outlining usage rights and responsibilities.
This commit is contained in:
Puechberty Arthur
2026-04-01 23:30:51 +02:00
parent 572f29c442
commit 8400e26c0a
9 changed files with 318 additions and 162 deletions
+16 -24
View File
@@ -1,34 +1,26 @@
# === Étape 1 : Build === FROM node:22-alpine AS calculatrice-base
FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
# Copier les fichiers de dépendances pour profiter du cache Docker FROM calculatrice-base AS calculatrice-deps
COPY package*.json ./ COPY package.json package-lock.json ./
# Installer uniquement ce qu'il faut pour le build
RUN npm ci RUN npm ci
# Copier tout le code FROM calculatrice-deps AS calculatrice-dev
COPY . . COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"]
# Build Next.js pour la production FROM calculatrice-deps AS calculatrice-builder
COPY . .
RUN npm run build RUN npm run build
# === Étape 2 : Runner léger === FROM calculatrice-base AS calculatrice-runner
FROM node:20-alpine AS runner
WORKDIR /app
# Copier uniquement ce qui est nécessaire pour la prod
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
# Mode production
ENV NODE_ENV=production ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
COPY --from=calculatrice-builder /app/.next/standalone ./
COPY --from=calculatrice-builder /app/.next/static ./.next/static
COPY --from=calculatrice-builder /app/public ./public
EXPOSE 3000 EXPOSE 3000
CMD ["node", "server.js"]
# Lancer le serveur Next.js
CMD ["npm", "start"]
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2026 Arthur
Licence pour tous les projets Arthur
1. Définition
Cette licence définit les droits et obligations concernant l'utilisation, la modification et la redistribution du code fourni par l'auteur.
2. Autorisation d'utilisation
Vous êtes libre d'utiliser ce code pour vos projets personnels ou commerciaux. L'utilisation doit inclure une mention de l'auteur dune manière libre (ex: "inspiré de ArthurP").
3. Modification
Vous pouvez modifier, adapter ou améliorer le code pour vos besoins. Les modifications doivent être identifiées comme telles et ne doivent pas être présentées comme l'original.
4. Redistribution
- Le code original **ne peut pas être redistribué tel quel**.
- Les versions modifiées peuvent être partagées, sous réserve de mentionner l'auteur original.
5. Usage commercial
Lusage commercial des versions modifiées est autorisé. Vous pouvez générer des revenus avec votre version modifiée.
6. Attribution
L'auteur original doit être cité dune manière libre, mais visible, sur tout projet utilisant ce code ou ses dérivés.
7. Responsabilité
Le code est fourni "tel quel", sans garantie daucune sorte. Lauteur décline toute responsabilité pour tout dommage direct ou indirect résultant de lutilisation du code.
+24 -22
View File
@@ -1,33 +1,32 @@
# Calculatrice # Calculatrice
Application web de calculatrice réalisée avec Next.js. Calculatrice web (simple + scientifique) construite avec Next.js.
## Site en ligne ## Liens
- Projet: https://calculatrice.arthurp.fr - Site: https://calculatrice.arthurp.fr
- Site principal: https://arthurp.fr
- Contact: https://contact.arthurp.fr
- Email: contact@arthurp.fr
## Objectif ## Stack
Proposer une calculatrice simple, rapide et responsive, utilisable sur desktop et mobile.
## Stack technique
- Next.js 16 - Next.js 16
- React 19 - React 19
- TypeScript - TypeScript
## Lancement en local ## Developpement local
Prerequis: Node.js 20+ Prerequis: Node.js 22+
```bash ```bash
npm install npm ci
npm run dev npm run dev
``` ```
Application accessible sur http://localhost:3000 Application: http://localhost:3000
## Scripts utiles ## Scripts
```bash ```bash
npm run dev npm run dev
@@ -36,21 +35,24 @@ npm run start
npm run lint npm run lint
``` ```
## Deploiement ## Docker
Build de production: Mode dev:
```bash ```bash
npm run build docker compose --profile dev up --build
``` ```
Ensuite deployer sur votre plateforme cible (Vercel, VPS, etc.). Mode production:
## Backlinks ```bash
docker compose --profile prod up --build -d
```
- Calculatrice en ligne: https://calculatrice.arthurp.fr - Dev: http://localhost:3000
- Site principal: https://arthurp.fr - Prod: http://localhost:3014
## Licence ## Pages legales
Projet personnel. - /mentions-legales
- /politique-de-confidentialite
+145 -110
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import Link from "next/link";
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
/* ══════════════════════════════════════════════ /* ══════════════════════════════════════════════
@@ -151,39 +152,37 @@ export default function Calculator() {
const [error, setError] = useState(""); // Message d'erreur const [error, setError] = useState(""); // Message d'erreur
const [steps, setSteps] = useState<string[]>([]); // Étapes de calcul const [steps, setSteps] = useState<string[]>([]); // Étapes de calcul
const [showSteps, setShowSteps] = useState(false); // Afficher les étapes const [showSteps, setShowSteps] = useState(false); // Afficher les étapes
const [history, setHistory] = useState<HistoryEntry[]>([]); // Historique const [history, setHistory] = useState<HistoryEntry[]>(() => {
if (typeof window === "undefined") return [];
try {
const savedHistory = localStorage.getItem(HISTORY_KEY);
return savedHistory ? JSON.parse(savedHistory) : [];
} catch {
return [];
}
}); // Historique
const [showHistory, setShowHistory] = useState(false); // Panneau historique visible const [showHistory, setShowHistory] = useState(false); // Panneau historique visible
const [isScientific, setIsScientific] = useState(false); // Mode scientifique const [isScientific, setIsScientific] = useState(false); // Mode scientifique
const [isDark, setIsDark] = useState(false); // Thème sombre const [isDark, setIsDark] = useState(() => {
if (typeof window === "undefined") return false;
try {
const savedTheme = localStorage.getItem(THEME_KEY);
if (savedTheme === "dark") return true;
if (savedTheme === "light") return false;
return window.matchMedia("(prefers-color-scheme: dark)").matches;
} catch {
return false;
}
}); // Thème sombre
const [copied, setCopied] = useState(false); // Feedback copie const [copied, setCopied] = useState(false); // Feedback copie
const [lastKey, setLastKey] = useState(""); // Dernière touche pressée (feedback visuel) const [lastKey, setLastKey] = useState(""); // Dernière touche pressée (feedback visuel)
const displayRef = useRef<HTMLDivElement>(null); const displayRef = useRef<HTMLDivElement>(null);
const simpleRef = useRef<HTMLButtonElement>(null);
const sciRef = useRef<HTMLButtonElement>(null);
// ── Chargement initial depuis localStorage ── // ── Synchroniser la classe de thème avec l'état courant ──
useEffect(() => { useEffect(() => {
try { document.documentElement.classList.toggle("dark", isDark);
const savedHistory = localStorage.getItem(HISTORY_KEY); }, [isDark]);
if (savedHistory) setHistory(JSON.parse(savedHistory));
const savedTheme = localStorage.getItem(THEME_KEY);
if (savedTheme === "dark") {
setIsDark(true);
document.documentElement.classList.add("dark");
} else if (savedTheme === "light") {
setIsDark(false);
document.documentElement.classList.remove("dark");
} else {
// Détecter la préférence système
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
setIsDark(prefersDark);
}
} catch {
// localStorage non disponible
}
}, []);
// ── Sauvegarder l'historique dans localStorage ── // ── Sauvegarder l'historique dans localStorage ──
useEffect(() => { useEffect(() => {
@@ -430,7 +429,7 @@ export default function Calculator() {
════════════════════════════════════════════ */ ════════════════════════════════════════════ */
return ( return (
<div className="min-h-screen flex flex-col items-center justify-center p-4 sm:p-8 transition-colors duration-300"> <div className="min-h-screen flex flex-col items-center p-4 sm:p-8 transition-colors duration-300">
{/* Lien d'accessibilité : aller au contenu principal */} {/* Lien d'accessibilité : aller au contenu principal */}
<a <a
href="#calculatrice" href="#calculatrice"
@@ -463,23 +462,16 @@ export default function Calculator() {
<div <div
className="mode-slider" className="mode-slider"
style={{ style={{
left: isScientific transform: isScientific ? "translateX(100%)" : "translateX(0)",
? (simpleRef.current?.offsetWidth ?? 80) + 2 + "px"
: "2px",
width: isScientific
? (sciRef.current?.offsetWidth ?? 100) + "px"
: (simpleRef.current?.offsetWidth ?? 80) + "px",
}} }}
/> />
<button <button
ref={simpleRef}
className={!isScientific ? "active" : ""} className={!isScientific ? "active" : ""}
onClick={() => setIsScientific(false)} onClick={() => setIsScientific(false)}
> >
Simple Simple
</button> </button>
<button <button
ref={sciRef}
className={isScientific ? "active" : ""} className={isScientific ? "active" : ""}
onClick={() => setIsScientific(true)} onClick={() => setIsScientific(true)}
> >
@@ -747,102 +739,145 @@ export default function Calculator() {
</div> </div>
)} )}
{/* ── Footer SEO avec contenu sémantique ── */} {/* ── Contenu SEO ── */}
<footer className="mt-12 w-full max-w-2xl animate-fade-in" style={{ animationDelay: "0.3s" }}> <section className="mt-10 w-full max-w-3xl space-y-5" aria-labelledby="seo-content-heading">
{/* Contenu textuel riche pour le SEO */} <h2 id="seo-content-heading" className="text-xl font-bold" style={{ color: "var(--foreground)" }}>
<article className="mb-8 px-4"> Calculatrice en ligne simple et scientifique
<h2 className="text-xl font-bold mb-3" style={{ color: "var(--foreground)" }}> </h2>
Calculatrice en ligne gratuite <p className="text-sm leading-relaxed" style={{ color: "var(--muted)" }}>
</h2> Cette calculatrice en ligne gratuite permet de faire vos opérations du quotidien et vos calculs plus avances,
<p className="text-sm leading-relaxed mb-4" style={{ color: "var(--muted)" }}> directement depuis votre navigateur. Aucun compte, aucune installation et une interface rapide sur mobile comme
Notre calculatrice en ligne gratuite vous permet d&apos;effectuer tous vos calculs sur ordinateur.
directement dans votre navigateur, sans installation ni inscription. Que vous ayez </p>
besoin d&apos;une simple addition ou d&apos;un calcul scientifique complexe avec des <p className="text-sm leading-relaxed" style={{ color: "var(--muted)" }}>
fonctions trigonométriques, notre outil s&apos;adapte à vos besoins. Le mode simple couvre addition, soustraction, multiplication et division. Le mode scientifique ajoute sin, cos,
</p> tan, logarithmes, racine carree, puissances, factorielle et constantes pi / e. L&apos;historique est conserve
localement pour retrouver vos derniers calculs.
</p>
<h2 className="text-lg font-semibold mb-2" style={{ color: "var(--foreground)" }}> <div className="rounded-xl border p-4" style={{ borderColor: "var(--border-color)", background: "var(--surface)" }}>
Mode simple : les opérations essentielles <h3 className="text-base font-semibold" style={{ color: "var(--foreground)" }}>
</h2> Questions frequentes
<p className="text-sm leading-relaxed mb-4" style={{ color: "var(--muted)" }}> </h3>
Le mode simple couvre les quatre opérations fondamentales : addition (+), <div className="mt-3 space-y-2 text-sm" style={{ color: "var(--muted)" }}>
soustraction (), multiplication (×) et division (÷). Vous pouvez utiliser
des parenthèses pour structurer vos expressions et obtenir des résultats
précis. La gestion des erreurs vous avertit automatiquement en cas de
division par zéro.
</p>
<h2 className="text-lg font-semibold mb-2" style={{ color: "var(--foreground)" }}>
Mode scientifique : des fonctions avancées
</h2>
<p className="text-sm leading-relaxed mb-4" style={{ color: "var(--muted)" }}>
Basculez en mode scientifique pour accéder aux fonctions trigonométriques
(sinus, cosinus, tangente), aux logarithmes (log décimal, logarithme naturel),
à la racine carrée, aux puissances, à la factorielle, ainsi qu&apos;aux
constantes mathématiques π et e. Idéal pour les étudiants, ingénieurs et
professionnels.
</p>
</article>
{/* Section FAQ pour le SEO */}
<section className="mb-8 px-4" aria-labelledby="faq-heading">
<h2 id="faq-heading" className="text-lg font-semibold mb-3" style={{ color: "var(--foreground)" }}>
Questions fréquentes
</h2>
<div className="space-y-3">
{[ {[
{ {
q: "Comment utiliser la calculatrice en ligne ?", q: "Comment faire un calcul rapidement ?",
a: "Cliquez sur les boutons ou utilisez votre clavier pour saisir une expression mathématique, puis appuyez sur « = » ou Entrée pour obtenir le résultat.", a: "Saisissez votre expression puis appuyez sur Entree ou sur le bouton =.",
}, },
{ {
q: "Quelles fonctions scientifiques sont disponibles ?", q: "Puis-je utiliser le clavier ?",
a: "Sinus, cosinus, tangente, logarithme décimal, logarithme naturel, racine carrée, puissances, factorielle, et les constantes π et e.", a: "Oui. Les chiffres et operateurs sont pris en charge. Entree calcule, Echap efface et Retour arriere supprime le dernier caractere.",
}, },
{ {
q: "L'historique des calculs est-il sauvegardé ?", q: "A quoi sert le mode scientifique ?",
a: "Oui, vos 50 derniers calculs sont sauvegardés automatiquement dans votre navigateur et persistent même après fermeture de la page.", a: "Il ajoute les fonctions avancees: sin, cos, tan, log, ln, racine carree, puissances et factorielle.",
}, },
{ {
q: "La calculatrice est-elle vraiment gratuite ?", q: "Est-ce que l'historique est conserve ?",
a: "Oui, 100 % gratuite, sans publicité et sans inscription. Utilisez-la autant que vous le souhaitez.", a: "Oui, les 50 derniers calculs sont stockes localement dans votre navigateur.",
}, },
{ {
q: "Puis-je utiliser des raccourcis clavier ?", q: "Puis-je reutiliser un ancien calcul ?",
a: "Oui ! Chiffres et opérateurs au clavier, Entrée pour calculer, Échap pour effacer, Retour arrière pour supprimer, Ctrl+C pour copier.", a: "Oui, ouvrez l'historique puis cliquez sur une ligne pour recharger l'expression et le resultat.",
}, },
].map((faq, i) => ( {
q: "La calculatrice fonctionne-t-elle sur mobile ?",
a: "Oui, l'interface est adaptee aux ecrans mobiles et ordinateurs.",
},
{
q: "Dois-je creer un compte ?",
a: "Non, aucun compte n'est necessaire pour utiliser la calculatrice.",
},
{
q: "Comment contacter le proprietaire du site ?",
a: "Via contact.arthurp.fr ou par email a contact@arthurp.fr.",
},
].map((item, i) => (
<details <details
key={i} key={i}
className="rounded-lg overflow-hidden transition-all" className="rounded-lg border px-3 py-2"
style={{ style={{ borderColor: "var(--border-color)", background: "var(--background)" }}
background: "var(--surface)",
border: "1px solid var(--border-color)",
}}
> >
<summary <summary className="cursor-pointer font-medium" style={{ color: "var(--foreground)" }}>
className="px-4 py-3 cursor-pointer text-sm font-medium select-none hover:bg-[var(--surface-hover)] transition-colors" {item.q}
style={{ color: "var(--foreground)" }}
>
{faq.q}
</summary> </summary>
<p <p className="mt-2 leading-relaxed">{item.a}</p>
className="px-4 pb-3 text-sm leading-relaxed"
style={{ color: "var(--muted)" }}
>
{faq.a}
</p>
</details> </details>
))} ))}
</div> </div>
</section> </div>
</section>
{/* Copyright */} {/* ── Footer normal en bas de page ── */}
<div className="text-center text-xs pb-4" style={{ color: "var(--muted)" }}> <footer className="mt-auto w-full px-3 pt-10 pb-3">
<p>© {new Date().getFullYear()} Calculatrice en ligne gratuite Simple &amp; Scientifique</p> <div
<p className="mt-1 opacity-60"> className="mx-auto w-full max-w-5xl rounded-2xl border p-4 sm:p-6 backdrop-blur-xl"
Outil de calcul en ligne rapide, gratuit et sans inscription. style={{
</p> background: "color-mix(in srgb, var(--surface) 88%, transparent)",
borderColor: "var(--border-color)",
boxShadow: "var(--shadow-lg)",
}}
>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
<section>
<h2 className="text-sm font-semibold tracking-wide uppercase" style={{ color: "var(--foreground)" }}>
Navigation
</h2>
<nav className="mt-3 flex flex-col gap-2 text-sm" aria-label="Navigation du site">
<Link href="/" className="hover:underline" style={{ color: "var(--muted)" }}>Accueil</Link>
<a href="https://arthurp.fr/projets" target="_blank" rel="noreferrer" className="hover:underline" style={{ color: "var(--muted)" }}>Projets</a>
<a href="https://contact.arthurp.fr" target="_blank" rel="noreferrer" className="hover:underline" style={{ color: "var(--muted)" }}>Contact</a>
</nav>
</section>
<section>
<h2 className="text-sm font-semibold tracking-wide uppercase" style={{ color: "var(--foreground)" }}>
Liens
</h2>
<div className="mt-3 flex flex-col gap-2 text-sm">
<a href="https://arthurp.fr" target="_blank" rel="noreferrer" className="hover:underline" style={{ color: "var(--muted)" }}>
arthurp.fr
</a>
<a href="https://github.com/arthur-pbty" target="_blank" rel="noreferrer" className="hover:underline" style={{ color: "var(--muted)" }}>
GitHub
</a>
</div>
</section>
<section>
<h2 className="text-sm font-semibold tracking-wide uppercase" style={{ color: "var(--foreground)" }}>
Légal
</h2>
<div className="mt-3 flex flex-col gap-2 text-sm">
<Link href="/mentions-legales" className="hover:underline" style={{ color: "var(--muted)" }}>
Mentions légales
</Link>
<Link href="/politique-de-confidentialite" className="hover:underline" style={{ color: "var(--muted)" }}>
Politique de confidentialité
</Link>
</div>
</section>
<section>
<h2 className="text-sm font-semibold tracking-wide uppercase" style={{ color: "var(--foreground)" }}>
Contact
</h2>
<div className="mt-3 flex flex-col gap-2 text-sm" style={{ color: "var(--muted)" }}>
<a href="https://contact.arthurp.fr" target="_blank" rel="noreferrer" className="hover:underline">
contact.arthurp.fr
</a>
<a href="mailto:contact@arthurp.fr" className="hover:underline">
contact@arthurp.fr
</a>
<p className="text-xs opacity-80">Fait avec et auto-hébergé sur Proxmox.</p>
</div>
</section>
</div>
<div className="mt-6 border-t pt-3 text-center text-xs" style={{ color: "var(--muted)", borderColor: "var(--border-color)" }}>
© {new Date().getFullYear()} Arthur P. Tous droits réservés.
</div>
</div> </div>
</footer> </footer>
</div> </div>
+6 -1
View File
@@ -285,6 +285,7 @@ body {
.mode-toggle { .mode-toggle {
position: relative; position: relative;
display: flex; display: flex;
min-width: 210px;
background: var(--surface); background: var(--surface);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 10px; border-radius: 10px;
@@ -292,9 +293,11 @@ body {
} }
.mode-toggle button { .mode-toggle button {
flex: 1;
position: relative; position: relative;
z-index: 1; z-index: 1;
padding: 6px 16px; padding: 6px 16px;
text-align: center;
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 500; font-weight: 500;
color: var(--muted); color: var(--muted);
@@ -311,10 +314,12 @@ body {
.mode-slider { .mode-slider {
position: absolute; position: absolute;
top: 2px; top: 2px;
left: 2px;
bottom: 2px; bottom: 2px;
width: calc(50% - 2px);
border-radius: 8px; border-radius: 8px;
background: var(--primary); background: var(--primary);
transition: left 0.3s ease, width 0.3s ease; transition: transform 0.3s ease;
} }
/* ══════════════════════════════════════════════ /* ══════════════════════════════════════════════
+34
View File
@@ -0,0 +1,34 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Mentions legales",
description: "Mentions legales du site calculatrice.arthurp.fr",
};
export default function MentionsLegalesPage() {
return (
<main className="min-h-screen px-4 py-12 sm:px-8">
<div className="mx-auto w-full max-w-3xl rounded-2xl border p-6 sm:p-8" style={{ background: "var(--surface)", borderColor: "var(--border-color)" }}>
<h1 className="text-2xl font-bold sm:text-3xl">Mentions legales</h1>
<section className="mt-6 space-y-3 text-sm leading-relaxed" style={{ color: "var(--muted)" }}>
<p>
Site: calculatrice.arthurp.fr
</p>
<p>
Proprietaire et editeur: Arthur P.
</p>
<p>
Contact principal: <a href="https://contact.arthurp.fr" className="hover:underline">contact.arthurp.fr</a>
</p>
<p>
Email: <a href="mailto:contact@arthurp.fr" className="hover:underline">contact@arthurp.fr</a>
</p>
<p>
Hebergement: infrastructure auto-hebergee sur Proxmox.
</p>
</section>
</div>
</main>
);
}
+31
View File
@@ -0,0 +1,31 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Politique de confidentialite",
description: "Politique de confidentialite du site calculatrice.arthurp.fr",
};
export default function PolitiqueConfidentialitePage() {
return (
<main className="min-h-screen px-4 py-12 sm:px-8">
<div className="mx-auto w-full max-w-3xl rounded-2xl border p-6 sm:p-8" style={{ background: "var(--surface)", borderColor: "var(--border-color)" }}>
<h1 className="text-2xl font-bold sm:text-3xl">Politique de confidentialite</h1>
<section className="mt-6 space-y-3 text-sm leading-relaxed" style={{ color: "var(--muted)" }}>
<p>
Cette application ne requiert pas de compte et ne collecte pas de donnees personnelles cote serveur pour son fonctionnement courant.
</p>
<p>
L&apos;historique de calcul est stocke localement dans votre navigateur (localStorage) pour ameliorer l&apos;experience utilisateur.
</p>
<p>
Vous pouvez supprimer ces donnees a tout moment depuis le bouton &quot;Tout effacer&quot; dans l&apos;historique ou via les reglages de votre navigateur.
</p>
<p>
Pour toute demande, vous pouvez utiliser <a href="https://contact.arthurp.fr" className="hover:underline">contact.arthurp.fr</a> ou <a href="mailto:contact@arthurp.fr" className="hover:underline">contact@arthurp.fr</a>.
</p>
</section>
</div>
</main>
);
}
+12
View File
@@ -15,5 +15,17 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: "monthly", changeFrequency: "monthly",
priority: 1.0, priority: 1.0,
}, },
{
url: `${baseUrl}/mentions-legales`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.4,
},
{
url: `${baseUrl}/politique-de-confidentialite`,
lastModified: new Date(),
changeFrequency: "yearly",
priority: 0.4,
},
]; ];
} }
+25 -5
View File
@@ -1,11 +1,31 @@
services: services:
web: calculatrice-dev:
build: . profiles: ["dev"]
build:
context: .
target: calculatrice-dev
container_name: calculatrice-dev
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- ./:/app - .:/app
- /app/node_modules - /app/node_modules
- /app/.next
environment: environment:
- NODE_ENV=development NEXT_TELEMETRY_DISABLED: "1"
command: npm run dev NODE_ENV: development
calculatrice-prod:
profiles: ["prod"]
build:
context: .
target: calculatrice-runner
container_name: calculatrice-prod
restart: unless-stopped
ports:
- "3014:3000"
env_file:
- .env
environment:
NEXT_TELEMETRY_DISABLED: "1"
NODE_ENV: production