fix(frontend): Step3 - Amélioration genre + ajout fichiers Docker
- 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
This commit is contained in:
parent
aee061f9bd
commit
9a6590c31b
42
backend/Dockerfile
Normal file
42
backend/Dockerfile
Normal file
@ -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"]
|
||||||
100
docker-compose.yml
Normal file
100
docker-compose.yml
Normal file
@ -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
|
||||||
16
frontend/Dockerfile
Normal file
16
frontend/Dockerfile
Normal file
@ -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;"]
|
||||||
@ -1 +1 @@
|
|||||||
flutter.sdk=C:\\Users\\marti\\dev\\flutter
|
flutter.sdk=/home/deploy/snap/flutter/common/flutter
|
||||||
@ -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(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -416,7 +416,7 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> {
|
|||||||
Switch(value: widget.childData.isUnbornChild, onChanged: widget.onToggleIsUnborn, activeColor: Theme.of(context).primaryColor),
|
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(
|
CustomAppTextField(
|
||||||
controller: _firstNameController,
|
controller: _firstNameController,
|
||||||
labelText: 'Prénom',
|
labelText: 'Prénom',
|
||||||
@ -432,33 +432,28 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
fieldHeight: 55.0 * 1.1, // 60.5
|
fieldHeight: 55.0 * 1.1, // 60.5
|
||||||
),
|
),
|
||||||
const SizedBox(height: 9.0 * 1.1), // 9.9
|
const SizedBox(height: 6.0 * 1.1),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('Genre', style: GoogleFonts.merienda(fontSize: 16 * 1.1, fontWeight: FontWeight.w600)),
|
Radio<String>(
|
||||||
Row(
|
value: 'F',
|
||||||
children: [
|
groupValue: widget.childData.gender,
|
||||||
Radio<String>(
|
onChanged: (value) => widget.onGenderChanged(value!),
|
||||||
value: 'H',
|
activeColor: Theme.of(context).primaryColor,
|
||||||
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<String>(
|
|
||||||
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)),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
Text('Fille', style: GoogleFonts.merienda(fontSize: 16 * 1.1)),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Radio<String>(
|
||||||
|
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(
|
CustomAppTextField(
|
||||||
controller: _dobController,
|
controller: _dobController,
|
||||||
labelText: widget.childData.isUnbornChild ? 'Date prévisionnelle de naissance' : 'Date de naissance',
|
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,
|
suffixIcon: Icons.calendar_today,
|
||||||
fieldHeight: 55.0 * 1.1, // 60.5
|
fieldHeight: 55.0 * 1.1, // 60.5
|
||||||
),
|
),
|
||||||
const SizedBox(height: 11.0 * 1.1), // 12.1
|
const SizedBox(height: 8.0 * 1.1),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -476,14 +471,14 @@ class _ChildCardWidgetState extends State<_ChildCardWidget> {
|
|||||||
label: 'Consentement photo',
|
label: 'Consentement photo',
|
||||||
value: widget.childData.photoConsent,
|
value: widget.childData.photoConsent,
|
||||||
onChanged: widget.onTogglePhotoConsent,
|
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(
|
AppCustomCheckbox(
|
||||||
label: 'Naissance multiple',
|
label: 'Naissance multiple',
|
||||||
value: widget.childData.multipleBirth,
|
value: widget.childData.multipleBirth,
|
||||||
onChanged: widget.onToggleMultipleBirth,
|
onChanged: widget.onToggleMultipleBirth,
|
||||||
checkboxSize: 22.0 * 1.1, // 24.2
|
checkboxSize: 22.0 * 1.1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
13
frontend/nginx.conf
Normal file
13
frontend/nginx.conf
Normal file
@ -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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user