Merge branch 'main' into feature/user_profile
# Conflicts: # public/userarea/include/topbar.php # public/userarea/scadenzario/include/my_deadlines_widget.php
This commit is contained in:
@@ -111,6 +111,14 @@ class LoginController extends Controller
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
} elseif ($user->hasRole('User')) {
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
} elseif ($user->hasRole('HR')) {
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
} elseif ($user->hasRole('SuperUser')) {
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
} elseif ($user->hasRole('Management')) {
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
} elseif ($user->hasRole('Quality')) {
|
||||
return redirect()->to('userarea/production_dashboard.php');
|
||||
}
|
||||
|
||||
// Se il ruolo non è specificato, reindirizza alla home predefinita
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex align-items-center" href="../users">
|
||||
<a class="dropdown-item d-flex align-items-center" href="user_settings.php">
|
||||
<i class="bx bx-user fs-5"></i><span>Utente</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -51,7 +51,7 @@ $dashboardSections = [
|
||||
'permission' => 'warehouse.dashboard.view',
|
||||
],
|
||||
[
|
||||
'label' => 'Scadenziario',
|
||||
'label' => 'Smart-Alert',
|
||||
'icon' => '⏰',
|
||||
'class' => 'btn-scadenziario',
|
||||
'url' => 'scadenzario/index.php',
|
||||
@@ -187,7 +187,7 @@ $dashboardSections = [
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<title>Dashboard Produzione - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
<title>Dashboard <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
|
||||
<!-- Bootstrap + jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
@@ -539,7 +539,7 @@ $dashboardSections = [
|
||||
<?php include(__DIR__ . '/include/training_widget.php'); ?>
|
||||
</div>
|
||||
|
||||
<h3 class="dashboard-title">Dashboard Produzione</h3>
|
||||
<h3 class="dashboard-title">Dashboard</h3>
|
||||
|
||||
<!-- ===== STATISTICHE PRINCIPALI ===== -->
|
||||
<div class="stats-row">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Renders two status banners for the current user:
|
||||
* - red -> overdue deadlines (scaduta)
|
||||
@@ -44,63 +45,90 @@ if (!$_emp || ($_overdue === 0 && $_approaching === 0)) {
|
||||
?>
|
||||
<style>
|
||||
.my-deadlines-widgets {
|
||||
display: flex; flex-wrap: wrap; gap: 0.75rem;
|
||||
margin-bottom: 1rem; width: 100%;
|
||||
}
|
||||
.my-deadlines-widgets:empty { display: none; }
|
||||
/* When two widget containers are nested inside an outer .my-deadlines-widgets
|
||||
(e.g. on the production dashboard), let their children flow into the outer flex. */
|
||||
.my-deadlines-widgets .my-deadlines-widgets {
|
||||
display: contents;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw {
|
||||
flex: 1 1 0; min-width: 0;
|
||||
display: flex; align-items: center; gap: 0.75rem;
|
||||
padding: 0.8rem 0.9rem; border-radius: 0.6rem;
|
||||
text-decoration: none; color: #fff;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
||||
flex: 1 1 260px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
padding: 0.85rem 1rem;
|
||||
border-radius: 0.6rem;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.my-deadlines-widgets .mdw:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); color: #fff; }
|
||||
.my-deadlines-widgets .mdw-red { background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%); }
|
||||
.my-deadlines-widgets .mdw-orange { background: linear-gradient(135deg, #e8930c 0%, #c77a00 100%); }
|
||||
.my-deadlines-widgets .mdw-gray { background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); }
|
||||
|
||||
.my-deadlines-widgets .mdw:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw-red {
|
||||
background: linear-gradient(135deg, #dc3545 0%, #b02a37 100%);
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw-orange {
|
||||
background: linear-gradient(135deg, #e8930c 0%, #c77a00 100%);
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw-icon {
|
||||
width: 38px; height: 38px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: rgba(255,255,255,0.22); font-size: 1.05rem; flex-shrink: 0;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.22);
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.my-deadlines-widgets .mdw-body { flex: 1; line-height: 1.2; min-width: 0; }
|
||||
.my-deadlines-widgets .mdw-count { font-size: 1.5rem; font-weight: 700; }
|
||||
.my-deadlines-widgets .mdw-label { font-size: 0.78rem; opacity: 0.95;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.my-deadlines-widgets .mdw-arrow { opacity: 0.7; font-size: 0.85rem; flex-shrink: 0; }
|
||||
@media (max-width: 991.98px) {
|
||||
.my-deadlines-widgets .mdw { flex: 1 1 calc(50% - 0.375rem); }
|
||||
|
||||
.my-deadlines-widgets .mdw-body {
|
||||
flex: 1;
|
||||
line-height: 1.2;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.my-deadlines-widgets .mdw { flex: 1 1 100%; }
|
||||
|
||||
.my-deadlines-widgets .mdw-count {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw-label {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.my-deadlines-widgets .mdw-arrow {
|
||||
opacity: 0.7;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
<div class="my-deadlines-widgets">
|
||||
<?php if ($_overdue > 0): ?>
|
||||
<a class="mdw mdw-red" href="scadenzario/index.php?filter_my=1&filter_status=scaduta">
|
||||
<span class="mdw-icon"><i class="fa-solid fa-triangle-exclamation"></i></span>
|
||||
<span class="mdw-body">
|
||||
<span class="mdw-count"><?= $_overdue ?></span>
|
||||
<span class="mdw-label d-block">Scadenz<?= $_overdue === 1 ? 'a' : 'e' ?> scadut<?= $_overdue === 1 ? 'a' : 'e' ?> — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||
</span>
|
||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($_approaching > 0): ?>
|
||||
<a class="mdw mdw-orange" href="scadenzario/index.php?filter_my=1&filter_status=in-scadenza">
|
||||
<span class="mdw-icon"><i class="fa-solid fa-clock"></i></span>
|
||||
<span class="mdw-body">
|
||||
<span class="mdw-count"><?= $_approaching ?></span>
|
||||
<span class="mdw-label d-block">In scadenza a breve — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||
</span>
|
||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($_overdue > 0): ?>
|
||||
<a class="mdw mdw-red" href="scadenzario/index.php?filter_my=1&filter_status=scaduta">
|
||||
<span class="mdw-icon"><i class="fa-solid fa-triangle-exclamation"></i></span>
|
||||
<span class="mdw-body">
|
||||
<span class="mdw-count"><?= $_overdue ?></span>
|
||||
<span class="mdw-label d-block">Task<?= $_overdue === 1 ? '' : 's' ?> scadut<?= $_overdue === 1 ? 'o' : 'i' ?> — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||
</span>
|
||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($_approaching > 0): ?>
|
||||
<a class="mdw mdw-orange" href="scadenzario/index.php?filter_my=1&filter_status=in-scadenza">
|
||||
<span class="mdw-icon"><i class="fa-solid fa-clock"></i></span>
|
||||
<span class="mdw-body">
|
||||
<span class="mdw-count"><?= $_approaching ?></span>
|
||||
<span class="mdw-label d-block">In scadenza a breve — <?= $_dept !== '' ? htmlspecialchars($_dept, ENT_QUOTES, 'UTF-8') : 'personali' ?></span>
|
||||
</span>
|
||||
<span class="mdw-arrow"><i class="fa-solid fa-arrow-right"></i></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -1115,15 +1115,15 @@ function getContrastTextColor($hexColor)
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="dlDocDate" class="form-label fw-semibold">Data documento</label>
|
||||
<input type="date" class="form-control" id="dlDocDate" name="document_date">
|
||||
<input type="text" class="form-control js-date-it" id="dlDocDate" name="document_date" placeholder="gg/mm/aaaa" autocomplete="off">
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="dlDueDate" class="form-label fw-semibold">Data scadenza <span class="text-danger">*</span></label>
|
||||
<input type="date" class="form-control" id="dlDueDate" name="due_date" required>
|
||||
<input type="text" class="form-control js-date-it" id="dlDueDate" name="due_date" placeholder="gg/mm/aaaa" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="dlCheckDate" class="form-label fw-semibold">Data ultimo controllo</label>
|
||||
<input type="date" class="form-control" id="dlCheckDate" name="check_date">
|
||||
<input type="text" class="form-control js-date-it" id="dlCheckDate" name="check_date" placeholder="gg/mm/aaaa" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1221,6 +1221,33 @@ function getContrastTextColor($hexColor)
|
||||
var fpDue = flatpickr('#filterDueRange', fpOpts);
|
||||
var fpCheck = flatpickr('#filterCheckRange', fpOpts);
|
||||
|
||||
// --- Flatpickr Italian date fields in deadline modal ---
|
||||
// Visible format: dd/mm/yyyy
|
||||
// Submitted format: yyyy-mm-dd, compatible with MySQL DATE
|
||||
var fpDocDate = flatpickr('#dlDocDate', {
|
||||
dateFormat: 'Y-m-d',
|
||||
altInput: true,
|
||||
altFormat: 'd/m/Y',
|
||||
locale: 'it',
|
||||
allowInput: true
|
||||
});
|
||||
|
||||
var fpDueDate = flatpickr('#dlDueDate', {
|
||||
dateFormat: 'Y-m-d',
|
||||
altInput: true,
|
||||
altFormat: 'd/m/Y',
|
||||
locale: 'it',
|
||||
allowInput: true
|
||||
});
|
||||
|
||||
var fpCheckDate = flatpickr('#dlCheckDate', {
|
||||
dateFormat: 'Y-m-d',
|
||||
altInput: true,
|
||||
altFormat: 'd/m/Y',
|
||||
locale: 'it',
|
||||
allowInput: true
|
||||
});
|
||||
|
||||
// --- Select2 ---
|
||||
$('#dlSubject').select2({
|
||||
theme: 'bootstrap-5',
|
||||
@@ -1295,7 +1322,7 @@ function getContrastTextColor($hexColor)
|
||||
var iso = d.getFullYear() + '-' +
|
||||
String(d.getMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(d.getDate()).padStart(2, '0');
|
||||
document.getElementById('dlDueDate').value = iso;
|
||||
fpDueDate.setDate(iso, true, 'Y-m-d');
|
||||
}
|
||||
$('#dlDocDate, #dlRecurrence').on('change', computeDueDate);
|
||||
|
||||
@@ -1464,16 +1491,24 @@ function getContrastTextColor($hexColor)
|
||||
var modal = new bootstrap.Modal(document.getElementById('deadlineModal'));
|
||||
var form = document.getElementById('deadlineForm');
|
||||
|
||||
// Add
|
||||
// Add
|
||||
document.getElementById('btnAddDeadline').addEventListener('click', function() {
|
||||
form.reset();
|
||||
|
||||
document.getElementById('dlId').value = '';
|
||||
document.getElementById('dlNotifDays').value = '7';
|
||||
document.getElementById('modalTitle').textContent = 'Nuova Scadenza';
|
||||
document.getElementById('dlFiles').value = '';
|
||||
|
||||
fpDocDate.clear();
|
||||
fpDueDate.clear();
|
||||
fpCheckDate.clear();
|
||||
|
||||
$('#dlSubject').val('').trigger('change');
|
||||
$('#dlDepartments').val(null).trigger('change');
|
||||
$('#dlEmployees').val(null).trigger('change');
|
||||
|
||||
renderAttachments([]);
|
||||
modal.show();
|
||||
});
|
||||
@@ -1639,9 +1674,9 @@ function getContrastTextColor($hexColor)
|
||||
document.getElementById('dlTopic').value = d.topic || '';
|
||||
document.getElementById('dlLaw').value = d.law_regulation || '';
|
||||
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
||||
document.getElementById('dlDocDate').value = d.document_date || '';
|
||||
document.getElementById('dlDueDate').value = d.due_date || '';
|
||||
document.getElementById('dlCheckDate').value = d.check_date || '';
|
||||
fpDocDate.setDate(d.document_date || null, false, 'Y-m-d');
|
||||
fpDueDate.setDate(d.due_date || null, false, 'Y-m-d');
|
||||
fpCheckDate.setDate(d.check_date || null, false, 'Y-m-d');
|
||||
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
||||
document.getElementById('dlStorage').value = d.storage_location || '';
|
||||
document.getElementById('dlNotes').value = d.notes || '';
|
||||
@@ -1765,9 +1800,9 @@ function getContrastTextColor($hexColor)
|
||||
document.getElementById('dlTopic').value = d.topic || '';
|
||||
document.getElementById('dlLaw').value = d.law_regulation || '';
|
||||
document.getElementById('dlRecurrence').value = d.recurrence_type || 'once';
|
||||
document.getElementById('dlDocDate').value = d.document_date || '';
|
||||
document.getElementById('dlDueDate').value = d.due_date || '';
|
||||
document.getElementById('dlCheckDate').value = d.check_date || '';
|
||||
fpDocDate.setDate(d.document_date || null, false, 'Y-m-d');
|
||||
fpDueDate.setDate(d.due_date || null, false, 'Y-m-d');
|
||||
fpCheckDate.setDate(d.check_date || null, false, 'Y-m-d');
|
||||
document.getElementById('dlNotifDays').value = d.notification_days || 7;
|
||||
document.getElementById('dlStorage').value = d.storage_location || '';
|
||||
document.getElementById('dlNotes').value = d.notes || '';
|
||||
|
||||
@@ -0,0 +1,868 @@
|
||||
<?php include('include/headscript.php'); ?>
|
||||
<?php
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$userId = (int)($iduserlogin ?? 0);
|
||||
|
||||
if ($userId <= 0) {
|
||||
die('Utente non valido.');
|
||||
}
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (empty($_SESSION['user_settings_csrf'])) {
|
||||
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
$csrfToken = $_SESSION['user_settings_csrf'];
|
||||
|
||||
$successMessage = '';
|
||||
$errorMessage = '';
|
||||
|
||||
// Load countries.
|
||||
$countries = [];
|
||||
try {
|
||||
$stmtCountries = $pdo->query("
|
||||
SELECT id, name, iso_3166_2
|
||||
FROM auth_countries
|
||||
ORDER BY name ASC
|
||||
");
|
||||
$countries = $stmtCountries->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (Exception $e) {
|
||||
$countries = [];
|
||||
}
|
||||
|
||||
// Load current user.
|
||||
$stmtProfileUser = $pdo->prepare("
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
password,
|
||||
first_name,
|
||||
last_name,
|
||||
phone,
|
||||
avatar,
|
||||
address,
|
||||
country_id,
|
||||
birthday,
|
||||
role_id,
|
||||
status,
|
||||
last_login
|
||||
FROM auth_users
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtProfileUser->execute([$userId]);
|
||||
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$profileUser) {
|
||||
die('Utente non trovato.');
|
||||
}
|
||||
|
||||
function e($value)
|
||||
{
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function normalizeAvatarPath($avatar)
|
||||
{
|
||||
$avatar = trim((string)$avatar);
|
||||
|
||||
if ($avatar === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// If the database already contains a complete relative path, use it as it is.
|
||||
if (
|
||||
str_starts_with($avatar, '../') ||
|
||||
str_starts_with($avatar, './') ||
|
||||
str_starts_with($avatar, '/') ||
|
||||
str_starts_with($avatar, 'http://') ||
|
||||
str_starts_with($avatar, 'https://')
|
||||
) {
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
// If the database contains only the filename, build the expected user upload path.
|
||||
return '../upload/users/' . $avatar;
|
||||
}
|
||||
|
||||
function getAvatarInitials($profileUser)
|
||||
{
|
||||
$first = trim((string)($profileUser['first_name'] ?? ''));
|
||||
$last = trim((string)($profileUser['last_name'] ?? ''));
|
||||
$email = trim((string)($profileUser['email'] ?? ''));
|
||||
|
||||
$initials = '';
|
||||
|
||||
if ($first !== '') {
|
||||
$initials .= mb_substr($first, 0, 1);
|
||||
}
|
||||
|
||||
if ($last !== '') {
|
||||
$initials .= mb_substr($last, 0, 1);
|
||||
}
|
||||
|
||||
if ($initials === '' && $email !== '') {
|
||||
$initials = mb_substr($email, 0, 1);
|
||||
}
|
||||
|
||||
return strtoupper($initials ?: 'U');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$postedToken = $_POST['csrf_token'] ?? '';
|
||||
|
||||
if (!hash_equals($csrfToken, $postedToken)) {
|
||||
$errorMessage = 'Sessione non valida. Ricarica la pagina e riprova.';
|
||||
} else {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$firstName = trim($_POST['first_name'] ?? '');
|
||||
$lastName = trim($_POST['last_name'] ?? '');
|
||||
$phone = trim($_POST['phone'] ?? '');
|
||||
$address = trim($_POST['address'] ?? '');
|
||||
$countryId = $_POST['country_id'] !== '' ? (int)$_POST['country_id'] : null;
|
||||
$birthday = trim($_POST['birthday'] ?? '');
|
||||
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
$birthdayValue = null;
|
||||
$avatarToSave = $profileUser['avatar'];
|
||||
|
||||
if ($birthday !== '') {
|
||||
$dateObj = DateTime::createFromFormat('Y-m-d', $birthday);
|
||||
|
||||
if (!$dateObj || $dateObj->format('Y-m-d') !== $birthday) {
|
||||
$errorMessage = 'La data di nascita non è valida.';
|
||||
} else {
|
||||
$birthdayValue = $birthday;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errorMessage && $email === '') {
|
||||
$errorMessage = 'L’email è obbligatoria.';
|
||||
}
|
||||
|
||||
if (!$errorMessage && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errorMessage = 'L’email inserita non è valida.';
|
||||
}
|
||||
|
||||
// Check unique email.
|
||||
if (!$errorMessage) {
|
||||
$stmtCheckEmail = $pdo->prepare("
|
||||
SELECT id
|
||||
FROM auth_users
|
||||
WHERE email = ? AND id <> ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtCheckEmail->execute([$email, $userId]);
|
||||
|
||||
if ($stmtCheckEmail->fetchColumn()) {
|
||||
$errorMessage = 'Questa email è già utilizzata da un altro utente.';
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar upload.
|
||||
if (!$errorMessage && isset($_FILES['avatar']) && $_FILES['avatar']['error'] !== UPLOAD_ERR_NO_FILE) {
|
||||
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
|
||||
$errorMessage = 'Errore durante il caricamento dell’avatar.';
|
||||
} else {
|
||||
$maxFileSize = 2 * 1024 * 1024; // 2 MB
|
||||
|
||||
if ($_FILES['avatar']['size'] > $maxFileSize) {
|
||||
$errorMessage = 'L’avatar non può superare 2 MB.';
|
||||
} else {
|
||||
$tmpFile = $_FILES['avatar']['tmp_name'];
|
||||
$originalName = $_FILES['avatar']['name'];
|
||||
|
||||
$allowedMimeTypes = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
'image/gif' => 'gif',
|
||||
];
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($tmpFile);
|
||||
|
||||
if (!array_key_exists($mimeType, $allowedMimeTypes)) {
|
||||
$errorMessage = 'Formato avatar non valido. Sono consentiti JPG, PNG, WEBP o GIF.';
|
||||
} else {
|
||||
$uploadDir = __DIR__ . '/../upload/users/';
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
$extension = $allowedMimeTypes[$mimeType];
|
||||
$safeOriginalName = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', pathinfo($originalName, PATHINFO_FILENAME));
|
||||
$fileName = time() . '_' . $userId . '_' . $safeOriginalName . '.' . $extension;
|
||||
|
||||
$destination = $uploadDir . $fileName;
|
||||
|
||||
if (!move_uploaded_file($tmpFile, $destination)) {
|
||||
$errorMessage = 'Impossibile salvare il file avatar.';
|
||||
} else {
|
||||
// Path used by pages inside userarea, for example:
|
||||
// <img src="../upload/users/file.jpg">
|
||||
$avatarToSave = $fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$passwordToSave = null;
|
||||
$wantsPasswordChange = ($currentPassword !== '' || $newPassword !== '' || $confirmPassword !== '');
|
||||
|
||||
if (!$errorMessage && $wantsPasswordChange) {
|
||||
if ($currentPassword === '') {
|
||||
$errorMessage = 'Inserisci la password attuale.';
|
||||
} elseif ($newPassword === '') {
|
||||
$errorMessage = 'Inserisci la nuova password.';
|
||||
} elseif (strlen($newPassword) < 8) {
|
||||
$errorMessage = 'La nuova password deve contenere almeno 8 caratteri.';
|
||||
} elseif ($newPassword !== $confirmPassword) {
|
||||
$errorMessage = 'La conferma password non corrisponde.';
|
||||
} elseif (!password_verify($currentPassword, $profileUser['password'])) {
|
||||
$errorMessage = 'La password attuale non è corretta.';
|
||||
} else {
|
||||
// Password is encrypted before saving.
|
||||
$passwordToSave = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$errorMessage) {
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE auth_users
|
||||
SET
|
||||
email = :email,
|
||||
first_name = :first_name,
|
||||
last_name = :last_name,
|
||||
phone = :phone,
|
||||
avatar = :avatar,
|
||||
address = :address,
|
||||
country_id = :country_id,
|
||||
birthday = :birthday,
|
||||
updated_at = NOW()
|
||||
WHERE id = :id
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$stmtUpdate->execute([
|
||||
':email' => $email,
|
||||
':first_name' => $firstName !== '' ? $firstName : null,
|
||||
':last_name' => $lastName !== '' ? $lastName : null,
|
||||
':phone' => $phone !== '' ? $phone : null,
|
||||
':avatar' => $avatarToSave !== '' ? $avatarToSave : null,
|
||||
':address' => $address !== '' ? $address : null,
|
||||
':country_id' => $countryId,
|
||||
':birthday' => $birthdayValue,
|
||||
':id' => $userId,
|
||||
]);
|
||||
|
||||
if ($passwordToSave !== null) {
|
||||
$stmtPassword = $pdo->prepare("
|
||||
UPDATE auth_users
|
||||
SET password = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtPassword->execute([$passwordToSave, $userId]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
$successMessage = $passwordToSave !== null
|
||||
? 'Profilo, avatar e password aggiornati correttamente.'
|
||||
: 'Profilo aggiornato correttamente.';
|
||||
|
||||
// Reload updated user.
|
||||
$stmtProfileUser->execute([$userId]);
|
||||
$profileUser = $stmtProfileUser->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$_SESSION['user_settings_csrf'] = bin2hex(random_bytes(32));
|
||||
$csrfToken = $_SESSION['user_settings_csrf'];
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
$errorMessage = 'Errore durante il salvataggio delle impostazioni.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$avatarPath = normalizeAvatarPath($profileUser['avatar'] ?? '');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<title>Impostazioni Utente <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: linear-gradient(135deg, #f3f6f8, #e8eef3);
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
color: #2b3e50;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 1.4rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.settings-wrap {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
h3.settings-title {
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
color: #2b3e50;
|
||||
margin-bottom: 1.2rem;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
padding: 16px 18px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.settings-icon {
|
||||
font-size: 1.8rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.settings-heading {
|
||||
margin: 0;
|
||||
font-weight: 800;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.settings-subtitle {
|
||||
margin: 0;
|
||||
color: #6b7a89;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.settings-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profile-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.avatar-panel {
|
||||
background: linear-gradient(135deg, #f7fbff, #edf5ff);
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 18px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.avatar-box {
|
||||
width: 132px;
|
||||
height: 132px;
|
||||
border-radius: 34px;
|
||||
background: linear-gradient(135deg, #cde5ff, #dff0ff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: #2b3e50;
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
margin: 0 auto 14px auto;
|
||||
}
|
||||
|
||||
.avatar-box img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 800;
|
||||
margin: 0;
|
||||
color: #2b3e50;
|
||||
}
|
||||
|
||||
.avatar-email {
|
||||
color: #6b7a89;
|
||||
margin: 4px 0 14px 0;
|
||||
font-size: 0.92rem;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.avatar-upload-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
border-radius: 14px;
|
||||
padding: 11px 14px;
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
color: #2b3e50;
|
||||
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.avatar-upload-label:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||
}
|
||||
|
||||
.avatar-upload-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.avatar-help {
|
||||
font-size: 0.82rem;
|
||||
color: #6b7a89;
|
||||
margin-top: 10px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.profile-meta {
|
||||
color: #6b7a89;
|
||||
font-size: 0.88rem;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: 700;
|
||||
color: #2b3e50;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-select {
|
||||
border-radius: 12px;
|
||||
border: 1px solid #d8e0e7;
|
||||
padding: 10px 12px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-control:focus,
|
||||
.form-select:focus {
|
||||
border-color: #8bbcf7;
|
||||
box-shadow: 0 0 0 0.18rem rgba(139, 188, 247, 0.25);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.86rem;
|
||||
color: #6b7a89;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.password-box {
|
||||
background: linear-gradient(135deg, #fff7e6, #fffaf0);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
border: 1px solid rgba(255, 184, 107, 0.45);
|
||||
}
|
||||
|
||||
.btn-save-settings {
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 13px 24px;
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #61ce5dff, #61ce5dff);
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-save-settings:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 13px 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
color: #2b3e50;
|
||||
background: linear-gradient(135deg, #e5e7eb, #f3f4f6);
|
||||
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-back:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.13);
|
||||
color: #2b3e50;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 16px;
|
||||
border: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.readonly-note {
|
||||
background: linear-gradient(135deg, #cde5ff, #dff0ff);
|
||||
border-radius: 16px;
|
||||
padding: 14px 16px;
|
||||
color: #2b3e50;
|
||||
font-weight: 600;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.selected-file-name {
|
||||
font-size: 0.84rem;
|
||||
color: #2b3e50;
|
||||
margin-top: 8px;
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.profile-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.avatar-panel {
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.settings-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.actions-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-save-settings,
|
||||
.btn-back {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper toggled">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
|
||||
<div class="settings-wrap">
|
||||
<h3 class="settings-title">Impostazioni Utente</h3>
|
||||
|
||||
<?php if ($successMessage): ?>
|
||||
<div class="alert alert-success">
|
||||
<?= e($successMessage); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($errorMessage): ?>
|
||||
<div class="alert alert-danger">
|
||||
<?= e($errorMessage); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" autocomplete="off">
|
||||
<input type="hidden" name="csrf_token" value="<?= e($csrfToken); ?>">
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-header">
|
||||
<div class="settings-icon">👤</div>
|
||||
<div>
|
||||
<p class="settings-heading">Profilo personale</p>
|
||||
<p class="settings-subtitle">Dati anagrafici, contatti e avatar utente</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-body">
|
||||
|
||||
<div class="readonly-note">
|
||||
Ruolo, stato account e impostazioni di sicurezza avanzate non sono modificabili da questa pagina.
|
||||
</div>
|
||||
|
||||
<div class="profile-layout">
|
||||
<div class="avatar-panel">
|
||||
<div class="avatar-box" id="avatarPreviewBox">
|
||||
<?php if (!empty($avatarPath)): ?>
|
||||
<img src="<?= e($avatarPath); ?>" class="user-img" alt="user avatar" id="avatarPreviewImage">
|
||||
<?php else: ?>
|
||||
<span id="avatarInitials"><?= e(getAvatarInitials($profileUser)); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<p class="avatar-name">
|
||||
<?= e(trim(($profileUser['first_name'] ?? '') . ' ' . ($profileUser['last_name'] ?? '')) ?: 'Utente'); ?>
|
||||
</p>
|
||||
|
||||
<p class="avatar-email">
|
||||
<?= e($profileUser['email']); ?>
|
||||
</p>
|
||||
|
||||
<label for="avatar" class="avatar-upload-label">
|
||||
Carica avatar
|
||||
</label>
|
||||
|
||||
<input type="file"
|
||||
class="avatar-upload-input"
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
accept="image/jpeg,image/png,image/webp,image/gif">
|
||||
|
||||
<div id="selectedFileName" class="selected-file-name"></div>
|
||||
|
||||
<div class="avatar-help">
|
||||
Formati consentiti: JPG, PNG, WEBP, GIF.<br>
|
||||
Dimensione massima: 2 MB.
|
||||
</div>
|
||||
|
||||
<div class="profile-meta">
|
||||
Stato account: <?= e($profileUser['status']); ?>
|
||||
<?php if (!empty($profileUser['last_login'])): ?>
|
||||
<br>Ultimo accesso: <?= e(date('d/m/Y H:i', strtotime($profileUser['last_login']))); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="first_name">Nome</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
value="<?= e($profileUser['first_name']); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="last_name">Cognome</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
value="<?= e($profileUser['last_name']); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="email">Email</label>
|
||||
<input type="email"
|
||||
class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value="<?= e($profileUser['email']); ?>"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="phone">Telefono</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="phone"
|
||||
name="phone"
|
||||
value="<?= e($profileUser['phone']); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="birthday">Data di nascita</label>
|
||||
<input type="date"
|
||||
class="form-control"
|
||||
id="birthday"
|
||||
name="birthday"
|
||||
value="<?= e($profileUser['birthday']); ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" for="country_id">Paese</label>
|
||||
<select class="form-select" id="country_id" name="country_id">
|
||||
<option value="">Seleziona...</option>
|
||||
<?php foreach ($countries as $country): ?>
|
||||
<option value="<?= (int)$country['id']; ?>"
|
||||
<?= ((int)$profileUser['country_id'] === (int)$country['id']) ? 'selected' : ''; ?>>
|
||||
<?= e($country['name'] . (!empty($country['iso_3166_2']) ? ' (' . $country['iso_3166_2'] . ')' : '')); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label class="form-label" for="address">Indirizzo</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="address"
|
||||
name="address"
|
||||
value="<?= e($profileUser['address']); ?>">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<div class="settings-header">
|
||||
<div class="settings-icon">🔐</div>
|
||||
<div>
|
||||
<p class="settings-heading">Cambio password</p>
|
||||
<p class="settings-subtitle">Compila questa sezione solo se vuoi modificare la password</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-body">
|
||||
<div class="password-box">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="current_password">Password attuale</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="current_password"
|
||||
name="current_password"
|
||||
autocomplete="current-password">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="new_password">Nuova password</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="new_password"
|
||||
name="new_password"
|
||||
autocomplete="new-password">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" for="confirm_password">Conferma nuova password</label>
|
||||
<input type="password"
|
||||
class="form-control"
|
||||
id="confirm_password"
|
||||
name="confirm_password"
|
||||
autocomplete="new-password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-text">
|
||||
Se lasci questi campi vuoti, la password attuale rimane invariata.
|
||||
La nuova password deve avere almeno 8 caratteri.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions-row">
|
||||
<a href="production_dashboard.php" class="btn-back">← Torna alla dashboard</a>
|
||||
<button type="submit" class="btn-save-settings">Salva impostazioni</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const avatarInput = document.getElementById('avatar');
|
||||
const previewBox = document.getElementById('avatarPreviewBox');
|
||||
const selectedFileName = document.getElementById('selectedFileName');
|
||||
|
||||
if (!avatarInput || !previewBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
avatarInput.addEventListener('change', function() {
|
||||
const file = this.files && this.files[0] ? this.files[0] : null;
|
||||
|
||||
if (!file) {
|
||||
selectedFileName.textContent = '';
|
||||
return;
|
||||
}
|
||||
|
||||
selectedFileName.textContent = file.name;
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(event) {
|
||||
previewBox.innerHTML = '';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = event.target.result;
|
||||
img.className = 'user-img';
|
||||
img.alt = 'user avatar';
|
||||
|
||||
previewBox.appendChild(img);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user