From 9a6590c31bfbfee0845b3d858c9b134647e00233 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Tue, 2 Dec 2025 15:40:46 +0100 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20Step3=20-=20Am=C3=A9lioration?= =?UTF-8?q?=20genre=20+=20ajout=20fichiers=20Docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Suppression label 'Genre', boutons radio centrés (Fille/Garçon) - Réduction espacements pour éviter dépassement - Ajout docker-compose.yml et Dockerfiles depuis master Ticket #38 --- backend/Dockerfile | 42 ++++++++ docker-compose.yml | 100 ++++++++++++++++++ frontend/Dockerfile | 16 +++ frontend/android/local.properties | 2 +- .../auth/parent_register_step3_screen.dart | 51 ++++----- frontend/nginx.conf | 13 +++ 6 files changed, 195 insertions(+), 29 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 docker-compose.yml create mode 100644 frontend/Dockerfile create mode 100644 frontend/nginx.conf diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..b1d64fd --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,42 @@ +FROM node:22-alpine AS builder + +WORKDIR /app + +# Copier les fichiers de configuration +COPY package*.json ./ +COPY tsconfig*.json ./ +COPY nest-cli.json ./ + +# Installer TOUTES les dépendances (dev + prod pour le build) +RUN npm install && npm cache clean --force + +# Copier le code source +COPY src ./src + +# Builder l'application +RUN npm run build + +# Stage production +FROM node:22-alpine AS production + +WORKDIR /app + +# Installer seulement les dépendances de production +COPY package*.json ./ +RUN npm install --only=production && npm cache clean --force + +# Copier le build depuis le stage builder +COPY --from=builder /app/dist ./dist + +# Créer un utilisateur non-root +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nestjs -u 1001 + +# Créer le dossier uploads et donner les permissions +RUN mkdir -p /app/uploads/photos && chown -R nestjs:nodejs /app/uploads + +USER nestjs + +EXPOSE 3000 + +CMD ["node", "dist/main"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1e97b62 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,100 @@ +services: + # Base de données PostgreSQL + database: + image: postgres:17 + container_name: ptitspas-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - ./database/BDD.sql:/docker-entrypoint-initdb.d/01_init.sql + - postgres_data:/var/lib/postgresql/data + networks: + - ptitspas_network + + # Interface d'administration DB + pgadmin: + image: dpage/pgadmin4 + container_name: ptitspas-pgadmin + restart: unless-stopped + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} + SCRIPT_NAME: /pgadmin + depends_on: + - database + labels: + - "traefik.enable=true" + - "traefik.http.routers.ptitspas-pgadmin.rule=Host(\"app.ptits-pas.fr\") && PathPrefix(\"/pgadmin\")" + - "traefik.http.routers.ptitspas-pgadmin.entrypoints=websecure" + - "traefik.http.routers.ptitspas-pgadmin.tls.certresolver=leresolver" + - "traefik.http.routers.ptitspas-pgadmin.priority=30" + - "traefik.http.services.ptitspas-pgadmin.loadbalancer.server.port=80" + networks: + - ptitspas_network + - proxy_network + + # Backend NestJS + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: ptitspas-backend + restart: unless-stopped + environment: + POSTGRES_HOST: ${POSTGRES_HOST} + POSTGRES_PORT: ${POSTGRES_PORT} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + API_PORT: ${API_PORT} + JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET} + JWT_ACCESS_EXPIRES: ${JWT_ACCESS_EXPIRES} + JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET} + JWT_REFRESH_EXPIRES: ${JWT_REFRESH_EXPIRES} + NODE_ENV: ${NODE_ENV} + depends_on: + - database + labels: + - "traefik.enable=true" + - "traefik.http.routers.ptitspas-api.rule=Host(\"app.ptits-pas.fr\") && PathPrefix(\"/api\")" + - "traefik.http.routers.ptitspas-api.entrypoints=websecure" + - "traefik.http.routers.ptitspas-api.tls.certresolver=leresolver" + - "traefik.http.routers.ptitspas-api.priority=20" + - "traefik.http.services.ptitspas-api.loadbalancer.server.port=3000" + networks: + - ptitspas_network + - proxy_network + + # Frontend Flutter + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: ptitspas-frontend + restart: unless-stopped + environment: + API_URL: ${API_URL} + depends_on: + - backend + labels: + - "traefik.enable=true" + - "traefik.http.routers.ptitspas-front.rule=Host(\"app.ptits-pas.fr\")" + - "traefik.http.routers.ptitspas-front.entrypoints=websecure" + - "traefik.http.routers.ptitspas-front.tls.certresolver=leresolver" + - "traefik.http.routers.ptitspas-front.priority=10" + - "traefik.http.services.ptitspas-front.loadbalancer.server.port=80" + networks: + - ptitspas_network + - proxy_network + +volumes: + postgres_data: + +networks: + ptitspas_network: + driver: bridge + proxy_network: + external: true diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..7aaab71 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,16 @@ +# Stage builder +FROM ghcr.io/cirruslabs/flutter:3.19.0 AS builder +WORKDIR /app +COPY pubspec.* ./ +RUN flutter pub get +COPY . . +RUN flutter build web --release + +# Stage production +FROM nginx:alpine +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /app/build/web /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/android/local.properties b/frontend/android/local.properties index 189af7a..faddb1f 100644 --- a/frontend/android/local.properties +++ b/frontend/android/local.properties @@ -1 +1 @@ -flutter.sdk=C:\\Users\\marti\\dev\\flutter \ No newline at end of file +flutter.sdk=/home/deploy/snap/flutter/common/flutter \ No newline at end of file diff --git a/frontend/lib/screens/auth/parent_register_step3_screen.dart b/frontend/lib/screens/auth/parent_register_step3_screen.dart index 264bb65..1e70f2a 100644 --- a/frontend/lib/screens/auth/parent_register_step3_screen.dart +++ b/frontend/lib/screens/auth/parent_register_step3_screen.dart @@ -408,7 +408,7 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> { ), ), ), - const SizedBox(height: 12.0 * 1.1), // Augmenté pour plus d'espace après la photo + const SizedBox(height: 8.0 * 1.1), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -416,7 +416,7 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> { Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor), ], ), - const SizedBox(height: 9.0 * 1.1), // 9.9 + const SizedBox(height: 6.0 * 1.1) CustomAppTextField( controller: _firstNameController, labelText: 'Prénom', @@ -432,33 +432,28 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> { enabled: true, fieldHeight: 55.0 * 1.1, // 60.5 ), - const SizedBox(height: 9.0 * 1.1), // 9.9 + const SizedBox(height: 6.0 * 1.1), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Genre', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)), - Row( - children: [ - Radio( - value: 'H', - groupValue: widget.childData.gender, - onChanged: (value) => widget.onGenderChanged(value!), - activeColor: Theme.of(context).primaryColor, - ), - Text('H', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), - const SizedBox(width: 20), - Radio( - value: 'F', - groupValue: widget.childData.gender, - onChanged: (value) => widget.onGenderChanged(value!), - activeColor: Theme.of(context).primaryColor, - ), - Text('F', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), - ], + Radio( + value: 'F', + groupValue: widget.childData.gender, + onChanged: (value) => widget.onGenderChanged(value!), + activeColor: Theme.of(context).primaryColor, ), + Text('Fille', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), + const SizedBox(width: 20), + Radio( + value: 'H', + groupValue: widget.childData.gender, + onChanged: (value) => widget.onGenderChanged(value!), + activeColor: Theme.of(context).primaryColor, + ), + Text('Garçon', style: GoogleFonts.merienda(fontSize: 16 * 1.1)), ], ), - const SizedBox(height: 9.0 * 1.1), // 9.9 + const SizedBox(height: 6.0 * 1.1) CustomAppTextField( controller: _dobController, labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance', @@ -468,7 +463,7 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> { suffixIcon: Icons.calendar_today, fieldHeight: 55.0 * 1.1, // 60.5 ), - const SizedBox(height: 11.0 * 1.1), // 12.1 + const SizedBox(height: 8.0 * 1.1), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -476,14 +471,14 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> { label: 'Consentement photo', value: widget.childData.photoConsent, onChanged: widget.onTogglePhotoConsent, - checkboxSize: 22.0 * 1.1, // 24.2 + checkboxSize: 22.0 * 1.1, ), - const SizedBox(height: 6.0 * 1.1), // 6.6 + const SizedBox(height: 4.0 * 1.1), AppCustomCheckbox( label: 'Naissance multiple', value: widget.childData.multipleBirth, onChanged: widget.onToggleMultipleBirth, - checkboxSize: 22.0 * 1.1, // 24.2 + checkboxSize: 22.0 * 1.1, ), ], ), diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..29d7e49 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,13 @@ +server { + listen 80; + server_name ynov.ptits-pas.fr; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Gestion des erreurs + error_page 404 /index.html; +}