CSSの最適化や内部挙動の改良
This commit is contained in:
@@ -2,94 +2,42 @@
|
||||
|
||||
{{define "head"}}
|
||||
<style>
|
||||
@keyframes pulse-bg {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #ffe69c;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-bg-danger {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #f5c2c7;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink-banner {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.anxiety-warning {
|
||||
animation: pulse-bg 2s infinite;
|
||||
}
|
||||
|
||||
.anxiety-danger {
|
||||
animation: pulse-bg-danger 1s infinite;
|
||||
}
|
||||
|
||||
.urgent-banner {
|
||||
z-index: 1030;
|
||||
animation: blink-banner 1s infinite;
|
||||
}
|
||||
|
||||
.urgent-banner-danger {
|
||||
background: linear-gradient(90deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.urgent-banner-warning {
|
||||
background: linear-gradient(90deg, #fd7e14, #e06c00);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.urgent-countdown {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dashboard-stat-card {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dashboard-stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.urgent-banner-danger {
|
||||
background: linear-gradient(90deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
}
|
||||
.urgent-banner-warning {
|
||||
background: linear-gradient(90deg, #fd7e14, #e06c00);
|
||||
color: white;
|
||||
}
|
||||
.urgent-countdown {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<div id="urgentBanner" class="urgent-banner py-3 text-center d-none">
|
||||
<div class="container">
|
||||
<i class="bi bi-exclamation-octagon-fill me-2"></i>
|
||||
<div id="urgentBanner" class="urgent-banner py-3 text-center d-none" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="container position-relative">
|
||||
<i class="bi bi-exclamation-octagon-fill me-2" aria-hidden="true"></i>
|
||||
<span id="urgentMessage"></span>
|
||||
<div class="urgent-countdown mt-1">
|
||||
<i class="bi bi-stopwatch"></i> <span id="urgentCountdown"></span>
|
||||
<i class="bi bi-stopwatch" aria-hidden="true"></i> <span id="urgentCountdown"></span>
|
||||
</div>
|
||||
<button type="button" id="closeBanner" class="btn-close btn-close-white position-absolute top-0 end-0" aria-label="バナーを閉じる" onclick="document.getElementById('urgentBanner').classList.add('d-none')"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="mb-4"><i class="bi bi-house-door me-2"></i>ダッシュボード</h1>
|
||||
<h1 class="mb-4"><i class="bi bi-house-door me-2" aria-hidden="true"></i>ダッシュボード</h1>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-6 col-md-3">
|
||||
@@ -101,7 +49,7 @@
|
||||
<h6 class="text-white-50">未完了の課題</h6>
|
||||
<h2 class="mb-0">{{.stats.TotalPending}}</h2>
|
||||
</div>
|
||||
<i class="bi bi-list-task display-4 opacity-50"></i>
|
||||
<i class="bi bi-list-task display-4 opacity-50" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,7 +64,7 @@
|
||||
<h6 class="text-dark-50">今日が期限</h6>
|
||||
<h2 class="mb-0">{{.stats.DueToday}}</h2>
|
||||
</div>
|
||||
<i class="bi bi-calendar-event display-4 opacity-50"></i>
|
||||
<i class="bi bi-calendar-event display-4 opacity-50" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,7 +79,7 @@
|
||||
<h6 class="text-white-50">今週が期限</h6>
|
||||
<h2 class="mb-0">{{.stats.DueThisWeek}}</h2>
|
||||
</div>
|
||||
<i class="bi bi-calendar-week display-4 opacity-50"></i>
|
||||
<i class="bi bi-calendar-week display-4 opacity-50" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,7 +94,7 @@
|
||||
<h6 class="text-white-50">期限切れ</h6>
|
||||
<h2 class="mb-0">{{.stats.Overdue}}</h2>
|
||||
</div>
|
||||
<i class="bi bi-exclamation-triangle display-4 opacity-50"></i>
|
||||
<i class="bi bi-exclamation-triangle display-4 opacity-50" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,20 +106,22 @@
|
||||
{{if .overdue}}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-danger">
|
||||
<div class="card-header bg-danger text-white"><i class="bi bi-exclamation-triangle me-2"></i>期限切れの課題</div>
|
||||
<div class="card-header bg-danger text-white"><i class="bi bi-exclamation-triangle me-2" aria-hidden="true"></i>期限切れの課題</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
{{range .overdue}}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center"
|
||||
data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<div>
|
||||
{{if .Subject}}<span class="badge bg-secondary me-1">{{.Subject}}</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">重要</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-danger">{{formatDateTime .DueDate}}</small>
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST"><input type="hidden" name="_csrf"
|
||||
value="{{$.csrfToken}}"><button type="submit" class="btn btn-sm btn-success"
|
||||
title="完了にする"><i class="bi bi-check-lg"></i></button></form>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
<button type="submit" class="btn btn-sm btn-success" aria-label="{{.Title}}を完了にする">
|
||||
<i class="bi bi-check-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -181,20 +131,22 @@
|
||||
{{if .dueToday}}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-warning">
|
||||
<div class="card-header bg-warning text-dark"><i class="bi bi-calendar-event me-2"></i>今日が期限</div>
|
||||
<div class="card-header bg-warning text-dark"><i class="bi bi-calendar-event me-2" aria-hidden="true"></i>今日が期限</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
{{range .dueToday}}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center"
|
||||
data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<div>
|
||||
{{if .Subject}}<span class="badge bg-secondary me-1">{{.Subject}}</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">重要</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-muted">{{formatDateTime .DueDate}}</small>
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST"><input type="hidden" name="_csrf"
|
||||
value="{{$.csrfToken}}"><button type="submit" class="btn btn-sm btn-success"
|
||||
title="完了にする"><i class="bi bi-check-lg"></i></button></form>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
<button type="submit" class="btn btn-sm btn-success" aria-label="{{.Title}}を完了にする">
|
||||
<i class="bi bi-check-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -204,20 +156,22 @@
|
||||
{{if .upcoming}}
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-info">
|
||||
<div class="card-header bg-info text-white"><i class="bi bi-calendar-week me-2"></i>今週の課題</div>
|
||||
<div class="card-header bg-info text-white"><i class="bi bi-calendar-week me-2" aria-hidden="true"></i>今週の課題</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
{{range .upcoming}}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center"
|
||||
data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center" data-priority="{{.Priority}}" data-due="{{.DueDate.UnixMilli}}">
|
||||
<div>
|
||||
{{if .Subject}}<span class="badge bg-secondary me-1">{{.Subject}}</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">重要</span>{{end}}
|
||||
{{if eq .Priority "high"}}<span class="badge bg-danger me-1">高<span class="visually-hidden">(重要度:高)</span></span>{{end}}
|
||||
<strong>{{.Title}}</strong>
|
||||
<br><small class="text-muted">{{formatDateTime .DueDate}}</small>
|
||||
</div>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST"><input type="hidden" name="_csrf"
|
||||
value="{{$.csrfToken}}"><button type="submit" class="btn btn-sm btn-success"
|
||||
title="完了にする"><i class="bi bi-check-lg"></i></button></form>
|
||||
<form action="/assignments/{{.ID}}/toggle" method="POST">
|
||||
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
|
||||
<button type="submit" class="btn btn-sm btn-success" aria-label="{{.Title}}を完了にする">
|
||||
<i class="bi bi-check-lg" aria-hidden="true"></i>
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
@@ -228,10 +182,10 @@
|
||||
|
||||
{{if and (not .overdue) (not .dueToday) (not .upcoming)}}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-emoji-smile display-1 text-success"></i>
|
||||
<i class="bi bi-emoji-smile display-1 text-success" aria-hidden="true"></i>
|
||||
<h3 class="mt-3">今週の課題はありません!</h3>
|
||||
<p class="text-muted">新しい課題を登録しましょう</p>
|
||||
<a href="/assignments/new" class="btn btn-primary"><i class="bi bi-plus-circle me-1"></i>課題を登録</a>
|
||||
<a href="/assignments/new" class="btn btn-primary"><i class="bi bi-plus-circle me-1" aria-hidden="true"></i>課題を登録</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -239,20 +193,20 @@
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
(function () {
|
||||
var banner = document.getElementById('urgentBanner');
|
||||
var message = document.getElementById('urgentMessage');
|
||||
var banner = document.getElementById('urgentBanner');
|
||||
var message = document.getElementById('urgentMessage');
|
||||
var countdown = document.getElementById('urgentCountdown');
|
||||
var body = document.body;
|
||||
|
||||
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
var items = document.querySelectorAll('[data-priority="high"][data-due]');
|
||||
var mostUrgent = null;
|
||||
var mostUrgentDue = Infinity;
|
||||
|
||||
items.forEach(function (item) {
|
||||
var due = parseInt(item.dataset.due);
|
||||
var now = Date.now();
|
||||
var due = parseInt(item.dataset.due);
|
||||
var now = Date.now();
|
||||
var diff = due - now;
|
||||
|
||||
if (diff > 0 && diff < mostUrgentDue) {
|
||||
mostUrgentDue = diff;
|
||||
var titleEl = item.querySelector('strong');
|
||||
@@ -260,59 +214,47 @@
|
||||
}
|
||||
});
|
||||
|
||||
var hasOverdueHigh = false;
|
||||
var overdueItems = document.querySelectorAll('[data-priority="high"]');
|
||||
overdueItems.forEach(function (item) {
|
||||
var hasOverdueHigh = Array.from(document.querySelectorAll('[data-priority="high"]')).some(function(item) {
|
||||
var due = parseInt(item.dataset.due);
|
||||
if (due && due < Date.now()) {
|
||||
hasOverdueHigh = true;
|
||||
}
|
||||
return due && due < Date.now();
|
||||
});
|
||||
|
||||
if (hasOverdueHigh) {
|
||||
banner.classList.remove('d-none');
|
||||
banner.classList.add('urgent-banner-danger');
|
||||
message.innerHTML = '🚨 <strong>期限切れの重要課題があります!</strong>';
|
||||
if (!reduced) banner.classList.add('anxiety-danger');
|
||||
message.textContent = '期限切れの重要課題があります!';
|
||||
countdown.textContent = '今すぐ対応してください!';
|
||||
body.classList.add('anxiety-danger');
|
||||
} else if (mostUrgent && mostUrgentDue < 24 * 60 * 60 * 1000) {
|
||||
banner.classList.remove('d-none');
|
||||
banner.classList.add('urgent-banner-danger');
|
||||
message.innerHTML = '🚨 <strong>「' + mostUrgent.title + '」の期限が迫っています!</strong>';
|
||||
body.classList.add('anxiety-danger');
|
||||
if (!reduced) banner.classList.add('anxiety-danger');
|
||||
message.textContent = '「' + mostUrgent.title + '」の期限が迫っています!';
|
||||
updateCountdown();
|
||||
setInterval(updateCountdown, 1000);
|
||||
if (!reduced) setInterval(updateCountdown, 1000);
|
||||
} else if (mostUrgent && mostUrgentDue < 3 * 24 * 60 * 60 * 1000) {
|
||||
banner.classList.remove('d-none');
|
||||
banner.classList.add('urgent-banner-warning');
|
||||
message.innerHTML = '⚠️ <strong>「' + mostUrgent.title + '」の期限が近づいています</strong>';
|
||||
body.classList.add('anxiety-warning');
|
||||
if (!reduced) banner.classList.add('anxiety-warning');
|
||||
message.textContent = '「' + mostUrgent.title + '」の期限が近づいています';
|
||||
updateCountdown();
|
||||
setInterval(updateCountdown, 1000);
|
||||
if (!reduced) setInterval(updateCountdown, 60000);
|
||||
}
|
||||
|
||||
function updateCountdown() {
|
||||
if (!mostUrgent) return;
|
||||
var now = Date.now();
|
||||
var diff = mostUrgent.due - now;
|
||||
|
||||
if (diff <= 0) {
|
||||
countdown.textContent = '期限切れ!';
|
||||
return;
|
||||
}
|
||||
|
||||
var days = Math.floor(diff / 86400000);
|
||||
var diff = mostUrgent.due - Date.now();
|
||||
if (diff <= 0) { countdown.textContent = '期限切れ!'; return; }
|
||||
var days = Math.floor(diff / 86400000);
|
||||
var hours = Math.floor((diff % 86400000) / 3600000);
|
||||
var mins = Math.floor((diff % 3600000) / 60000);
|
||||
var secs = Math.floor((diff % 60000) / 1000);
|
||||
|
||||
var text = 'あと ';
|
||||
var mins = Math.floor((diff % 3600000) / 60000);
|
||||
var secs = Math.floor((diff % 60000) / 1000);
|
||||
var text = 'あと ';
|
||||
if (days > 0) text += days + '日 ' + hours + '時間 ' + mins + '分 ' + secs + '秒';
|
||||
else if (hours > 0) text += hours + '時間 ' + mins + '分 ' + secs + '秒';
|
||||
else text += mins + '分 ' + secs + '秒';
|
||||
|
||||
countdown.textContent = text;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-exclamation-triangle display-1 text-danger"></i>
|
||||
<div class="text-center py-5" role="main">
|
||||
<i class="bi bi-exclamation-triangle display-1 text-danger" aria-hidden="true"></i>
|
||||
<h1 class="mt-4">{{.title}}</h1>
|
||||
<p class="lead text-muted">{{.message}}</p>
|
||||
<a href="/" class="btn btn-primary mt-3"><i class="bi bi-house-door me-1"></i>ダッシュボードに戻る</a>
|
||||
<p class="text-muted small">問題が続く場合はページを再読み込みするか、最初からやり直してください。</p>
|
||||
<div class="d-flex justify-content-center gap-2 mt-3">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="location.reload()">
|
||||
<i class="bi bi-arrow-clockwise me-1" aria-hidden="true"></i>再読み込み
|
||||
</button>
|
||||
<a href="/" class="btn btn-primary">
|
||||
<i class="bi bi-house-door me-1" aria-hidden="true"></i>ダッシュボードに戻る
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{{define "content"}}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="mb-4"><i class="bi bi-person me-2"></i>プロフィール</h1>
|
||||
<h1 class="mb-4"><i class="bi bi-person me-2" aria-hidden="true"></i>プロフィール</h1>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
@@ -11,25 +11,23 @@
|
||||
<h5 class="mb-0">アカウント情報</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .error}}<div class="alert alert-danger">{{.error}}</div>{{end}}
|
||||
{{if .success}}<div class="alert alert-success">{{.success}}</div>{{end}}
|
||||
{{if .error}}<div class="alert alert-danger" role="alert">{{.error}}</div>{{end}}
|
||||
{{if .success}}<div class="alert alert-success" role="status">{{.success}}</div>{{end}}
|
||||
<form method="POST" action="/profile">
|
||||
{{.csrfField}}
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">メールアドレス</label>
|
||||
<input type="email" class="form-control" id="email" value="{{.user.Email}}" disabled>
|
||||
<p class="form-label mb-1 text-muted small">メールアドレス</p>
|
||||
<p class="mb-0 fw-bold">{{.user.Email}}</p>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">名前</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{.user.Name}}"
|
||||
required>
|
||||
<input type="text" class="form-control" id="name" name="name" value="{{.user.Name}}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">ロール</label>
|
||||
<input type="text" class="form-control"
|
||||
value="{{if eq .user.Role `admin`}}管理者{{else}}ユーザー{{end}}" disabled>
|
||||
<p class="form-label mb-1 text-muted small">ロール</p>
|
||||
<p class="mb-0 fw-bold">{{if eq .user.Role "admin"}}管理者{{else}}ユーザー{{end}}</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i>更新</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1" aria-hidden="true"></i>更新</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,111 +38,101 @@
|
||||
<h5 class="mb-0">パスワード変更</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .passwordError}}<div class="alert alert-danger">{{.passwordError}}</div>{{end}}
|
||||
{{if .passwordSuccess}}<div class="alert alert-success">{{.passwordSuccess}}</div>{{end}}
|
||||
{{if .passwordError}}<div class="alert alert-danger" role="alert">{{.passwordError}}</div>{{end}}
|
||||
{{if .passwordSuccess}}<div class="alert alert-success" role="status">{{.passwordSuccess}}</div>{{end}}
|
||||
<form method="POST" action="/profile/password">
|
||||
{{.csrfField}}
|
||||
<div class="mb-3">
|
||||
<label for="old_password" class="form-label">現在のパスワード</label>
|
||||
<input type="password" class="form-control" id="old_password" name="old_password"
|
||||
required>
|
||||
<input type="password" class="form-control" id="old_password" name="old_password" required autocomplete="current-password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">新しいパスワード</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password"
|
||||
required minlength="6">
|
||||
<div class="form-text">6文字以上</div>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password" required minlength="8" autocomplete="new-password">
|
||||
<div class="form-text">8文字以上</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">新しいパスワード(確認)</label>
|
||||
<input type="password" class="form-control" id="confirm_password"
|
||||
name="confirm_password" required>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required minlength="8" autocomplete="new-password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning"><i class="bi bi-key me-1"></i>パスワード変更</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-key me-1" aria-hidden="true"></i>パスワード変更</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2段階認証設定 -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-shield-lock me-2"></i>2段階認証(2FA)</h5>
|
||||
<h5 class="mb-0"><i class="bi bi-shield-lock me-2" aria-hidden="true"></i>2段階認証(2FA)</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .totpError}}<div class="alert alert-danger">{{.totpError}}</div>{{end}}
|
||||
{{if .totpSuccess}}<div class="alert alert-success">{{.totpSuccess}}</div>{{end}}
|
||||
{{if .totpError}}<div class="alert alert-danger" role="alert">{{.totpError}}</div>{{end}}
|
||||
{{if .totpSuccess}}<div class="alert alert-success" role="status">{{.totpSuccess}}</div>{{end}}
|
||||
{{if .user.TOTPEnabled}}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<span class="badge bg-success me-2"><i class="bi bi-check-lg me-1"></i>有効</span>
|
||||
<span class="badge bg-success me-2"><i class="bi bi-check-lg me-1" aria-hidden="true"></i>有効</span>
|
||||
<span class="text-muted">2段階認証が有効になっています</span>
|
||||
</div>
|
||||
<form method="POST" action="/profile/totp/disable">
|
||||
{{.csrfField}}
|
||||
<div class="mb-3">
|
||||
<label for="totp_disable_password" class="form-label">現在のパスワードを入力して無効化</label>
|
||||
<input type="password" class="form-control" id="totp_disable_password" name="password"
|
||||
placeholder="パスワード" required style="max-width:320px">
|
||||
<input type="password" class="form-control" id="totp_disable_password" name="password" placeholder="パスワード" required style="max-width:320px" autocomplete="current-password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-shield-x me-1"></i>2段階認証を無効化
|
||||
<i class="bi bi-shield-x me-1" aria-hidden="true"></i>2段階認証を無効化
|
||||
</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<span class="badge bg-secondary me-2"><i class="bi bi-x-lg me-1"></i>無効</span>
|
||||
<span class="badge bg-secondary me-2"><i class="bi bi-x-lg me-1" aria-hidden="true"></i>無効</span>
|
||||
<span class="text-muted">2段階認証が設定されていません</span>
|
||||
</div>
|
||||
<p class="text-muted small">2段階認証を有効にするとセキュリティが向上します。Google Authenticator などのアプリが必要です。</p>
|
||||
<a href="/profile/totp/setup" class="btn btn-primary">
|
||||
<i class="bi bi-shield-plus me-1"></i>2段階認証を設定する
|
||||
<i class="bi bi-shield-plus me-1" aria-hidden="true"></i>2段階認証を設定する
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知設定 -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-bell me-2"></i>通知設定</h5>
|
||||
<h5 class="mb-0"><i class="bi bi-bell me-2" aria-hidden="true"></i>通知設定</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .notifyError}}<div class="alert alert-danger">{{.notifyError}}</div>{{end}}
|
||||
{{if .notifySuccess}}<div class="alert alert-success">{{.notifySuccess}}</div>{{end}}
|
||||
{{if .notifyError}}<div class="alert alert-danger" role="alert">{{.notifyError}}</div>{{end}}
|
||||
{{if .notifySuccess}}<div class="alert alert-success" role="status">{{.notifySuccess}}</div>{{end}}
|
||||
<form method="POST" action="/profile/notifications">
|
||||
{{.csrfField}}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6 class="mb-3"><i class="bi bi-telegram me-1"></i>Telegram</h6>
|
||||
<h6 class="mb-3"><i class="bi bi-telegram me-1" aria-hidden="true"></i>Telegram</h6>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="telegram_enabled"
|
||||
name="telegram_enabled" {{if .notifySettings.TelegramEnabled}}checked{{end}}>
|
||||
<input class="form-check-input" type="checkbox" id="telegram_enabled" name="telegram_enabled" {{if .notifySettings.TelegramEnabled}}checked{{end}}>
|
||||
<label class="form-check-label" for="telegram_enabled">Telegram通知を有効化</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="telegram_chat_id" class="form-label">Chat ID</label>
|
||||
<input type="text" class="form-control" id="telegram_chat_id" name="telegram_chat_id"
|
||||
value="{{.notifySettings.TelegramChatID}}" placeholder="例: 123456789">
|
||||
<input type="text" class="form-control" id="telegram_chat_id" name="telegram_chat_id" value="{{.notifySettings.TelegramChatID}}" placeholder="例: 123456789">
|
||||
<div class="form-text">
|
||||
Botに<code>/start</code>を送信後、<a href="https://t.me/userinfobot"
|
||||
target="_blank">@userinfobot</a>でIDを確認
|
||||
Botに<code>/start</code>を送信後、<a href="https://t.me/userinfobot" target="_blank" rel="noopener noreferrer">@userinfobot</a>でIDを確認
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-3">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="notify_on_create" name="notify_on_create"
|
||||
{{if .notifySettings.NotifyOnCreate}}checked{{end}}>
|
||||
<input class="form-check-input" type="checkbox" id="notify_on_create" name="notify_on_create" {{if .notifySettings.NotifyOnCreate}}checked{{end}}>
|
||||
<label class="form-check-label" for="notify_on_create">
|
||||
<i class="bi bi-plus-circle me-1"></i>課題追加時に通知する
|
||||
<i class="bi bi-plus-circle me-1" aria-hidden="true"></i>課題追加時に通知する
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i>通知設定を保存</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1" aria-hidden="true"></i>通知設定を保存</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
<div class="col-md-7 col-lg-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-shield-lock me-2"></i>2段階認証の設定</h5>
|
||||
<h5 class="mb-0"><i class="bi bi-shield-lock me-2" aria-hidden="true"></i>2段階認証の設定</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
{{if .error}}
|
||||
<div class="alert alert-danger">{{.error}}</div>
|
||||
<div class="alert alert-danger" role="alert">{{.error}}</div>
|
||||
{{end}}
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
<div class="alert alert-info" role="note">
|
||||
<i class="bi bi-info-circle me-1" aria-hidden="true"></i>
|
||||
Google Authenticator、Authy などの認証アプリを使用してください。
|
||||
</div>
|
||||
|
||||
@@ -25,17 +25,16 @@
|
||||
</ol>
|
||||
|
||||
<div class="text-center mb-4">
|
||||
<img src="data:image/png;base64,{{.qrCode}}" alt="QRコード" class="border rounded"
|
||||
style="max-width:200px">
|
||||
<img src="data:image/png;base64,{{.qrCode}}" alt="2段階認証設定用QRコード" class="border rounded" style="max-width:200px">
|
||||
<p class="text-muted small mt-2">QRコードを再スキャンしたい場合はページを再読み込みしてください。</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">シークレットキー(手動入力の場合)</label>
|
||||
<label class="form-label fw-bold" for="secretKey">シークレットキー(手動入力の場合)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control font-monospace" id="secretKey" value="{{.secret}}"
|
||||
readonly>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="copySecret()" title="コピー">
|
||||
<i class="bi bi-clipboard"></i>
|
||||
<input type="text" class="form-control font-monospace" id="secretKey" value="{{.secret}}" readonly aria-label="シークレットキー">
|
||||
<button class="btn btn-outline-secondary" type="button" id="copySecretBtn" aria-label="シークレットキーをコピー">
|
||||
<i class="bi bi-clipboard" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">認証アプリで「手動入力」を選択し、このキーを入力してください。</div>
|
||||
@@ -45,19 +44,16 @@
|
||||
{{.csrfField}}
|
||||
<div class="mb-3">
|
||||
<label for="totp_password" class="form-label fw-bold">現在のパスワード</label>
|
||||
<input type="password" class="form-control" id="totp_password" name="password"
|
||||
placeholder="パスワードを入力" required>
|
||||
<input type="password" class="form-control" id="totp_password" name="password" placeholder="パスワードを入力" required autocomplete="current-password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="totp_code" class="form-label fw-bold">認証コードで確認</label>
|
||||
<input type="text" class="form-control form-control-lg text-center" id="totp_code"
|
||||
name="totp_code" placeholder="000000" maxlength="6" pattern="[0-9]{6}" inputmode="numeric"
|
||||
autocomplete="off" autofocus required>
|
||||
<input type="text" class="form-control form-control-lg text-center" id="totp_code" name="totp_code" placeholder="000000" maxlength="6" pattern="[0-9]{6}" inputmode="numeric" autocomplete="off" autofocus required>
|
||||
<div class="form-text">認証アプリに表示された6桁のコードを入力してください。</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-shield-check me-1"></i>有効化
|
||||
<i class="bi bi-shield-check me-1" aria-hidden="true"></i>有効化
|
||||
</button>
|
||||
<a href="/profile" class="btn btn-outline-secondary">キャンセル</a>
|
||||
</div>
|
||||
@@ -70,14 +66,23 @@
|
||||
|
||||
{{define "scripts"}}
|
||||
<script>
|
||||
function copySecret() {
|
||||
const el = document.getElementById('secretKey');
|
||||
el.select();
|
||||
navigator.clipboard.writeText(el.value).then(() => {
|
||||
const btn = el.nextElementSibling;
|
||||
btn.innerHTML = '<i class="bi bi-check-lg"></i>';
|
||||
setTimeout(() => { btn.innerHTML = '<i class="bi bi-clipboard"></i>'; }, 2000);
|
||||
document.getElementById('copySecretBtn').addEventListener('click', function() {
|
||||
var btn = this;
|
||||
var val = document.getElementById('secretKey').value;
|
||||
if (!navigator.clipboard) {
|
||||
showCopyFeedback('クリップボードがサポートされていません。手動でコピーしてください。');
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(val).then(function() {
|
||||
btn.innerHTML = '<i class="bi bi-check-lg" aria-hidden="true"></i>';
|
||||
btn.setAttribute('aria-label', 'コピーしました');
|
||||
setTimeout(function() {
|
||||
btn.innerHTML = '<i class="bi bi-clipboard" aria-hidden="true"></i>';
|
||||
btn.setAttribute('aria-label', 'シークレットキーをコピー');
|
||||
}, 2000);
|
||||
}).catch(function(err) {
|
||||
showCopyFeedback('コピーに失敗しました: ' + (err.message || err));
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user