Globinours/app/Views/auth.php

229 lines
13 KiB
PHP

<!doctype html>
<html lang="<?= htmlspecialchars(I18n::locale()) ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?= htmlspecialchars($title) ?> — Globinours</title>
<link rel="icon" href="/favicon.ico">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="stylesheet" href="/assets/vendor/tabler/tabler.min.css">
<link rel="stylesheet" href="/assets/mobile.css">
<script defer src="/assets/pwa.js"></script>
<style>
body {
min-height: 100vh;
background: radial-gradient(circle at 15% 15%, rgba(214, 174, 103, .22), transparent 32rem), radial-gradient(circle at 85% 85%, rgba(176, 128, 65, .16), transparent 30rem), #f7f1e6;
display: grid;
place-items: center;
padding: 2rem 0
}
.login-shell {
width: min(94vw, <?=$mode === 'setup' ? '980px' : '440px' ?>)
}
.login-logo {
width: 150px;
height: 150px;
object-fit: contain;
filter: drop-shadow(0 10px 22px rgba(70, 52, 30, .12))
}
.login-card {
border: 1px solid rgba(145, 110, 65, .18);
border-radius: 1.2rem;
box-shadow: 0 18px 55px rgba(71, 53, 31, .12);
overflow: hidden
}
.login-card .card-body {
padding: 2rem
}
.brand-subtitle {
color: #7b6b59
}
.check-row {
display: flex;
gap: .75rem;
padding: .6rem 0;
border-bottom: 1px solid rgba(98, 105, 118, .12)
}
.check-row:last-child {
border-bottom: 0
}
.check-icon {
width: 1.5rem;
flex: 0 0 1.5rem;
text-align: center
}
.setup-path {
word-break: break-all
}
</style>
</head>
<body>
<main class="login-shell">
<div class="text-center mb-3"><img class="login-logo" src="/auth/logo" alt="Globinours">
<h1 class="mb-1">Globinours</h1>
<div class="brand-subtitle"><?= htmlspecialchars(t('auth.tagline')) ?></div>
</div>
<form id="authForm" method="post" action="<?= $mode === 'setup' ? '/setup' : '/login' ?>"
class="card login-card" novalidate>
<div class="card-body"><input type="hidden" name="csrf"
value="<?= htmlspecialchars(Auth::csrf()) ?>"><?php if ($mode === 'login'): ?><input type="hidden"
name="next" value="<?= htmlspecialchars((string)($_GET['next'] ?? '/dashboard')) ?>"><?php endif; ?>
<h2 class="text-center mb-3"><?= htmlspecialchars(t($mode === 'setup' ? 'auth.setup' : 'auth.login')) ?>
</h2><?php if ($mode === 'login' && isset($_GET['restored'])): ?><div class="alert alert-success">✅
<?= htmlspecialchars(t('auth.restore_success')) ?></div><?php endif; ?><?php if ($error): ?><div
class="alert alert-danger"><?= htmlspecialchars($error) ?></div><?php endif; ?><div
id="formValidationError" class="alert alert-danger d-none">
<?= htmlspecialchars(t('auth.fix_fields')) ?></div>
<?php if ($mode === 'setup'): ?><div class="row g-4">
<div class="col-lg-5">
<h3><?= h(t('install.requirements')) ?></h3>
<p class="text-muted small"><?= h(t('install.requirements_help')) ?></p>
<?php foreach ($checks ?? [] as $check): ?><div class="check-row"><span
class="check-icon"><?= $check['ok'] ? '✅' : ($check['level'] === 'required' ? '❌' : '⚠️') ?></span>
<div><strong><?= h($check['label']) ?></strong>
<div class="small text-muted"><?= h($check['detail']) ?></div>
</div>
</div><?php endforeach; ?><div class="alert alert-info mt-3 mb-0">
<strong><?= h(t('install.public_directory')) ?></strong>
<div class="setup-path small mt-1"><?= h($publicPath ?? '') ?></div>
</div>
</div>
<div class="col-lg-7">
<h3><?= h(t('install.association')) ?></h3>
<div class="row g-3 mb-4">
<div class="col-12"><label
class="form-label required"><?= h(t('settings.association_name')) ?></label><input
class="form-control" name="association_name"
value="<?= h($_POST['association_name'] ?? '') ?>" required></div>
<div class="col-md-6"><label class="form-label"><?= h(t('settings.rna')) ?></label><input
class="form-control" name="association_rna"
value="<?= h($_POST['association_rna'] ?? '') ?>"></div>
<div class="col-md-6"><label class="form-label"><?= h(t('settings.siret')) ?></label><input
class="form-control" name="association_siret"
value="<?= h($_POST['association_siret'] ?? '') ?>"></div>
<div class="col-12"><label class="form-label"><?= h(t('settings.address')) ?></label><input
class="form-control" name="association_address"
value="<?= h($_POST['association_address'] ?? '') ?>"></div>
<div class="col-md-4"><label
class="form-label"><?= h(t('settings.postcode')) ?></label><input
class="form-control" name="association_postal_code"
value="<?= h($_POST['association_postal_code'] ?? '') ?>"></div>
<div class="col-md-8"><label class="form-label"><?= h(t('settings.city')) ?></label><input
class="form-control" name="association_city"
value="<?= h($_POST['association_city'] ?? '') ?>"></div>
<div class="col-md-6"><label class="form-label"><?= h(t('settings.phone')) ?></label><input
class="form-control" name="association_phone"
value="<?= h($_POST['association_phone'] ?? '') ?>"></div>
<div class="col-md-6"><label class="form-label"><?= h(t('settings.email')) ?></label><input
class="form-control" type="email" name="association_email"
value="<?= h($_POST['association_email'] ?? '') ?>"></div>
<div class="col-md-6"><label
class="form-label"><?= h(t('settings.language')) ?></label><select
class="form-select"
name="app_language"><?php foreach (I18n::LOCALES as $code => $label): ?><option
value="<?= h($code) ?>"><?= h($label) ?></option><?php endforeach; ?></select>
</div>
<div class="col-md-6"><label
class="form-label"><?= h(t('public_site.main_address')) ?></label><select
class="form-select" name="public_site_routing">
<option value="integrated"><?= h(t('public_site.routing_management')) ?></option>
<option value="root"><?= h(t('public_site.routing_public')) ?></option>
</select></div>
</div>
<h3><?= h(t('install.administrator')) ?></h3>
<div class="mb-3"><label
class="form-label"><?= htmlspecialchars(t('auth.display_name')) ?></label><input
class="form-control form-control-lg" name="display_name" autocomplete="name" required
autofocus>
<div class="invalid-feedback"></div>
</div><?php endif; ?>
<div class="mb-3"><label
class="form-label"><?= htmlspecialchars(t('auth.username')) ?></label><input
class="form-control form-control-lg" name="username" autocomplete="username" required
<?= $mode === 'login' ? 'autofocus' : '' ?>>
<div class="invalid-feedback"></div>
</div>
<div class="mb-3"><label
class="form-label"><?= htmlspecialchars(t('auth.password')) ?></label><input
class="form-control form-control-lg" type="password" name="password"
autocomplete="<?= $mode === 'setup' ? 'new-password' : 'current-password' ?>"
minlength="10" required>
<div class="invalid-feedback"></div>
</div>
<?php if ($mode === 'login'): ?><label class="form-check mb-2"><input class="form-check-input"
type="checkbox" name="remember_device" value="1"><span
class="form-check-label"><?= h(t('auth.remember_device')) ?></span><span
class="form-hint"><?= h(t('auth.remember_device_help')) ?></span></label><input
type="hidden" name="device_name" id="deviceName" value=""><?php endif; ?>
<?php if ($mode === 'setup'): ?><div class="mb-3"><label
class="form-label"><?= htmlspecialchars(t('auth.password_confirm')) ?></label><input
class="form-control form-control-lg" type="password" name="password_confirm"
autocomplete="new-password" minlength="10" required>
<div class="invalid-feedback"></div>
</div>
</div>
</div><?php endif; ?>
<button class="btn btn-primary btn-lg w-100 mt-3"
<?= $mode === 'setup' && !($canInstall ?? false) ? 'disabled' : '' ?>><?= htmlspecialchars(t($mode === 'setup' ? 'auth.create' : 'auth.sign_in')) ?></button>
</div>
</form>
</main>
<script>
const form = document.getElementById('authForm'),
summary = document.getElementById('formValidationError');
function frenchMessage(field) {
if (field.validity.valueMissing) return < ? = json_encode(t('auth.required'), JSON_UNESCAPED_UNICODE) ? > ;
if (field.validity.tooShort) return < ? = json_encode(t('auth.password_min', ['count' => '__COUNT__']),
JSON_UNESCAPED_UNICODE) ? > .replace('__COUNT__', field.minLength);
if (field.validity.patternMismatch) return < ? = json_encode(t('auth.invalid_format'),
JSON_UNESCAPED_UNICODE) ? > ;
return < ? = json_encode(t('auth.check_field'), JSON_UNESCAPED_UNICODE) ? > ;
}
form.addEventListener('submit', event => {
form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
const password = form.elements.password,
confirmation = form.elements.password_confirm;
if (confirmation) confirmation.setCustomValidity(password.value !== confirmation.value ? < ? =
json_encode(t('auth.password_mismatch'), JSON_UNESCAPED_UNICODE) ? > : '');
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
summary.classList.remove('d-none');
form.querySelectorAll(':invalid').forEach(field => {
field.classList.add('is-invalid');
const feedback = field.parentElement.querySelector('.invalid-feedback');
if (feedback) feedback.textContent = field.validationMessage && field.validity
.customError ? field.validationMessage : frenchMessage(field);
});
form.querySelector(':invalid')?.focus();
}
});
form.querySelectorAll('input').forEach(field => field.addEventListener('input', () => {
field.setCustomValidity('');
field.classList.remove('is-invalid');
summary.classList.add('d-none');
}));
const deviceName = document.getElementById('deviceName');
if (deviceName && navigator.userAgent.includes('GlobinoursAndroid')) {
deviceName.value = 'Application Android';
const remember = form.elements.remember_device;
if (remember) remember.checked = true;
}
</script>
</body>
</html>