ajout d'une modale pour le changement de mot de passe
This commit is contained in:
parent
b3c9c43142
commit
b100499a15
31
admin.php
31
admin.php
@ -240,31 +240,6 @@ ob_start(); ?>
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<h2>Changer le mot de passe</h2>
|
|
||||||
<form method="POST" class="password-form">
|
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo Cyla::generateCSRFToken(); ?>">
|
|
||||||
<input type="hidden" name="action" value="change_password">
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="current_password">Mot de passe actuel</label>
|
|
||||||
<input type="password" id="current_password" name="current_password" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="new_password">Nouveau mot de passe</label>
|
|
||||||
<input type="password" id="new_password" name="new_password" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="confirm_password">Confirmer le nouveau mot de passe</label>
|
|
||||||
<input type="password" id="confirm_password" name="confirm_password" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn">Changer le mot de passe</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>Fichiers hébergés</h2>
|
<h2>Fichiers hébergés</h2>
|
||||||
<?php if (empty($files)): ?>
|
<?php if (empty($files)): ?>
|
||||||
@ -361,7 +336,11 @@ function copyShareLink(url) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<div id="passwordModalRoot"></div>
|
||||||
|
<script>
|
||||||
|
window.CSRF_TOKEN = "<?php echo Cyla::generateCSRFToken(); ?>";
|
||||||
|
</script>
|
||||||
|
<script src="js/password-modal.js"></script>
|
||||||
<?php
|
<?php
|
||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
require 'layout.php';
|
require 'layout.php';
|
||||||
|
@ -7,7 +7,7 @@ if (!defined('CYLA_CORE')) {
|
|||||||
|
|
||||||
// Site configuration
|
// Site configuration
|
||||||
define('SITE_NAME', 'Cyla');
|
define('SITE_NAME', 'Cyla');
|
||||||
define('SITE_VERSION', '3.0.0');
|
define('SITE_VERSION', '3.0.1');
|
||||||
define('SITE_URL', 'https://concepts.esenjin.xyz/cyla/');
|
define('SITE_URL', 'https://concepts.esenjin.xyz/cyla/');
|
||||||
|
|
||||||
// Files configuration
|
// Files configuration
|
||||||
|
@ -69,15 +69,23 @@ nav {
|
|||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
/* Navigation */
|
||||||
|
.nav-link,
|
||||||
|
button.nav-link {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
padding: var(--spacing-sm) var(--spacing-md);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
|
font: inherit; /* Pour que le bouton utilise la même police */
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover,
|
||||||
|
button.nav-link:hover {
|
||||||
background-color: var(--color-border);
|
background-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,3 +351,39 @@ footer {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Styles pour la modale */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.75);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 9999;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
background-color: var(--color-bg-alt);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 1rem;
|
||||||
|
animation: modalAppear 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modalAppear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95) translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
200
js/password-modal.js
Normal file
200
js/password-modal.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
const ChangePasswordModal = ({ isOpen, onClose, onSubmit, csrfToken }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const data = Object.fromEntries(formData.entries());
|
||||||
|
onSubmit(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return React.createElement('div', {
|
||||||
|
className: 'modal-overlay',
|
||||||
|
onClick: (e) => {
|
||||||
|
if (e.target === e.currentTarget) onClose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'modal-container'
|
||||||
|
}, [
|
||||||
|
React.createElement('div', {
|
||||||
|
key: 'header',
|
||||||
|
className: 'flex justify-between items-center p-4 border-b border-neutral-700'
|
||||||
|
}, [
|
||||||
|
React.createElement('h2', {
|
||||||
|
className: 'text-xl font-semibold text-rose-400'
|
||||||
|
}, 'Changer le mot de passe'),
|
||||||
|
React.createElement('button', {
|
||||||
|
onClick: onClose,
|
||||||
|
className: 'btn btn-secondary'
|
||||||
|
}, '×')
|
||||||
|
]),
|
||||||
|
React.createElement('form', {
|
||||||
|
key: 'form',
|
||||||
|
onSubmit: handleSubmit,
|
||||||
|
className: 'p-4 space-y-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'csrf_token',
|
||||||
|
value: csrfToken
|
||||||
|
}),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'action',
|
||||||
|
value: 'change_password'
|
||||||
|
}),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('label', {
|
||||||
|
className: 'block text-sm text-neutral-400'
|
||||||
|
}, 'Mot de passe actuel'),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'password',
|
||||||
|
name: 'current_password',
|
||||||
|
required: true,
|
||||||
|
className: 'w-full p-2 bg-neutral-800 border border-neutral-700 rounded focus:border-rose-500 focus:outline-none'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('label', {
|
||||||
|
className: 'block text-sm text-neutral-400'
|
||||||
|
}, 'Nouveau mot de passe'),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'password',
|
||||||
|
name: 'new_password',
|
||||||
|
required: true,
|
||||||
|
className: 'w-full p-2 bg-neutral-800 border border-neutral-700 rounded focus:border-rose-500 focus:outline-none'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'space-y-2'
|
||||||
|
}, [
|
||||||
|
React.createElement('label', {
|
||||||
|
className: 'block text-sm text-neutral-400'
|
||||||
|
}, 'Confirmer le nouveau mot de passe'),
|
||||||
|
React.createElement('input', {
|
||||||
|
type: 'password',
|
||||||
|
name: 'confirm_password',
|
||||||
|
required: true,
|
||||||
|
className: 'w-full p-2 bg-neutral-800 border border-neutral-700 rounded focus:border-rose-500 focus:outline-none'
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
React.createElement('div', {
|
||||||
|
className: 'flex justify-end gap-2 pt-4'
|
||||||
|
}, [
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'button',
|
||||||
|
onClick: onClose,
|
||||||
|
className: 'btn btn-secondary'
|
||||||
|
}, 'Annuler'),
|
||||||
|
React.createElement('button', {
|
||||||
|
type: 'submit',
|
||||||
|
className: 'btn'
|
||||||
|
}, 'Changer le mot de passe')
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// App wrapper
|
||||||
|
function PasswordModalApp({ csrfToken }) {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const btn = document.getElementById('changePasswordBtn');
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
setIsOpen(true);
|
||||||
|
document.body.classList.add('modal-open');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer la fermeture avec la touche Echap
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleEscape = (e) => {
|
||||||
|
if (e.key === 'Escape' && isOpen) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleEscape);
|
||||||
|
return () => document.removeEventListener('keydown', handleEscape);
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
// Ajouter la gestion du clic en dehors de la modale
|
||||||
|
const handleOutsideClick = (e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return React.createElement(ChangePasswordModal, {
|
||||||
|
isOpen,
|
||||||
|
onClose: handleClose,
|
||||||
|
onSubmit: handleSubmit,
|
||||||
|
csrfToken: csrfToken,
|
||||||
|
onOverlayClick: handleOutsideClick
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisation
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('passwordModalRoot'));
|
||||||
|
root.render(React.createElement(PasswordModalApp, {
|
||||||
|
csrfToken: window.CSRF_TOKEN
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Modifier la fonction PasswordModalApp pour accepter le token en props
|
||||||
|
function PasswordModalApp({ csrfToken }) {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const btn = document.getElementById('changePasswordBtn');
|
||||||
|
if (btn) {
|
||||||
|
btn.addEventListener('click', () => setIsOpen(true));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = async (formData) => {
|
||||||
|
try {
|
||||||
|
const form = new FormData();
|
||||||
|
Object.entries(formData).forEach(([key, value]) => {
|
||||||
|
form.append(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('admin.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: form
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert('Mot de passe modifié avec succès');
|
||||||
|
setIsOpen(false);
|
||||||
|
} else {
|
||||||
|
alert('Erreur lors du changement de mot de passe');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Erreur lors du changement de mot de passe');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return React.createElement(ChangePasswordModal, {
|
||||||
|
isOpen,
|
||||||
|
onClose: () => setIsOpen(false),
|
||||||
|
onSubmit: handleSubmit,
|
||||||
|
csrfToken: csrfToken
|
||||||
|
});
|
||||||
|
}
|
18
layout.php
18
layout.php
@ -14,19 +14,27 @@ if (!defined('CYLA_CORE')) {
|
|||||||
<link rel="stylesheet" href="css/style.css">
|
<link rel="stylesheet" href="css/style.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel/7.22.5/babel.min.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="passwordModalRoot"></div>
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
<div class="nav-brand">
|
<div class="nav-brand">
|
||||||
<a href="index.php"><?php echo SITE_NAME; ?></a>
|
<a href="index.php"><?php echo SITE_NAME; ?></a>
|
||||||
</div>
|
</div>
|
||||||
<?php if (Cyla::isLoggedIn()): ?>
|
<?php if (Cyla::isLoggedIn()): ?>
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
<a href="admin.php" class="nav-link">Administration</a>
|
<a href="admin.php" class="nav-link">Administration</a>
|
||||||
<a href="logout.php?csrf_token=<?php echo Cyla::generateCSRFToken(); ?>" class="nav-link">Déconnexion</a>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
|
id="changePasswordBtn"
|
||||||
|
class="nav-link"
|
||||||
|
onclick="console.log('Clic natif détecté')"
|
||||||
|
>
|
||||||
|
Changer le mot de passe
|
||||||
|
</button>
|
||||||
|
<a href="logout.php?csrf_token=<?php echo Cyla::generateCSRFToken(); ?>" class="nav-link">Déconnexion</a>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user