Correction invitation

This commit is contained in:
ocroguennec committed 2026-09-19 19:28:59 +02:00
1 parent a58a8460f7
commit 14cf4e5a19
2 files changed
+21 -10

No files matched your search

+8 -1
View File
@@ -84,7 +84,14 @@ router.post('/', async (req, res, next) => {
// Envoyer l'email // Envoyer l'email
const cfg = getSmtpConfig(); const cfg = getSmtpConfig();
const appUrl = cfg.appUrl || ''; // Retire le / final avant de concaténer (19/09/26 — bug corrigé : app_url est
// habituellement saisi avec un slash final par les admins, ex. "https://mon-domaine/",
// et sans ce trim le lien devenait "https://mon-domaine//invitation/<token>". Ce double
// slash ne matche pas la route React Router "/invitation/:token", qui tombe alors sur le
// catch-all protégé de App.jsx et redirige silencieusement l'utilisateur invité vers
// /login au lieu de la page d'inscription. Même correctif déjà appliqué partout ailleurs
// (auth.js : vérification email, reset de mot de passe).
const appUrl = cfg.appUrl?.replace(/\/$/, '') || '';
const inviteUrl = `${appUrl}/invitation/${token}`; const inviteUrl = `${appUrl}/invitation/${token}`;
const roleLabel = role === 'admin' ? 'Administrateur' : 'Utilisateur'; const roleLabel = role === 'admin' ? 'Administrateur' : 'Utilisateur';
+13 -9
View File
@@ -8,7 +8,7 @@ import ConfirmModal from '../components/ConfirmModal.jsx';
import EmptyState from '../components/EmptyState.jsx'; import EmptyState from '../components/EmptyState.jsx';
import PageIcon from '../components/PageIcon.jsx'; import PageIcon from '../components/PageIcon.jsx';
import { useAsyncAction } from '../hooks/useAsyncAction.js'; import { useAsyncAction } from '../hooks/useAsyncAction.js';
import { fmtEUR, fmtDate } from '../utils/format.js'; import { fmtEUR, fmtDate, memberLabel } from '../utils/format.js';
import { import {
sortMouvements, computeSoldeIndicatifMap, signedMontant, MOUV_TYPE_LABELS, sortMouvements, computeSoldeIndicatifMap, signedMontant, MOUV_TYPE_LABELS,
ExportDropdown, mouvToCSV, mouvToXLS, mouvToJSON, timestampSuffix, dlBlob, ExportDropdown, mouvToCSV, mouvToXLS, mouvToJSON, timestampSuffix, dlBlob,
@@ -432,19 +432,20 @@ export default function PlateformesPe() {
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Deal</th><th>Souscrit le</th><th className="num">Montant investi</th> <th>Deal</th>{multiDetenteur && <th>Détenteur</th>}<th>Souscrit le</th><th className="num">Montant investi</th>
<th className="num">Dernière valorisation</th><th className="num">TVPI</th> <th className="num">Dernière valorisation</th><th className="num">TVPI</th>
<th>Statut</th><th>Détention</th> <th>Statut</th><th>Détention</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{platDeals.length === 0 && ( {platDeals.length === 0 && (
<tr><td colSpan={7} className="text-muted" style={{ textAlign: 'center' }}>Aucun deal sur cette plateforme</td></tr> <tr><td colSpan={multiDetenteur ? 8 : 7} className="text-muted" style={{ textAlign: 'center' }}>Aucun deal sur cette plateforme</td></tr>
)} )}
{platDeals.map(d => ( {platDeals.map(d => (
<tr key={d.id} className="dr-row" style={{ cursor: 'pointer' }} <tr key={d.id} className="dr-row" style={{ cursor: 'pointer' }}
onClick={() => navigate(`/investissements/${d.id}`)}> onClick={() => navigate(`/investissements/${d.id}`)}>
<td>{d.nom_deal}</td> <td>{d.nom_deal}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{d.investisseur_nom || '—'}</td>}
<td>{fmtDate(d.date_souscription)}</td> <td>{fmtDate(d.date_souscription)}</td>
<td className="num">{fmtEUR(d.montant_investi)}</td> <td className="num">{fmtEUR(d.montant_investi)}</td>
<td className="num">{d.derniere_valorisation != null ? fmtEUR(d.derniere_valorisation) : '—'}</td> <td className="num">{d.derniere_valorisation != null ? fmtEUR(d.derniere_valorisation) : '—'}</td>
@@ -468,16 +469,17 @@ export default function PlateformesPe() {
</p> </p>
<table> <table>
<thead> <thead>
<tr><th>Date</th><th>Deal</th><th>Nature</th><th className="num">Montant net perçu</th></tr> <tr><th>Date</th><th>Deal</th>{multiDetenteur && <th>Détenteur</th>}<th>Nature</th><th className="num">Montant net perçu</th></tr>
</thead> </thead>
<tbody> <tbody>
{platRembs.length === 0 && ( {platRembs.length === 0 && (
<tr><td colSpan={4} className="text-muted" style={{ textAlign: 'center' }}>Aucune distribution enregistrée</td></tr> <tr><td colSpan={multiDetenteur ? 5 : 4} className="text-muted" style={{ textAlign: 'center' }}>Aucune distribution enregistrée</td></tr>
)} )}
{[...platRembs].sort((a, b) => b.date_remb.localeCompare(a.date_remb) || b.id - a.id).map(r => ( {[...platRembs].sort((a, b) => b.date_remb.localeCompare(a.date_remb) || b.id - a.id).map(r => (
<tr key={r.id}> <tr key={r.id}>
<td>{fmtDate(r.date_remb)}</td> <td>{fmtDate(r.date_remb)}</td>
<td>{deals.find(d => d.id === r.investissement_pe_id)?.nom_deal || '—'}</td> <td>{deals.find(d => d.id === r.investissement_pe_id)?.nom_deal || '—'}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.investisseur_nom || '—'}</td>}
<td className="text-muted">{r.distribution_nature === 'interet_non_deploye' ? 'Intérêt sur capital non déployé' : 'Restitution de capital'}</td> <td className="text-muted">{r.distribution_nature === 'interet_non_deploye' ? 'Intérêt sur capital non déployé' : 'Restitution de capital'}</td>
<td className="num">{fmtEUR(r.net_recu)}</td> <td className="num">{fmtEUR(r.net_recu)}</td>
</tr> </tr>
@@ -496,16 +498,17 @@ export default function PlateformesPe() {
</div> </div>
<table> <table>
<thead> <thead>
<tr><th>Date</th><th>Type</th><th className="num">Montant</th><th>Libellé</th><th style={{ width: 40 }}></th></tr> <tr><th>Date</th><th>Type</th>{multiDetenteur && <th>Détenteur</th>}<th className="num">Montant</th><th>Libellé</th><th style={{ width: 40 }}></th></tr>
</thead> </thead>
<tbody> <tbody>
{platDepots.length === 0 && ( {platDepots.length === 0 && (
<tr><td colSpan={5} className="text-muted" style={{ textAlign: 'center' }}>Aucun mouvement</td></tr> <tr><td colSpan={multiDetenteur ? 6 : 5} className="text-muted" style={{ textAlign: 'center' }}>Aucun mouvement</td></tr>
)} )}
{[...platDepots].sort((a, b) => b.date_operation.localeCompare(a.date_operation) || b.id - a.id).map(r => ( {[...platDepots].sort((a, b) => b.date_operation.localeCompare(a.date_operation) || b.id - a.id).map(r => (
<tr key={r.id} className="dr-row" style={{ cursor: 'pointer' }} onClick={() => openDrEdit(r)}> <tr key={r.id} className="dr-row" style={{ cursor: 'pointer' }} onClick={() => openDrEdit(r)}>
<td>{fmtDate(r.date_operation)}</td> <td>{fmtDate(r.date_operation)}</td>
<td>{r.type === 'depot' ? 'Dépôt' : 'Retrait'}</td> <td>{r.type === 'depot' ? 'Dépôt' : 'Retrait'}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{(() => { const inv = investisseurs.find(i => i.id === r.investisseur_id); return inv ? memberLabel(inv) : '—'; })()}</td>}
<td className="num">{fmtEUR(r.montant)}</td> <td className="num">{fmtEUR(r.montant)}</td>
<td className="text-muted">{r.libelle || '—'}</td> <td className="text-muted">{r.libelle || '—'}</td>
<td onClick={e => e.stopPropagation()}> <td onClick={e => e.stopPropagation()}>
@@ -536,11 +539,11 @@ export default function PlateformesPe() {
</p> </p>
<table> <table>
<thead> <thead>
<tr><th>Date</th><th>Type</th><th>Libellé</th><th className="num">Montant</th><th className="num">Solde indicatif</th></tr> <tr><th>Date</th><th>Type</th>{multiDetenteur && <th>Détenteur</th>}<th>Libellé</th><th className="num">Montant</th><th className="num">Solde indicatif</th></tr>
</thead> </thead>
<tbody> <tbody>
{walletSorted.length === 0 && ( {walletSorted.length === 0 && (
<tr><td colSpan={5} className="text-muted" style={{ textAlign: 'center' }}>Aucun mouvement</td></tr> <tr><td colSpan={multiDetenteur ? 6 : 5} className="text-muted" style={{ textAlign: 'center' }}>Aucun mouvement</td></tr>
)} )}
{walletSorted.map(r => { {walletSorted.map(r => {
// signedMontant() (MouvementsShared.jsx) reste la seule source de vérité pour ce // signedMontant() (MouvementsShared.jsx) reste la seule source de vérité pour ce
@@ -551,6 +554,7 @@ export default function PlateformesPe() {
<tr key={r.id}> <tr key={r.id}>
<td>{fmtDate(r.date_operation)}</td> <td>{fmtDate(r.date_operation)}</td>
<td>{MOUV_TYPE_LABELS[r.type] || r.type}</td> <td>{MOUV_TYPE_LABELS[r.type] || r.type}</td>
{multiDetenteur && <td className="text-muted" style={{ fontSize: 'var(--fs-sm)' }}>{r.investisseur_nom || '—'}</td>}
<td className="text-muted">{r.libelle || '—'}</td> <td className="text-muted">{r.libelle || '—'}</td>
<td className="num" style={{ color: signed < 0 ? 'var(--danger, #dc2626)' : signed > 0 ? 'var(--success, #16a34a)' : undefined }}> <td className="num" style={{ color: signed < 0 ? 'var(--danger, #dc2626)' : signed > 0 ? 'var(--success, #16a34a)' : undefined }}>
{signed > 0 ? '+' : ''}{fmtEUR(signed)} {signed > 0 ? '+' : ''}{fmtEUR(signed)}