229 lines
13 KiB
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>
|