login improvements
This commit is contained in:
1
.php-cs-fixer.cache
Normal file
1
.php-cs-fixer.cache
Normal file
File diff suppressed because one or more lines are too long
311
bin/agent-prompts.sh
Executable file
311
bin/agent-prompts.sh
Executable file
@@ -0,0 +1,311 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
Usage:
|
||||||
|
bin/agent-prompts.sh --task TASK_ID [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--task TASK_ID Required. Task identifier (e.g. AUTH-LOGIN-REVIEW-001)
|
||||||
|
--role ROLE planner|executor|executor-rerun|reviewer-guards|reviewer-acceptance|finalizer|all
|
||||||
|
Default: all
|
||||||
|
--goal TEXT Planner goal text
|
||||||
|
--scope-in TEXT Planner scope-in text
|
||||||
|
--scope-out TEXT Planner scope-out text
|
||||||
|
--guards CSV Planner required guards (e.g. GR-CORE-005,GR-SEC-001)
|
||||||
|
--gates CSV Planner required gates (e.g. QG-001,QG-002,QG-003,QG-006)
|
||||||
|
--run-dir PATH Override run directory (default: agent-system/runs/<TASK_ID>)
|
||||||
|
--short Emit 3-line one-pager prompt variants
|
||||||
|
-h, --help Show help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
bin/agent-prompts.sh --task AUTH-LOGIN-REVIEW-001
|
||||||
|
bin/agent-prompts.sh --task AUTH-LOGIN-REVIEW-001 --role planner --goal "Auth/Login review"
|
||||||
|
bin/agent-prompts.sh --task TASK-42 --short
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
task_id=""
|
||||||
|
role="all"
|
||||||
|
goal="<KURZES_ZIEL>"
|
||||||
|
scope_in="<DATEIEN/BEREICHE>"
|
||||||
|
scope_out="<AUSSERHALB_SCOPE>"
|
||||||
|
guards="<GR-...>"
|
||||||
|
gates="<QG-...>"
|
||||||
|
short_mode=0
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--task)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --task requires a value" >&2; exit 2; }
|
||||||
|
task_id="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--role)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --role requires a value" >&2; exit 2; }
|
||||||
|
role="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--goal)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --goal requires a value" >&2; exit 2; }
|
||||||
|
goal="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--scope-in)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --scope-in requires a value" >&2; exit 2; }
|
||||||
|
scope_in="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--scope-out)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --scope-out requires a value" >&2; exit 2; }
|
||||||
|
scope_out="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--guards)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --guards requires a value" >&2; exit 2; }
|
||||||
|
guards="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--gates)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --gates requires a value" >&2; exit 2; }
|
||||||
|
gates="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--run-dir)
|
||||||
|
[[ $# -ge 2 ]] || { echo "[ERROR] --run-dir requires a value" >&2; exit 2; }
|
||||||
|
run_dir="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--short)
|
||||||
|
short_mode=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "[ERROR] Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$task_id" ]]; then
|
||||||
|
echo "[ERROR] --task is required" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${run_dir:-}" == "" ]]; then
|
||||||
|
run_dir="agent-system/runs/${task_id}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_valid_role() {
|
||||||
|
case "$1" in
|
||||||
|
planner|executor|executor-rerun|reviewer-guards|reviewer-acceptance|finalizer|all)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! is_valid_role "$role"; then
|
||||||
|
echo "[ERROR] Invalid --role: $role" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_divider() {
|
||||||
|
echo
|
||||||
|
echo "============================================================"
|
||||||
|
echo "$1"
|
||||||
|
echo "============================================================"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_short_prompts() {
|
||||||
|
cat <<EOF
|
||||||
|
Planner (3 Zeilen)
|
||||||
|
Du bist Planner im agent-system; liefere nur valid JSON nach planner.schema.json.
|
||||||
|
Task ${task_id}; Ziel ${goal}; Scope in/out + Pflicht-Guards ${guards} + Pflicht-Gates ${gates}.
|
||||||
|
Erzeuge SC-IDs, Schritte, Tests, Acceptance-Checks, Risiken -> ${run_dir}/plan.json.
|
||||||
|
|
||||||
|
Executor (3 Zeilen)
|
||||||
|
Du bist Executor im agent-system; liefere nur valid JSON nach executor.schema.json.
|
||||||
|
Input: ${run_dir}/plan.json; implementiere nur im Scope, keine Drift.
|
||||||
|
Führe relevante Gates aus (pass nur Exit 0), sonst blocked+open_items -> ${run_dir}/execution-report.json.
|
||||||
|
|
||||||
|
Executor Re-Run (3 Zeilen)
|
||||||
|
Du bist Executor Re-Run; liefere nur valid JSON nach executor.schema.json.
|
||||||
|
Input: plan.json + execution-report.json + review-guards.json; behebe alle Findings.
|
||||||
|
Gates neu ausführen, Report synchronisieren -> ${run_dir}/execution-report.json.
|
||||||
|
|
||||||
|
Reviewer-Guards (3 Zeilen)
|
||||||
|
Du bist Reviewer-Guards; liefere nur valid JSON nach reviewer-guards.schema.json.
|
||||||
|
Prüfe execution-report gegen alle required_guard_ids + required_quality_gate_ids aus plan.json.
|
||||||
|
Findings nur mit rule_ref, severity, file -> ${run_dir}/review-guards.json.
|
||||||
|
|
||||||
|
Reviewer-Acceptance (3 Zeilen)
|
||||||
|
Du bist Reviewer-Acceptance; liefere nur valid JSON nach reviewer-acceptance.schema.json.
|
||||||
|
Prüfe jedes SC-* aus plan.json gegen execution-report mit klarer Evidence.
|
||||||
|
Bei fail missing_or_wrong füllen -> ${run_dir}/review-acceptance.json.
|
||||||
|
|
||||||
|
Finalizer (3 Zeilen)
|
||||||
|
Du bist Finalizer; liefere nur valid JSON nach finalizer.schema.json.
|
||||||
|
Input: execution-report.json + review-guards.json + review-acceptance.json.
|
||||||
|
Nur commit/merge bei guard=pass, acceptance=pass, ci=pass; sonst hold+hold_reason -> ${run_dir}/finalize.json.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_planner() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Planner im agent-system.
|
||||||
|
Lies die Planner-Baseline + Guard/Gate-Kataloge und gib ausschließlich valid JSON nach agent-system/contracts/planner.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Ziel: ${goal}
|
||||||
|
Scope in: ${scope_in}
|
||||||
|
Scope out: ${scope_out}
|
||||||
|
Pflicht-Guards: ${guards}
|
||||||
|
Pflicht-Gates: ${gates}
|
||||||
|
|
||||||
|
Erstelle: klare SC-IDs, konkrete Schritte, Tests, Acceptance-Checks, Risiken mit Mitigation.
|
||||||
|
Output-Datei: ${run_dir}/plan.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_executor() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Executor im agent-system.
|
||||||
|
Lies die Executor-Baseline und gib ausschließlich valid JSON nach agent-system/contracts/executor.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Input: ${run_dir}/plan.json
|
||||||
|
|
||||||
|
Regeln:
|
||||||
|
- Nur Plan-Scope umsetzen, keine Scope-Drift.
|
||||||
|
- Guard-Evidence und Gate-Results vollständig.
|
||||||
|
- Gates nach echtem Exit-Code reporten (pass nur bei Exit 0).
|
||||||
|
- Bei Blockern: status="blocked" + genaue open_items.
|
||||||
|
|
||||||
|
Output-Datei: ${run_dir}/execution-report.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_executor_rerun() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Executor im agent-system.
|
||||||
|
Gib ausschließlich valid JSON nach agent-system/contracts/executor.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Input:
|
||||||
|
- ${run_dir}/plan.json
|
||||||
|
- ${run_dir}/execution-report.json
|
||||||
|
- ${run_dir}/review-guards.json
|
||||||
|
|
||||||
|
Behebe alle offenen Findings und synchronisiere den Report.
|
||||||
|
Pflicht: Gates erneut ausführen und konsistent reporten (echter Exit-Code).
|
||||||
|
Wenn alles grün: status="done". Sonst status="blocked" + exakte Blocker.
|
||||||
|
|
||||||
|
Output-Datei: ${run_dir}/execution-report.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_reviewer_guards() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Reviewer-Guards im agent-system.
|
||||||
|
Prüfe execution-report gegen Guardrails/Security und gib ausschließlich valid JSON nach agent-system/contracts/reviewer-guards.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Input:
|
||||||
|
- ${run_dir}/plan.json
|
||||||
|
- ${run_dir}/execution-report.json
|
||||||
|
|
||||||
|
Prüfe mindestens:
|
||||||
|
- alle required_guard_ids aus plan.json
|
||||||
|
- alle required_quality_gate_ids aus plan.json
|
||||||
|
|
||||||
|
Jedes Finding braucht: rule_ref (GR-/QG-ID), severity, file.
|
||||||
|
Output-Datei: ${run_dir}/review-guards.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_reviewer_acceptance() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Reviewer-Acceptance im agent-system.
|
||||||
|
Gib ausschließlich valid JSON nach agent-system/contracts/reviewer-acceptance.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Input:
|
||||||
|
- ${run_dir}/plan.json
|
||||||
|
- ${run_dir}/execution-report.json
|
||||||
|
|
||||||
|
Bewerte jedes SC-* aus plan.json einzeln mit Evidence.
|
||||||
|
Bei fail: missing_or_wrong explizit befüllen.
|
||||||
|
|
||||||
|
Output-Datei: ${run_dir}/review-acceptance.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
print_finalizer() {
|
||||||
|
cat <<EOF
|
||||||
|
Du bist Finalizer im agent-system.
|
||||||
|
Gib ausschließlich valid JSON nach agent-system/contracts/finalizer.schema.json aus.
|
||||||
|
|
||||||
|
Task: ${task_id}
|
||||||
|
Input:
|
||||||
|
- ${run_dir}/execution-report.json
|
||||||
|
- ${run_dir}/review-guards.json
|
||||||
|
- ${run_dir}/review-acceptance.json
|
||||||
|
|
||||||
|
Regel:
|
||||||
|
- commit/merge nur wenn guard_review=pass, acceptance_review=pass, ci_status=pass
|
||||||
|
- sonst final_action="hold" mit klarer hold_reason
|
||||||
|
|
||||||
|
Output-Datei: ${run_dir}/finalize.json
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $short_mode -eq 1 ]]; then
|
||||||
|
print_short_prompts
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$role" in
|
||||||
|
planner)
|
||||||
|
print_planner
|
||||||
|
;;
|
||||||
|
executor)
|
||||||
|
print_executor
|
||||||
|
;;
|
||||||
|
executor-rerun)
|
||||||
|
print_executor_rerun
|
||||||
|
;;
|
||||||
|
reviewer-guards)
|
||||||
|
print_reviewer_guards
|
||||||
|
;;
|
||||||
|
reviewer-acceptance)
|
||||||
|
print_reviewer_acceptance
|
||||||
|
;;
|
||||||
|
finalizer)
|
||||||
|
print_finalizer
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
print_divider "Planner"
|
||||||
|
print_planner
|
||||||
|
print_divider "Executor"
|
||||||
|
print_executor
|
||||||
|
print_divider "Executor Re-Run"
|
||||||
|
print_executor_rerun
|
||||||
|
print_divider "Reviewer-Guards"
|
||||||
|
print_reviewer_guards
|
||||||
|
print_divider "Reviewer-Acceptance"
|
||||||
|
print_reviewer_acceptance
|
||||||
|
print_divider "Finalizer"
|
||||||
|
print_finalizer
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -52,6 +52,9 @@
|
|||||||
"Choose how you want to sign in": "Anmeldeart auswählen",
|
"Choose how you want to sign in": "Anmeldeart auswählen",
|
||||||
"Continue": "Weiter",
|
"Continue": "Weiter",
|
||||||
"Use different email": "Andere E-Mail verwenden",
|
"Use different email": "Andere E-Mail verwenden",
|
||||||
|
"Encrypted connection": "Verschlüsselt",
|
||||||
|
"HTTPS/TLS 1.2+ in transit": "HTTPS/TLS 1.2+ bei der Übertragung",
|
||||||
|
"Problems logging in": "Probleme beim Login",
|
||||||
"We could not continue with these login details": "Mit diesen Login-Daten konnte nicht fortgefahren werden",
|
"We could not continue with these login details": "Mit diesen Login-Daten konnte nicht fortgefahren werden",
|
||||||
"Cached": "Cache",
|
"Cached": "Cache",
|
||||||
"Global settings are stored in the database. config/settings.php is only a runtime cache for selected app settings.": "Globale Einstellungen werden in der Datenbank gespeichert. config/settings.php ist nur ein Laufzeit-Cache für ausgewählte App-Einstellungen.",
|
"Global settings are stored in the database. config/settings.php is only a runtime cache for selected app settings.": "Globale Einstellungen werden in der Datenbank gespeichert. config/settings.php ist nur ein Laufzeit-Cache für ausgewählte App-Einstellungen.",
|
||||||
|
|||||||
@@ -52,6 +52,9 @@
|
|||||||
"Choose how you want to sign in": "Choose how you want to sign in",
|
"Choose how you want to sign in": "Choose how you want to sign in",
|
||||||
"Continue": "Continue",
|
"Continue": "Continue",
|
||||||
"Use different email": "Use different email",
|
"Use different email": "Use different email",
|
||||||
|
"Encrypted connection": "Encrypted",
|
||||||
|
"HTTPS/TLS 1.2+ in transit": "HTTPS/TLS 1.2+ in transit",
|
||||||
|
"Problems logging in": "Problems logging in",
|
||||||
"We could not continue with these login details": "We could not continue with these login details",
|
"We could not continue with these login details": "We could not continue with these login details",
|
||||||
"Too many login attempts. Please wait and try again.": "Too many login attempts. Please wait and try again.",
|
"Too many login attempts. Please wait and try again.": "Too many login attempts. Please wait and try again.",
|
||||||
"No login method is available for this tenant": "No login method is available for this tenant",
|
"No login method is available for this tenant": "No login method is available for this tenant",
|
||||||
|
|||||||
@@ -36,8 +36,5 @@ $errorNoticeId = !empty($errors) ? 'auth-forgot-error-notice' : '';
|
|||||||
<button type="submit"><?php e(t('Send code')); ?></button>
|
<button type="submit"><?php e(t('Send code')); ?></button>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
<p class="text-align-center">
|
|
||||||
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
|
|
||||||
</p>
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ $selectedTenantId = (int) ($selectedTenantId ?? 0);
|
|||||||
$selectedTenant = is_array($selectedTenant ?? null) ? $selectedTenant : null;
|
$selectedTenant = is_array($selectedTenant ?? null) ? $selectedTenant : null;
|
||||||
$selectedTenantMethods = is_array($selectedTenantMethods ?? null) ? $selectedTenantMethods : ['local' => false, 'microsoft' => false];
|
$selectedTenantMethods = is_array($selectedTenantMethods ?? null) ? $selectedTenantMethods : ['local' => false, 'microsoft' => false];
|
||||||
$selectedTenantSlug = (string) ($selectedTenant['slug'] ?? '');
|
$selectedTenantSlug = (string) ($selectedTenant['slug'] ?? '');
|
||||||
|
$selectedTenantLabel = trim((string) ($selectedTenant['description'] ?? ''));
|
||||||
|
if ($selectedTenantLabel === '') {
|
||||||
|
$selectedTenantLabel = t('Select tenant');
|
||||||
|
}
|
||||||
|
$selectedTenantAvatarUrl = (string) ($selectedTenant['avatar_url'] ?? '');
|
||||||
|
$selectedTenantInitial = strtoupper(substr($selectedTenantLabel, 0, 1));
|
||||||
|
if ($selectedTenantInitial === '') {
|
||||||
|
$selectedTenantInitial = '?';
|
||||||
|
}
|
||||||
$errors = is_array($errors ?? null) ? $errors : [];
|
$errors = is_array($errors ?? null) ? $errors : [];
|
||||||
$errorNoticeId = $errors ? 'auth-login-error-notice' : '';
|
$errorNoticeId = $errors ? 'auth-login-error-notice' : '';
|
||||||
$loginHeading = t('Login');
|
$loginHeading = t('Login');
|
||||||
@@ -38,15 +47,17 @@ if ($stage === 'resolve_email') {
|
|||||||
$loginHeading = t('Login options');
|
$loginHeading = t('Login options');
|
||||||
$loginSubheading = t('Choose how you want to sign in');
|
$loginSubheading = t('Choose how you want to sign in');
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<article>
|
<article class="login-card">
|
||||||
<header>
|
<header>
|
||||||
<hgroup>
|
<hgroup>
|
||||||
<h1><?php e($loginHeading); ?></h1>
|
<h1><?php e($loginHeading); ?></h1>
|
||||||
<p><?php e($loginSubheading); ?></p>
|
<p><?php e($loginSubheading); ?></p>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<?php if ($errors): ?>
|
<?php if ($errors): ?>
|
||||||
<div id="<?php e($errorNoticeId); ?>" class="notice" data-variant="error" role="alert" aria-live="assertive" tabindex="-1">
|
<div id="<?php e($errorNoticeId); ?>" class="notice" data-variant="error" role="alert" aria-live="assertive" tabindex="-1">
|
||||||
<ul>
|
<ul>
|
||||||
@@ -58,7 +69,7 @@ if ($stage === 'resolve_email') {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($stage === 'resolve_email'): ?>
|
<?php if ($stage === 'resolve_email'): ?>
|
||||||
<form method="post">
|
<form method="post" data-login-submit-lock="1">
|
||||||
<input type="hidden" name="stage" value="resolve_email">
|
<input type="hidden" name="stage" value="resolve_email">
|
||||||
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
||||||
<label for="email">
|
<label for="email">
|
||||||
@@ -71,12 +82,12 @@ if ($stage === 'resolve_email') {
|
|||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<?php elseif ($stage === 'choose_tenant'): ?>
|
<?php elseif ($stage === 'choose_tenant'): ?>
|
||||||
<form method="post" class="login-tenant-select-form">
|
<form method="post" class="login-tenant-select-form" data-login-submit-lock="1">
|
||||||
<input type="hidden" name="stage" value="choose_tenant">
|
<input type="hidden" name="stage" value="choose_tenant">
|
||||||
<input type="hidden" name="email" value="<?php e($email); ?>">
|
<input type="hidden" name="email" value="<?php e($email); ?>">
|
||||||
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
||||||
<fieldset class="login-tenant-fieldset">
|
<fieldset class="login-tenant-fieldset">
|
||||||
<legend class="login-tenant-select-label"><?php e(t('Which tenant do you want to sign in to?')); ?></legend>
|
<!-- <legend class="login-tenant-select-label"><?php e(t('Which tenant do you want to sign in to?')); ?></legend> -->
|
||||||
<div class="login-tenant-choice-grid">
|
<div class="login-tenant-choice-grid">
|
||||||
<?php foreach ($tenantCandidates as $tenantCandidate): ?>
|
<?php foreach ($tenantCandidates as $tenantCandidate): ?>
|
||||||
<?php
|
<?php
|
||||||
@@ -116,13 +127,47 @@ if ($stage === 'resolve_email') {
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="hidden" name="stage" value="reset">
|
<input type="hidden" name="stage" value="reset">
|
||||||
<p>
|
<p>
|
||||||
<button type="submit" class="secondary outline"><?php e(t('Back')); ?></button>
|
<button type="submit" class="secondary outline"><?php e(t('Use different email')); ?></button>
|
||||||
</p>
|
</p>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
<section class="login-tenant-context" role="status" aria-live="polite">
|
||||||
|
<div class="login-tenant-context-main">
|
||||||
|
<?php if ($selectedTenantAvatarUrl !== ''): ?>
|
||||||
|
<img class="login-tenant-context-avatar" src="<?php e($selectedTenantAvatarUrl); ?>" alt="" loading="lazy">
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="login-tenant-context-avatar-placeholder"><?php e($selectedTenantInitial); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="login-tenant-context-text">
|
||||||
|
<p class="login-tenant-context-name"><?php e($selectedTenantLabel); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="post" class="login-tenant-context-form">
|
||||||
|
<input type="hidden" name="stage" value="resolve_email">
|
||||||
|
<input type="hidden" name="email" value="<?php e($email); ?>">
|
||||||
|
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
||||||
|
<button type="submit" class="secondary outline"><?php e(t('Switch tenant')); ?></button>
|
||||||
|
<?php Session::getCsrfInput(); ?>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="login-methods-stack">
|
||||||
|
<?php if (!empty($selectedTenantMethods['microsoft']) && $selectedTenantSlug !== ''): ?>
|
||||||
|
<a class="primary login-microsoft-link"
|
||||||
|
href="<?php e(lurl('auth/microsoft/start?tenant=' . rawurlencode($selectedTenantSlug))); ?>">
|
||||||
|
<i class="bi bi-microsoft" aria-hidden="true"></i> <?php e(t('Sign in with Microsoft')); ?>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($selectedTenantMethods['local']) && !empty($selectedTenantMethods['microsoft'])): ?>
|
||||||
|
<div class="login-alt-separator" aria-hidden="true">
|
||||||
|
<span><?php e(t('Or')); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($selectedTenantMethods['local'])): ?>
|
<?php if (!empty($selectedTenantMethods['local'])): ?>
|
||||||
<form method="post">
|
<form method="post" class="login-password-form" data-login-submit-lock="1">
|
||||||
<input type="hidden" name="stage" value="login_password">
|
<input type="hidden" name="stage" value="login_password">
|
||||||
<input type="hidden" name="email" value="<?php e($email); ?>">
|
<input type="hidden" name="email" value="<?php e($email); ?>">
|
||||||
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
||||||
@@ -140,25 +185,10 @@ if ($stage === 'resolve_email') {
|
|||||||
<p>
|
<p>
|
||||||
<button type="submit"><?php e(t('Login')); ?></button>
|
<button type="submit"><?php e(t('Login')); ?></button>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Forgot password')); ?>?</a>
|
|
||||||
</p>
|
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (!empty($selectedTenantMethods['local']) && !empty($selectedTenantMethods['microsoft'])): ?>
|
|
||||||
<div class="login-alt-separator" aria-hidden="true">
|
|
||||||
<span><?php e(t('Or')); ?></span>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (!empty($selectedTenantMethods['microsoft']) && $selectedTenantSlug !== ''): ?>
|
|
||||||
<a class="primary login-microsoft-link"
|
|
||||||
href="<?php e(lurl('auth/microsoft/start?tenant=' . rawurlencode($selectedTenantSlug))); ?>">
|
|
||||||
<i class="bi bi-microsoft" aria-hidden="true"></i> <?php e(t('Sign in with Microsoft')); ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if (empty($selectedTenantMethods['local']) && empty($selectedTenantMethods['microsoft'])): ?>
|
<?php if (empty($selectedTenantMethods['local']) && empty($selectedTenantMethods['microsoft'])): ?>
|
||||||
<div class="notice" data-variant="warning" role="status" aria-live="polite">
|
<div class="notice" data-variant="warning" role="status" aria-live="polite">
|
||||||
@@ -171,14 +201,17 @@ if ($stage === 'resolve_email') {
|
|||||||
<input type="hidden" name="email" value="<?php e($email); ?>">
|
<input type="hidden" name="email" value="<?php e($email); ?>">
|
||||||
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
<input type="hidden" name="tenant_hint" value="<?php e($tenantHint); ?>">
|
||||||
<p>
|
<p>
|
||||||
<button type="submit" class="secondary outline"><?php e(t('Back')); ?></button>
|
<button type="submit" class="secondary outline"><?php e(t('Use different email')); ?></button>
|
||||||
</p>
|
</p>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (allowRegistration()): ?>
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
<hr>
|
<hr>
|
||||||
<p class="text-align-center"><?php e(t('No account yet')); ?>? <a href="<?php e(lurl('register')); ?>"><?php e(t('Register')); ?></a></p>
|
<p class="login-security-note">
|
||||||
<?php endif; ?>
|
<i class="bi bi-lock-fill" aria-hidden="true"></i>
|
||||||
|
<span><?php e(t('Encrypted connection')); ?></span>
|
||||||
|
</p>
|
||||||
|
<small class="login-security-note-detail muted"><?php e(t('HTTPS/TLS 1.2+ in transit')); ?></small>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ $passwordHints = is_array($passwordHints ?? null) ? $passwordHints : [];
|
|||||||
$errorNoticeId = !empty($error) ? 'auth-register-error-notice' : '';
|
$errorNoticeId = !empty($error) ? 'auth-register-error-notice' : '';
|
||||||
$passwordHintId = 'auth-register-password-hints';
|
$passwordHintId = 'auth-register-password-hints';
|
||||||
?>
|
?>
|
||||||
<a class="back_to_login small secondary outline" href="<?php e(lurl('login')); ?>"><i
|
|
||||||
class="bi bi-arrow-left" aria-hidden="true"></i> <?php e(t('Back to Login')); ?></a>
|
|
||||||
<article>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<hgroup>
|
<hgroup>
|
||||||
@@ -75,4 +73,5 @@ $passwordHintId = 'auth-register-password-hints';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -65,8 +65,5 @@ $passwordHintId = 'auth-reset-password-hints';
|
|||||||
<button type="submit"><?php e(t('Reset password')); ?></button>
|
<button type="submit"><?php e(t('Reset password')); ?></button>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
<p class="text-align-center">
|
|
||||||
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
|
|
||||||
</p>
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -42,10 +42,5 @@ $errorNoticeId = !empty($errors) ? 'auth-verify-error-notice' : '';
|
|||||||
<button type="submit"><?php e(t('Verify')); ?></button>
|
<button type="submit"><?php e(t('Verify')); ?></button>
|
||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
<p class="text-align-center">
|
|
||||||
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Send code again')); ?></a>
|
|
||||||
·
|
|
||||||
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
|
|
||||||
</p>
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -49,7 +49,5 @@ $errorNoticeId = !empty($errors) ? 'auth-verify-email-error-notice' : '';
|
|||||||
<?php Session::getCsrfInput(); ?>
|
<?php Session::getCsrfInput(); ?>
|
||||||
<button type="submit" class="secondary outline"><?php e(t('Send code again')); ?></button>
|
<button type="submit" class="secondary outline"><?php e(t('Send code again')); ?></button>
|
||||||
</form>
|
</form>
|
||||||
<p class="text-align-center login-secondary-action">
|
<?php require templatePath('partials/auth-help-links.phtml'); ?>
|
||||||
<a href="<?php e(lurl('login')); ?>"><?php e(t('Back to Login')); ?></a>
|
|
||||||
</p>
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ if ($bufferTitle !== '') {
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main class="login-main">
|
||||||
<div class="container-small">
|
<div class="container-small login-flash-container">
|
||||||
<?php require __DIR__ . '/partials/app-flash.phtml'; ?>
|
<?php require __DIR__ . '/partials/app-flash.phtml'; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="container-small">
|
<div class="container-small login-content-container">
|
||||||
<?php Buffer::get('html'); ?>
|
<?php Buffer::get('html'); ?>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
7
templates/partials/auth-help-links.phtml
Normal file
7
templates/partials/auth-help-links.phtml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<nav class="login-help-links" aria-label="<?php e(t('Support')); ?>">
|
||||||
|
<a href="<?php e(lurl('password/forgot')); ?>"><?php e(t('Forgot password')); ?></a>
|
||||||
|
<?php if (allowRegistration()): ?>
|
||||||
|
<a href="<?php e(lurl('register')); ?>"><?php e(t('Register')); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<a href="<?php e(lurl('imprint')); ?>"><?php e(t('Problems logging in')); ?></a>
|
||||||
|
</nav>
|
||||||
@@ -415,6 +415,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="light"] #gradient {
|
html[data-theme="light"] #gradient {
|
||||||
@@ -451,6 +452,7 @@
|
|||||||
transparent 40%
|
transparent 40%
|
||||||
);
|
);
|
||||||
background-blend-mode: normal, normal, normal, normal, normal, normal;
|
background-blend-mode: normal, normal, normal, normal, normal, normal;
|
||||||
|
opacity: 0.62;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="dark"] #gradient {
|
html[data-theme="dark"] #gradient {
|
||||||
@@ -536,7 +538,7 @@
|
|||||||
var(--c-9) var(--s-start-9),
|
var(--c-9) var(--s-start-9),
|
||||||
transparent var(--s-end-9)
|
transparent var(--s-end-9)
|
||||||
);
|
);
|
||||||
animation: ani-gradient 10s linear infinite alternate;
|
animation: ani-gradient 24s linear infinite alternate;
|
||||||
background-blend-mode:
|
background-blend-mode:
|
||||||
normal, normal, normal, normal, normal, normal, normal, normal, normal,
|
normal, normal, normal, normal, normal, normal, normal, normal, normal,
|
||||||
normal;
|
normal;
|
||||||
@@ -544,6 +546,38 @@
|
|||||||
contain: paint;
|
contain: paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-card header {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.login-main {
|
||||||
|
min-height: 100dvh;
|
||||||
|
display: grid;
|
||||||
|
align-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding-block: 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-flash-container:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content-container.container-small,
|
||||||
|
.login-flash-container.container-small {
|
||||||
|
width: min(370px, calc(100vw - 2rem));
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
border: 1px solid color-mix(in srgb, var(--app-border) 75%, transparent);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.back_to_login {
|
.back_to_login {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@@ -616,8 +650,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.login-tenant-choice-avatar {
|
.login-tenant-choice-avatar {
|
||||||
width: 8rem;
|
width: 5rem;
|
||||||
height: 3rem;
|
height: 2rem;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
object-position: left center;
|
object-position: left center;
|
||||||
@@ -637,13 +671,6 @@
|
|||||||
color: var(--app-contrast);
|
color: var(--app-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-tenant-choice-text {
|
|
||||||
color: var(--app-color);
|
|
||||||
font-weight: 600;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-alt-separator {
|
.login-alt-separator {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
@@ -667,7 +694,7 @@
|
|||||||
padding: 0 0.75rem;
|
padding: 0 0.75rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--app-muted);
|
color: var(--app-muted);
|
||||||
background: var(--app-surface);
|
background: var(--app-card-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-microsoft-link {
|
.login-microsoft-link {
|
||||||
@@ -676,14 +703,148 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-methods-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-password-form {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-password-form > p:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context {
|
||||||
|
border: 1px solid color-mix(in srgb, var(--app-border) 80%, transparent);
|
||||||
|
border-radius: var(--app-border-radius);
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--app-primary) 6%,
|
||||||
|
var(--app-card-background-color)
|
||||||
|
);
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-avatar,
|
||||||
|
.login-tenant-context-avatar-placeholder {
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 2.25rem;
|
||||||
|
border-radius: calc(var(--app-border-radius) * 0.7);
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: left center;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--app-card-background-color) 80%,
|
||||||
|
var(--app-primary) 20%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-avatar-placeholder {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--app-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-text {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--app-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-name {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-form {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-form button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-help-links {
|
||||||
|
margin: 1.75rem 0 0.25rem;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 0.95rem;
|
||||||
|
row-gap: 0.45rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-security-note {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-security-note i {
|
||||||
|
color: var(--app-primary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-security-note-detail {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form[aria-busy="true"] button[type="submit"],
|
||||||
|
button[data-submit-state="loading"] {
|
||||||
|
opacity: 0.75;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
.login-secondary-action {
|
.login-secondary-action {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html[data-theme="dark"] #gradient {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
main {
|
main.login-main {
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-tenant-context {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-tenant-context-form button {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,38 @@
|
|||||||
import './core/app-telemetry.js';
|
import './core/app-telemetry.js';
|
||||||
import './components/app-flash-auto-dismiss.js';
|
import './components/app-flash-auto-dismiss.js';
|
||||||
import './components/app-password-hints.js';
|
import './components/app-password-hints.js';
|
||||||
|
|
||||||
|
const focusPrimaryErrorNotice = () => {
|
||||||
|
const errorNotice = document.querySelector(
|
||||||
|
'.notice[data-variant="error"][role="alert"][tabindex="-1"]',
|
||||||
|
);
|
||||||
|
if (!(errorNotice instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
errorNotice.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const lockLoginSubmitButtons = () => {
|
||||||
|
const forms = document.querySelectorAll('form[data-login-submit-lock="1"]');
|
||||||
|
forms.forEach((form) => {
|
||||||
|
if (!(form instanceof HTMLFormElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.addEventListener('submit', () => {
|
||||||
|
if (form.dataset.submitted === '1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.dataset.submitted = '1';
|
||||||
|
form.setAttribute('aria-busy', 'true');
|
||||||
|
const submitButton = form.querySelector('button[type="submit"]');
|
||||||
|
if (!(submitButton instanceof HTMLButtonElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitButton.disabled = true;
|
||||||
|
submitButton.dataset.submitState = 'loading';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
focusPrimaryErrorNotice();
|
||||||
|
lockLoginSubmitButtons();
|
||||||
|
|||||||
Reference in New Issue
Block a user