Skip to content

Commit 9bb90b3

Browse files
committed
ADD README + Contact + some corrections and refactoring
1 parent c9f92d5 commit 9bb90b3

File tree

11 files changed

+348
-36
lines changed

11 files changed

+348
-36
lines changed

README.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,81 @@
11
# NextShape
22

3-
Une application web permettant de calculer les calories, l'IMC d'un utilisateur et de lui donner un suivi de son évolution au cours du temps
3+
NextShape est une application web de suivi de forme physique, qui permet à un utilisateur de :
4+
5+
- Gérer son **compte utilisateur** (inscription avec vérification e-mail, connexion, modification, suppression, réinitialisation du mot de passe).
6+
- Calculer son **IMC**, son **BMR**, son **TDEE** et ses **calories recommandées** en fonction de ses données personnelles.
7+
- Visualiser l'évolution de ses données corporelles dans un **historique**.
8+
9+
L'application est construite avec :
10+
11+
- **Frontend** : Vue 3, TypeScript, PrimeVue, Pinia, Vite
12+
- **Backend** : Django, Django REST Framework, JWT par cookie HttpOnly
13+
- **Base de données** : PostgreSQL
14+
- **CI/CD** : Docker, docker-compose, Render, GitHub Actions
15+
16+
## Fonctionnalités
17+
18+
- Authentification JWT via cookies HttpOnly
19+
- Calcul de l'IMC
20+
- Calcul du métabolisme de base (BMR), du TDEE et des calories recommandées
21+
- Historique dynamique avec tableau et modale responsive
22+
- Mise à jour et suppression des enregistrements
23+
- Envoi de codes par email via SMTP (Brevo)
24+
- Pipeline CI/CD avec test et déploiement Render (dev/prod)
25+
26+
## Installation
27+
28+
### Prérequis
29+
30+
- Docker + Docker Compose + PostgreSQL
31+
- Créer un fichier `.env` à la racine avec les variables suivantes :
32+
33+
```
34+
# Environnement et secrets Django
35+
ENV=local
36+
DJANGO_SECRET_KEY=django-insecure-...
37+
DJANGO_DEBUG=True
38+
DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost
39+
40+
# Secrets de la base de données
41+
DATABASE_NAME=nextshape_db
42+
DATABASE_USER=nextshape_user
43+
DATABASE_PASSWORD=nextshape_pwd
44+
DATABASE_HOST=db #(en lançant avec Docker et localhost si on veut lancer le serveur avec le terminal)
45+
DATABASE_PORT=5432
46+
47+
# Secrets pour l'envoi de mails à partir du serveur Django
48+
#(Exemple : utilisation de Brevo)
49+
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
50+
EMAIL_HOST=smtp.brevo.com
51+
EMAIL_PORT=587
52+
EMAIL_HOST_USER=user@email.com
53+
EMAIL_HOST_PASSWORD=motdepasse
54+
DEFAULT_FROM_EMAIL=NextShape <dev@email.com>
55+
EMAIL_USE_TLS=True
56+
57+
# URL vers l'API qu'utilisera le front pour envoyer les requêtes.
58+
VITE_API_URL=http://localhost:8000/api/
59+
```
60+
61+
- Lancer en local
62+
63+
```
64+
docker-compose up --build
65+
```
66+
67+
- Puis aller sur :
68+
69+
- Interface web : http://localhost
70+
71+
- API : http://localhost:8000/api/
72+
73+
### Base de données de test
74+
75+
Un utilisateur fictif avec des données déjà initialisées peut être un bon moyen de faire une démo très rapidement.
76+
C'est pour cela que durant le ENTRYPOINT, on réalise un **seed** qui injecte ces données fictives :
77+
78+
- Utilisateur :
79+
- Email : jessem@mail.com
80+
- Mot de passe : 123 (Pas du tout sécurisé)
81+
- Les données de l'utilisateur (des calculs de besoins caloriques) trouvés à la page Historique.

UI/src/components/RecapComponent.vue

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { useProgressRecord } from "@/stores/progressRecordStore"
33
import { ACTIVITY_LEVELS, GENDER, GOALS } from "@/assets/js/constants"
44
import { computed } from "vue"
55
import { formatDate } from "@/assets/js/utils"
6+
import Accordion from "primevue/accordion"
7+
import AccordionHeader from "primevue/accordionheader"
8+
import AccordionPanel from "primevue/accordionpanel"
9+
import AccordionContent from "primevue/accordioncontent"
610
711
// Appel des stores
812
const progressRecordStore = useProgressRecord()
@@ -91,15 +95,75 @@ const calories = computed(
9195

9296
<p class="font-mono text-sm">
9397
<strong>Calories recommandées :</strong><br />
94-
<span v-if="progressRecordStore.progressRecord.goal === 'perte'"
95-
>TDEE – 500 (déficit)</span
96-
>
98+
Idéalement, créez un écart calorique
99+
<span class="font-semibold">entre 200 et 300 kcal</span> par rapport
100+
à votre TDEE.
101+
<br />
102+
Un écart trop petit (<200 kcal) pas de résultats visibles. Un
103+
écart trop grand (>400–500 kcal) → résultats rapides mais rarement
104+
durables.
105+
<br /><br />
106+
107+
<span v-if="progressRecordStore.progressRecord.goal === 'perte'">
108+
🔽 <strong>Objectif : Perte de poids</strong> →
109+
<span class="text-blue-600">TDEE – 300 kcal</span> (déficit
110+
modéré)
111+
</span>
112+
97113
<span
98114
v-else-if="progressRecordStore.progressRecord.goal === 'prise'"
99-
>TDEE + 300 (surplus)</span
100115
>
101-
<span v-else>TDEE (maintien)</span>
116+
🔼 <strong>Objectif : Prise de masse</strong> →
117+
<span class="text-green-600">TDEE + 300 kcal</span> (surplus
118+
contrôlé)
119+
</span>
120+
121+
<span v-else>
122+
⚖️ <strong>Objectif : Maintien</strong> →
123+
<span class="text-orange-600">TDEE</span> (équilibre)
124+
</span>
102125
</p>
126+
<Accordion value="-1">
127+
<AccordionPanel value="0">
128+
<AccordionHeader
129+
>effets négatifs si l’écart est mal calibré</AccordionHeader
130+
>
131+
<AccordionContent>
132+
<div class="space-y-4 text-sm">
133+
<div>
134+
<strong>💡 Perte de poids (déficit trop grand)</strong>
135+
<ul class="list-disc ml-6 mt-1 text-gray-700">
136+
<li>
137+
Ralentissement du métabolisme (mode “économie
138+
d’énergie”)
139+
</li>
140+
<li>Perte musculaire si protéines insuffisantes</li>
141+
<li>Baisse d’énergie et performance</li>
142+
<li>
143+
Fringales et compulsions → craquage assuré après 1–2
144+
semaines
145+
</li>
146+
<li>
147+
Effet yo-yo : reprise rapide du poids perdu, voire plus
148+
</li>
149+
</ul>
150+
</div>
151+
<div>
152+
<strong>💡 Prise de masse (surplus trop grand)</strong>
153+
<ul class="list-disc ml-6 mt-1 text-gray-700">
154+
<li>Prise de gras excessive</li>
155+
<li>Problèmes digestifs (ballonnements, inconfort)</li>
156+
<li>Baisse de sensibilité à l’insuline</li>
157+
<li>
158+
“Marathon de bouffe” → tu tiens 1–2 semaines, puis tu
159+
craques, et retour en arrière
160+
</li>
161+
</ul>
162+
</div>
163+
</div>
164+
</AccordionContent>
165+
</AccordionPanel>
166+
</Accordion>
103167
</div>
104168
<div class="col-12 md:col-6 text-sm leading-relaxed">
105169
<p class="mb-3">

UI/src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const app = createApp(App)
2020
app.use(PrimeVue, {
2121
theme: {
2222
preset: Aura,
23+
options: {
24+
darkModeSelector: "",
25+
},
2326
},
2427
})
2528
app.use(router)

UI/src/services/apiService.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,14 @@ export const deleteRecord = async (id: number) => {
165165
throw error
166166
}
167167
}
168+
169+
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
170+
// Contact services
171+
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
172+
export const sendMail = async (payload: Record<string, any>) => {
173+
try {
174+
await api.post(`${API_URL}contact/`, payload)
175+
} catch (error) {
176+
throw error
177+
}
178+
}

UI/src/views/ConnexionView.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,18 @@ const openResetPasswordModal = () => {
6767
>
6868
<h2 class="text-5xl text-center text-primary">Connexion</h2>
6969
<form class="formgrid grid" @submit.prevent="validateAndProceed">
70-
<div class="field col-12">
71-
<label>Adresse Email</label>
70+
<div class="field col-12 md:col-6">
71+
<label for="email">Adresse Email</label>
7272
<InputText
73+
id="email"
7374
class="w-full"
7475
type="email"
7576
v-model="credentials.email"
7677
placeholder="Votre email"
7778
:invalid="invalidFields.email"
7879
/>
7980
</div>
80-
<div class="field col-12">
81+
<div class="field col-12 md:col-6">
8182
<label for="password">Mot de passe</label>
8283
<Password
8384
class="w-full"

UI/src/views/ContactView.vue

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,106 @@
1-
<script setup lang="ts"></script>
2-
<template>Contact</template>
1+
<script setup lang="ts">
2+
import { ref } from "vue"
3+
import { useToast } from "primevue/usetoast"
4+
import InputText from "primevue/inputtext"
5+
import Textarea from "primevue/textarea"
6+
import Button from "primevue/button"
7+
import Card from "primevue/card"
8+
import { sendMail } from "@/services/apiService"
9+
import { showToast } from "@/assets/js/utils"
10+
11+
const name = ref("")
12+
const email = ref("")
13+
const message = ref("")
14+
15+
const toast = useToast()
16+
17+
const sendMessage = async () => {
18+
if (!name.value || !email.value || !message.value) {
19+
showToast(
20+
toast,
21+
"error",
22+
"Champs requis",
23+
"Veuillez remplir tous les champs."
24+
)
25+
return
26+
}
27+
try {
28+
await sendMail({
29+
name: name.value,
30+
email: email.value,
31+
message: message.value,
32+
})
33+
showToast(
34+
toast,
35+
"success",
36+
"Message envoyé",
37+
"Nous vous répondrons bientôt !"
38+
)
39+
name.value = ""
40+
email.value = ""
41+
message.value = ""
42+
} catch (error: any) {
43+
showToast(
44+
toast,
45+
"error",
46+
"Erreur",
47+
error?.response?.data?.errors?.non_field_errors?.[0] ||
48+
error?.response?.data?.message ||
49+
"Échec de l'envoi de votre message." + error
50+
)
51+
}
52+
}
53+
</script>
54+
55+
<template>
56+
<Card class="w-full max-w-lg shadow-lg">
57+
<template #title>
58+
<h2 class="text-2xl font-semibold text-center text-gray-800">
59+
Contactez-nous
60+
</h2>
61+
</template>
62+
63+
<template #content>
64+
<form class="formgrid grid gap-2" @submit.prevent="sendMessage">
65+
<div class="field col-8 md:col-4">
66+
<label for="name">Nom</label>
67+
<InputText id="name" v-model="name" class="w-full" />
68+
</div>
69+
<div class="field col-8 md:col-4">
70+
<label for="email">Email</label>
71+
<InputText id="email" v-model="email" class="w-full" />
72+
</div>
73+
<div class="field col-12">
74+
<label for="message">Message</label>
75+
<Textarea
76+
id="message"
77+
v-model="message"
78+
rows="5"
79+
class="w-full"
80+
autoResize
81+
maxlength="1000"
82+
/>
83+
<span
84+
class="text-sm text-gray-500"
85+
:class="{
86+
'text-gray-500': message.length < 1000,
87+
'text-red-500': message.length >= 1000,
88+
}"
89+
>
90+
{{ message.length }}/1000
91+
</span>
92+
</div>
93+
<div class="field col-4 md:col-1">
94+
<Button
95+
label="Envoyer"
96+
icon="pi pi-send"
97+
class="w-full"
98+
type="submit"
99+
/>
100+
</div>
101+
</form>
102+
</template>
103+
</Card>
104+
</template>
105+
106+
<style scoped></style>

UI/src/views/InscriptionView.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,10 @@ const resetForm = () => {
225225
/>
226226
</div>
227227
<div class="field col-12 md:col-6">
228-
<label>Adresse Email *</label>
228+
<label for="email">Adresse Email *</label>
229229
<InputText
230230
class="w-full"
231+
id="email"
231232
type="email"
232233
v-model="formData.email"
233234
placeholder="Votre email"

WS/api/serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,9 @@ def _get_age(self, birth_date):
335335
- birth_date.year
336336
- ((today.month, today.day) < (birth_date.month, birth_date.day))
337337
)
338+
339+
340+
class ContactFormSerializer(serializers.Serializer):
341+
name = serializers.CharField(max_length=100)
342+
email = serializers.EmailField()
343+
message = serializers.CharField(max_length=1000)

WS/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .views import (
55
CaloriesRecordView,
66
CheckAuthenticationView,
7+
ContactView,
78
DeleteAccountView,
89
LoginView,
910
LogoutView,
@@ -52,4 +53,9 @@
5253
ProgressRecordsView.as_view(),
5354
name="progress-record-detail",
5455
),
56+
path(
57+
"contact/",
58+
ContactView.as_view(),
59+
name="contact",
60+
),
5561
]

0 commit comments

Comments
 (0)