Ajout page simulateur sans accès
This commit is contained in:
parent
1e9733fd90
commit
0a44dce37a
9 changed files with 2890 additions and 17 deletions
67
FIX_2FA_INPUT_SIZE.md
Normal file
67
FIX_2FA_INPUT_SIZE.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Correction : Encadrés 2FA trop grands sur la page de connexion
|
||||
|
||||
## 🐛 Problème
|
||||
|
||||
Sur la page de connexion (`/signin`), lors de la saisie du code 2FA (authentification à deux facteurs), les encadrés pour les 6 chiffres étaient beaucoup trop grands par rapport aux encadrés utilisés pour le code OTP par email.
|
||||
|
||||
## 🔍 Cause
|
||||
|
||||
Les inputs du 2FA utilisaient la classe CSS `flex-1` qui les rendait extensibles pour remplir tout l'espace disponible, combiné avec `justify-between` au lieu de `justify-center`.
|
||||
|
||||
**Code problématique** :
|
||||
```tsx
|
||||
<div className="flex justify-between gap-1 sm:gap-2">
|
||||
<input
|
||||
className="flex-1 h-12 sm:h-14 text-center text-xl sm:text-2xl font-mono border-2 border-[#6366f1]/40 rounded-xl bg-white/30 text-[#171424] focus:outline-none focus:ring-2 focus:ring-[#6366f1] shadow-md transition"
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
## ✅ Solution
|
||||
|
||||
Alignement du style des inputs 2FA sur celui des inputs OTP en utilisant :
|
||||
- Des largeurs fixes (`w-12 sm:w-14` + style inline `width: 3rem`)
|
||||
- `justify-center` au lieu de `justify-between`
|
||||
- Les mêmes classes de style pour une apparence cohérente
|
||||
|
||||
### Changements apportés
|
||||
|
||||
**Fichier** : `/app/signin/page.tsx`
|
||||
|
||||
#### Avant :
|
||||
```tsx
|
||||
<div className="flex justify-between gap-1 sm:gap-2">
|
||||
<input
|
||||
className="flex-1 h-12 sm:h-14 text-center text-xl sm:text-2xl font-mono border-2 border-[#6366f1]/40 rounded-xl bg-white/30 text-[#171424] focus:outline-none focus:ring-2 focus:ring-[#6366f1] shadow-md transition"
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Après :
|
||||
```tsx
|
||||
<div className="flex justify-center gap-1 sm:gap-2">
|
||||
<input
|
||||
className="w-12 sm:w-14 h-14 sm:h-16 text-center text-2xl sm:text-3xl rounded-xl sm:rounded-2xl bg-white/70 border-2 border-[#6366f1]/40 text-[#171424] placeholder-[#171424]/40 focus:outline-none focus:ring-2 focus:ring-[#6366f1]/40 shadow-lg transition flex-shrink-0"
|
||||
style={{ fontWeight: 700, letterSpacing: "0.1em", width: "3rem", minWidth: "3rem", maxWidth: "3rem" }}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 🎯 Améliorations
|
||||
|
||||
- ✅ Encadrés 2FA de taille fixe (3rem / ~48px)
|
||||
- ✅ Cohérence visuelle entre OTP et 2FA
|
||||
- ✅ Centrés sur la page au lieu d'être étirés
|
||||
- ✅ Même espacement et style que les inputs OTP
|
||||
- ✅ Meilleure lisibilité et UX
|
||||
|
||||
## 📸 Différences visuelles
|
||||
|
||||
### Avant
|
||||
- Inputs 2FA : Largeur flexible (trop large)
|
||||
- Distribution : `justify-between` (étalés sur toute la largeur)
|
||||
|
||||
### Après
|
||||
- Inputs 2FA : Largeur fixe de 3rem chacun
|
||||
- Distribution : `justify-center` (centrés et groupés)
|
||||
- Style identique aux inputs OTP pour cohérence
|
||||
68
FIX_PASSWORD_DECONNEXION.md
Normal file
68
FIX_PASSWORD_DECONNEXION.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Correction : Déconnexion automatique lors de la création d'un mot de passe
|
||||
|
||||
## 🐛 Problème
|
||||
|
||||
Lorsqu'un utilisateur créait ou mettait à jour son mot de passe via la page **Compte > Sécurité**, il était automatiquement déconnecté de son compte.
|
||||
|
||||
## 🔍 Cause
|
||||
|
||||
L'API `/api/auth/password-update` utilisait la méthode `admin.auth.admin.updateUserById()` de Supabase (Admin API) pour modifier le mot de passe.
|
||||
|
||||
**Comportement de Supabase** : Par défaut, pour des raisons de sécurité, l'Admin API **invalide automatiquement toutes les sessions actives** d'un utilisateur lorsqu'on modifie son mot de passe. Ce comportement assume qu'un changement de mot de passe doit forcer une reconnexion.
|
||||
|
||||
## ✅ Solution
|
||||
|
||||
Remplacement de l'Admin API par la méthode `supabase.auth.updateUser()` qui permet de mettre à jour le mot de passe **sans invalider la session active**.
|
||||
|
||||
### Changements apportés
|
||||
|
||||
**Fichier** : `/app/api/auth/password-update/route.ts`
|
||||
|
||||
#### Avant :
|
||||
```typescript
|
||||
// Utilisation de l'Admin API (invalide la session)
|
||||
const admin = createClient(url, serviceKey);
|
||||
const { error: updErr } = await admin.auth.admin.updateUserById(userId, {
|
||||
password: newPassword,
|
||||
user_metadata: {
|
||||
...session.user.user_metadata,
|
||||
hasPassword: true
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Après :
|
||||
```typescript
|
||||
// Utilisation de updateUser() (préserve la session)
|
||||
const { error: updErr } = await supabase.auth.updateUser({
|
||||
password: newPassword,
|
||||
data: {
|
||||
...session.user.user_metadata,
|
||||
hasPassword: true
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🎯 Résultat
|
||||
|
||||
- ✅ L'utilisateur peut créer/modifier son mot de passe sans être déconnecté
|
||||
- ✅ La session reste active après la mise à jour
|
||||
- ✅ L'email de confirmation est toujours envoyé
|
||||
- ✅ Toutes les validations de sécurité du mot de passe sont conservées
|
||||
|
||||
## 📝 Notes techniques
|
||||
|
||||
- **`updateUser()`** : Met à jour les informations de l'utilisateur authentifié via sa session active
|
||||
- **`admin.updateUserById()`** : Met à jour un utilisateur via l'Admin API (invalide toutes les sessions pour sécurité)
|
||||
|
||||
La méthode `updateUser()` est appropriée ici car :
|
||||
1. L'utilisateur est authentifié et modifie son propre mot de passe
|
||||
2. Il n'y a pas de raison de le déconnecter immédiatement
|
||||
3. C'est une action volontaire de l'utilisateur (pas une récupération de mot de passe compromise)
|
||||
|
||||
## 🔒 Sécurité
|
||||
|
||||
Le changement de mot de passe reste sécurisé :
|
||||
- Validation stricte du mot de passe (12+ caractères, majuscules, minuscules, chiffres, caractères spéciaux)
|
||||
- Email de confirmation envoyé
|
||||
- L'utilisateur doit être authentifié pour accéder à cette API
|
||||
118
app/(app)/simulateur/page.tsx
Normal file
118
app/(app)/simulateur/page.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { usePageTitle } from '@/hooks/usePageTitle';
|
||||
import { Calculator } from 'lucide-react';
|
||||
|
||||
export default function SimulateurPage() {
|
||||
usePageTitle("Simulateur de paie");
|
||||
|
||||
return (
|
||||
<div className="max-w-[1600px] mx-auto">
|
||||
<style jsx global>{`
|
||||
.simulateur-iframe {
|
||||
width: 100%;
|
||||
min-height: 1200px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
||||
background: white;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* En-tête */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Calculator className="w-8 h-8 text-indigo-600" />
|
||||
<h1 className="text-3xl font-bold text-slate-900">Simulateur de paie intermittent</h1>
|
||||
</div>
|
||||
<p className="text-slate-600">
|
||||
Calculez le coût de recrutement d'un intermittent du spectacle (CDDU)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Layout 2 colonnes : simulateur à gauche, cards info à droite */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_380px] gap-6">
|
||||
|
||||
{/* Colonne principale : Simulateur en iframe */}
|
||||
<div>
|
||||
<iframe
|
||||
src="/simulateur-embed.html"
|
||||
className="simulateur-iframe"
|
||||
title="Simulateur de paie intermittent"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Colonne droite : Cards d'information modernes */}
|
||||
<aside className="space-y-4 lg:sticky lg:top-24 lg:self-start" aria-label="Aide et explications">
|
||||
|
||||
{/* Card : Mode d'emploi */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-6 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-indigo-100 rounded-lg flex items-center justify-center">
|
||||
<svg className="w-5 h-5 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-slate-900 mb-2">Mode d'emploi</h3>
|
||||
<p className="text-sm text-slate-600 mb-3">
|
||||
Calculez le coût de recrutement d'un intermittent du spectacle en CDDU.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-2 text-sm text-slate-700">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 bg-indigo-50 text-indigo-600 rounded-full flex items-center justify-center text-xs font-semibold mt-0.5">1</span>
|
||||
<span>Choisissez la Convention Collective et le statut</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 bg-indigo-50 text-indigo-600 rounded-full flex items-center justify-center text-xs font-semibold mt-0.5">2</span>
|
||||
<span>Indiquez les cachets/heures et dates travaillées</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 bg-indigo-50 text-indigo-600 rounded-full flex items-center justify-center text-xs font-semibold mt-0.5">3</span>
|
||||
<span>Saisissez le montant (Brut, Net ou Coût employeur)</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 bg-indigo-50 text-indigo-600 rounded-full flex items-center justify-center text-xs font-semibold mt-0.5">4</span>
|
||||
<span>Consultez les résultats avec le détail des cotisations</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-slate-100">
|
||||
<p className="text-xs text-slate-500">
|
||||
<span className="font-medium text-slate-700">Taux 2025</span> • Contrats multi-mois : nous contacter
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card : Disclaimer */}
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl shadow-sm p-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg className="w-5 h-5 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-semibold text-amber-900 mb-2">Limitations & mentions</h3>
|
||||
<p className="text-xs text-amber-800 leading-relaxed">
|
||||
Le simulateur ne prévoit pas les cas particuliers : mineurs de moins de 16 ans,
|
||||
cumul annuel, taxe sur les salaires, taxe d'apprentissage, non-résidents fiscaux,
|
||||
contrats multi-mois.
|
||||
</p>
|
||||
<p className="text-xs text-amber-700 mt-2 italic">
|
||||
Résultats donnés à titre indicatif, sans valeur contractuelle.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { sendPasswordChangedEmail } from "@/lib/emailMigrationHelpers";
|
||||
|
||||
export async function POST(req: Request) {
|
||||
|
|
@ -45,19 +44,11 @@ export async function POST(req: Request) {
|
|||
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||
}
|
||||
|
||||
const url = process.env.SUPABASE_URL!;
|
||||
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
if (!url || !serviceKey) {
|
||||
return NextResponse.json({ error: "Configuration Supabase manquante" }, { status: 500 });
|
||||
}
|
||||
|
||||
const admin = createClient(url, serviceKey);
|
||||
const userId = session.user.id;
|
||||
|
||||
// Mettre à jour le mot de passe via l'Admin API
|
||||
const { error: updErr } = await admin.auth.admin.updateUserById(userId, {
|
||||
// Mettre à jour le mot de passe via updateUser() qui préserve la session active
|
||||
// Note: Cette méthode n'invalide PAS la session actuelle contrairement à l'Admin API
|
||||
const { error: updErr } = await supabase.auth.updateUser({
|
||||
password: newPassword,
|
||||
user_metadata: {
|
||||
data: {
|
||||
...session.user.user_metadata,
|
||||
hasPassword: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ export default function Sidebar({ clientInfo, isStaff = false, mobile = false, o
|
|||
<DisabledMenuItem
|
||||
icon={Calculator}
|
||||
label="Simulateur de paie"
|
||||
tooltipMessage="Le simulateur et les minima seront de retour dans quelques jours."
|
||||
tooltipMessage="Le simulateur de paie est temporairement désactivé."
|
||||
/>
|
||||
</div>
|
||||
{/* Menu Staff */}
|
||||
|
|
|
|||
|
|
@ -410,8 +410,8 @@ export async function sendPasswordChangedEmail(
|
|||
userEmail: toEmail,
|
||||
status: 'Modifié',
|
||||
eventDate: data.eventDate || new Date().toLocaleString('fr-FR'),
|
||||
platform: data.platform || 'Odentas Paie',
|
||||
ctaUrl: `${process.env.NEXT_PUBLIC_BASE_URL || 'https://paie.odentas.fr'}/compte/securite`,
|
||||
platform: data.platform || 'Espace Paie Odentas',
|
||||
ctaUrl: 'https://paie.odentas.fr',
|
||||
};
|
||||
|
||||
await sendUniversalEmailV2({
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ const EMAIL_TEMPLATES_V2: Record<EmailTypeV2, EmailTemplateV2> = {
|
|||
greeting: '{{#if firstName}}Bonjour {{firstName}},{{/if}}',
|
||||
mainMessage: 'Votre mot de passe a été modifié avec succès.',
|
||||
closingMessage: 'Si vous n’êtes pas à l’origine de cette modification, <a href="{{supportUrl}}" style="color:#0B5FFF; text-decoration:none;">contactez le support</a> immédiatement.',
|
||||
ctaText: 'Gérer ma sécurité',
|
||||
ctaText: 'Accès à l\'Espace Paie',
|
||||
footerText: 'Vous recevez cet e-mail pour confirmer une modification de mot de passe.',
|
||||
preheaderText: 'Mot de passe modifié · Vérifiez la sécurité de votre compte',
|
||||
colors: {
|
||||
|
|
|
|||
1374
public/simulateur-embed.html
Normal file
1374
public/simulateur-embed.html
Normal file
File diff suppressed because it is too large
Load diff
1255
simulateur.html
Normal file
1255
simulateur.html
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue