From c79d6350b018fa0bc50f55be67f50da890dd56f7 Mon Sep 17 00:00:00 2001 From: kydoCode <157696416+kydoCode@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:29:19 +0100 Subject: [PATCH 1/5] fix: ajout workflows Neon branching et cleanup sur le repo front --- .github/workflows/neon-branching.yml | 67 ++++++++++++++++++++++++++++ .github/workflows/neon-cleanup.yml | 48 ++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 .github/workflows/neon-branching.yml create mode 100644 .github/workflows/neon-cleanup.yml diff --git a/.github/workflows/neon-branching.yml b/.github/workflows/neon-branching.yml new file mode 100644 index 0000000..3f26530 --- /dev/null +++ b/.github/workflows/neon-branching.yml @@ -0,0 +1,67 @@ +name: Neon Branch on PR + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + create-db-branch: + runs-on: ubuntu-latest + + steps: + - name: Create Neon Branch + id: create-branch + run: | + echo "🚀 Création branche DB pour PR #${{ github.event.number }}" + + RESPONSE=$(curl -s -X POST \ + "https://console.neon.tech/api/v2/projects/${{ secrets.NEON_PROJECT_ID }}/branches" \ + -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}" \ + -H "Content-Type: application/json" \ + -d '{ + "branch": { + "name": "pr-${{ github.event.number }}", + "parent_id": "main" + } + }') + + echo "$RESPONSE" | jq '.' + + DB_URL=$(echo $RESPONSE | jq -r '.connection_uris[0].connection_uri') + + if [ "$DB_URL" = "null" ] || [ -z "$DB_URL" ]; then + echo "❌ Erreur création branche" + exit 1 + fi + + echo "NEW_DB_URL=$DB_URL" >> $GITHUB_ENV + echo "✅ Branche créée: pr-${{ github.event.number }}" + + - name: Comment PR with DB URL + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + ## 🗄️ Base de données éphémère créée ! + + **Branche Neon** : `pr-${{ github.event.number }}` + + **Connection String** : + ``` + ${{ env.NEW_DB_URL }} + ``` + + ### 🧪 Pour tester localement : + + 1. Copier l'URL ci-dessus + 2. Dans ton `.env` local : + ```bash + DATABASE_URL="" + ``` + 3. Lancer ton app : + ```bash + npm run dev + ``` + + ⚠️ Cette branche sera **automatiquement supprimée** à la fermeture de la PR. + + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/neon-cleanup.yml b/.github/workflows/neon-cleanup.yml new file mode 100644 index 0000000..9837fe3 --- /dev/null +++ b/.github/workflows/neon-cleanup.yml @@ -0,0 +1,48 @@ +name: Neon Cleanup on PR Close + +on: + pull_request: + types: [closed] + +jobs: + delete-db-branch: + runs-on: ubuntu-latest + + steps: + - name: Delete Neon Branch + run: | + BRANCH_NAME="pr-${{ github.event.number }}" + + echo "🗑️ Suppression branche DB: $BRANCH_NAME" + + # Récupérer liste des branches + BRANCHES=$(curl -s \ + "https://console.neon.tech/api/v2/projects/${{ secrets.NEON_PROJECT_ID }}/branches" \ + -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}") + + # Trouver l'ID de la branche + BRANCH_ID=$(echo $BRANCHES | jq -r ".branches[] | select(.name==\"$BRANCH_NAME\") | .id") + + if [ -z "$BRANCH_ID" ] || [ "$BRANCH_ID" = "null" ]; then + echo "⚠️ Branche $BRANCH_NAME introuvable (déjà supprimée ?)" + exit 0 + fi + + # Supprimer la branche + DELETE_RESPONSE=$(curl -s -X DELETE \ + "https://console.neon.tech/api/v2/projects/${{ secrets.NEON_PROJECT_ID }}/branches/$BRANCH_ID" \ + -H "Authorization: Bearer ${{ secrets.NEON_API_KEY }}") + + echo "✅ Branche $BRANCH_NAME supprimée" + + - name: Comment PR + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + ## 🗑️ Branche DB nettoyée + + La branche Neon `pr-${{ github.event.number }}` a été supprimée. + + Quota disponible restauré ✅ + + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 511d7665e58c59d8b3d610df5b3508c57eb5d378 Mon Sep 17 00:00:00 2001 From: kydoCode <157696416+kydoCode@users.noreply.github.com> Date: Wed, 25 Feb 2026 13:39:56 +0100 Subject: [PATCH 2/5] feat: validation Zod frontend sur formulaires login, register, profil et user stories --- package-lock.json | 2 +- package.json | 1 + src/pages/Dashboard.jsx | 24 ++++++++++++++++++++++-- src/pages/Login.jsx | 23 +++++++++++++++++------ src/pages/Profile.jsx | 16 +++++----------- src/pages/Register.jsx | 24 ++++++++++++++++++------ src/schemas/auth.schema.js | 13 +++++++++++++ src/schemas/profile.schema.js | 10 ++++++++++ src/schemas/userstory.schema.js | 9 +++++++++ 9 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 src/schemas/auth.schema.js create mode 100644 src/schemas/profile.schema.js create mode 100644 src/schemas/userstory.schema.js diff --git a/package-lock.json b/package-lock.json index 2bba6ff..e8b0863 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react-dom": "^19.2.0", "react-router-dom": "^7.13.0", "sonner": "^2.0.7", + "zod": "^4.3.6", "zustand": "^5.0.11" }, "devDependencies": { @@ -3905,7 +3906,6 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 3e3de10..566807e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react-dom": "^19.2.0", "react-router-dom": "^7.13.0", "sonner": "^2.0.7", + "zod": "^4.3.6", "zustand": "^5.0.11" }, "devDependencies": { diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index a597731..fbaaf94 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { useNavigate } from 'react-router-dom'; +import { userStorySchema } from '../schemas/userstory.schema'; import { DndContext, DragOverlay, @@ -29,7 +30,8 @@ const COLUMNS = [ { id: 'DONE', label: 'Done', color: 'text-green-400' }, ]; -const PRIORITY_COLORS = { +const PRIORITY_ORDER = { High: 0, Medium: 1, Low: 2 }; + High: 'text-red-400 bg-red-500/10', Medium: 'text-yellow-400 bg-yellow-500/10', Low: 'text-green-400 bg-green-500/10', @@ -99,6 +101,7 @@ export default function Dashboard() { const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [formData, setFormData] = useState(EMPTY_FORM); + const [formErrors, setFormErrors] = useState({}); const [editId, setEditId] = useState(null); const [activeId, setActiveId] = useState(null); @@ -133,10 +136,21 @@ export default function Dashboard() { setShowModal(false); setEditId(null); setFormData(EMPTY_FORM); + setFormErrors({}); }; const handleSubmit = async (e) => { e.preventDefault(); + setFormErrors({}); + + const result = userStorySchema.safeParse(formData); + if (!result.success) { + const errs = {}; + result.error.errors.forEach(err => { errs[err.path[0]] = err.message; }); + setFormErrors(errs); + return; + } + const payload = { title: `En tant que ${formData.asA}, je veux ${formData.iWant}`, description: `Afin de ${formData.soThat}`, @@ -244,7 +258,10 @@ export default function Dashboard() { s.status === col.id).sort((a, b) => a.position - b.position)} + stories={stories.filter(s => s.status === col.id).sort((a, b) => { + const pd = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]; + return pd !== 0 ? pd : a.id - b.id; + })} onEdit={handleEdit} onDelete={handleDelete} activeId={activeId} @@ -275,14 +292,17 @@ export default function Dashboard() {
setFormData({...formData, asA: e.target.value})} className="glass-input w-full" placeholder="utilisateur, admin..." required /> + {formErrors.asA &&

{formErrors.asA}

}