Phase 21: onboarding flow and new game reset
Onboarding overlay: on DOMContentLoaded the dashboard calls GET /auth/status. If no profile exists, a full-screen setup card replaces the normal UI (header, nav, and main are hidden). The user enters a username and clicks Start Playing — POST /auth/local is called, then the overlay hides and the Club tab loads normally. Pressing Enter in the username field also submits. Validation: minimum 2 characters, error message inline below the button. Danger Zone in Settings tab: a red-bordered section with a Reset All Progress button. Clicking opens a confirmation modal that requires the user to type the word RESET before the action is enabled. On confirm, POST /auth/reset is called, a toast is shown, and the page reloads after 1.5 s — which triggers the onboarding flow again since the profile was deleted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+150
-6
@@ -247,6 +247,54 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ── Onboarding overlay (shown when no profile exists) ─────────────────── -->
|
||||
<div id="onboarding-overlay" style="display:none;position:fixed;inset:0;z-index:1000;
|
||||
background:#0d1117;align-items:center;justify-content:center">
|
||||
<div style="background:#161b22;border:1px solid #30363d;border-radius:12px;
|
||||
padding:40px 48px;max-width:460px;width:90%;text-align:center">
|
||||
<div style="font-size:3rem;margin-bottom:16px">⚽</div>
|
||||
<h1 style="margin:0 0 8px;font-size:1.6rem">Welcome to OpenFUT</h1>
|
||||
<p style="color:#8b949e;margin:0 0 28px;font-size:0.9rem">
|
||||
Your offline FIFA 23 FUT club. Set up your manager profile to get started.
|
||||
</p>
|
||||
<div style="text-align:left;margin-bottom:20px">
|
||||
<label style="display:block;font-size:0.8rem;color:#8b949e;margin-bottom:6px">Manager username</label>
|
||||
<input id="ob-username" type="text" placeholder="Enter your username"
|
||||
style="width:100%;box-sizing:border-box;background:#0d1117;border:1px solid #30363d;
|
||||
color:#f0f6fc;padding:10px 14px;border-radius:6px;font-size:1rem"
|
||||
onkeydown="if(event.key==='Enter')createProfile()" />
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="createProfile()"
|
||||
style="width:100%;padding:12px;font-size:1rem">
|
||||
Start Playing
|
||||
</button>
|
||||
<p id="ob-error" style="color:#f85149;font-size:0.82rem;margin:12px 0 0;display:none"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Reset confirmation modal ──────────────────────────────────────────── -->
|
||||
<div id="reset-modal" style="display:none;position:fixed;inset:0;z-index:900;
|
||||
background:rgba(0,0,0,0.75);align-items:center;justify-content:center">
|
||||
<div style="background:#161b22;border:1px solid #7f1d1d;border-radius:10px;
|
||||
padding:32px 36px;max-width:420px;width:90%">
|
||||
<h3 style="color:#f85149;margin:0 0 12px">Reset All Progress?</h3>
|
||||
<p style="color:#8b949e;font-size:0.85rem;margin:0 0 20px">
|
||||
This will permanently delete your profile, cards, coins, matches, achievements,
|
||||
and all other data. There is no undo.
|
||||
</p>
|
||||
<p style="font-size:0.85rem;margin:0 0 10px">
|
||||
Type <strong style="color:#f85149">RESET</strong> to confirm:
|
||||
</p>
|
||||
<input id="reset-confirm-input" type="text" placeholder="RESET"
|
||||
style="width:100%;box-sizing:border-box;background:#0d1117;border:1px solid #7f1d1d;
|
||||
color:#f0f6fc;padding:8px 12px;border-radius:6px;margin-bottom:16px" />
|
||||
<div style="display:flex;gap:10px;justify-content:flex-end">
|
||||
<button class="btn btn-secondary" onclick="closeResetModal()">Cancel</button>
|
||||
<button class="btn btn-danger" onclick="executeReset()">Reset Everything</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<header>
|
||||
<h1>⚽ OpenFUT Dashboard</h1>
|
||||
<span class="badge">Offline</span>
|
||||
@@ -436,6 +484,14 @@ h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em;
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:32px;border:1px solid #7f1d1d;border-radius:8px;padding:20px 24px;background:#0d1117">
|
||||
<h3 style="color:#f85149;margin:0 0 8px">Danger Zone</h3>
|
||||
<p style="color:#8b949e;font-size:0.85rem;margin:0 0 16px">
|
||||
Reset all progress — this permanently deletes your profile, club, cards, coins,
|
||||
matches, achievements, and all other data. This action cannot be undone.
|
||||
</p>
|
||||
<button class="btn btn-danger" onclick="showResetModal()">Reset All Progress</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Matches tab ───────────────────────────────────────────────────────── -->
|
||||
@@ -2092,6 +2148,90 @@ async function toggleEvent(eventId, activate, btn) {
|
||||
|
||||
// ── Notifications ─────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Onboarding & Reset ───────────────────────────────────────────────────────
|
||||
|
||||
async function checkOnboarding() {
|
||||
try {
|
||||
const s = await api('GET', '/auth/status');
|
||||
if (!s.has_profile) {
|
||||
showOnboarding();
|
||||
}
|
||||
} catch (_) {
|
||||
showOnboarding();
|
||||
}
|
||||
}
|
||||
|
||||
function showOnboarding() {
|
||||
const overlay = el('onboarding-overlay');
|
||||
overlay.style.display = 'flex';
|
||||
document.querySelector('header').style.display = 'none';
|
||||
document.querySelector('nav').style.display = 'none';
|
||||
document.querySelector('main').style.display = 'none';
|
||||
setTimeout(() => { const inp = el('ob-username'); if (inp) inp.focus(); }, 100);
|
||||
}
|
||||
|
||||
function hideOnboarding() {
|
||||
el('onboarding-overlay').style.display = 'none';
|
||||
document.querySelector('header').style.display = '';
|
||||
document.querySelector('nav').style.display = '';
|
||||
document.querySelector('main').style.display = '';
|
||||
}
|
||||
|
||||
async function createProfile() {
|
||||
const username = (el('ob-username').value || '').trim();
|
||||
if (!username) { showObError('Please enter a username.'); return; }
|
||||
if (username.length < 2) { showObError('Username must be at least 2 characters.'); return; }
|
||||
try {
|
||||
const btn = document.querySelector('#onboarding-overlay button');
|
||||
btn.disabled = true; btn.textContent = 'Creating…';
|
||||
await api('POST', '/auth/local', { username });
|
||||
hideOnboarding();
|
||||
loadClub();
|
||||
showTab_internal('club');
|
||||
} catch (e) {
|
||||
showObError(e.message || 'Failed to create profile.');
|
||||
const btn = document.querySelector('#onboarding-overlay button');
|
||||
btn.disabled = false; btn.textContent = 'Start Playing';
|
||||
}
|
||||
}
|
||||
|
||||
function showObError(msg) {
|
||||
const err = el('ob-error');
|
||||
if (err) { err.textContent = msg; err.style.display = 'block'; }
|
||||
}
|
||||
|
||||
function showTab_internal(name) {
|
||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||
document.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
|
||||
const panel = el('tab-' + name);
|
||||
if (panel) panel.classList.add('active');
|
||||
const btn = document.querySelector(`nav button[onclick="showTab('${name}')"]`);
|
||||
if (btn) btn.classList.add('active');
|
||||
}
|
||||
|
||||
function showResetModal() {
|
||||
el('reset-confirm-input').value = '';
|
||||
el('reset-modal').style.display = 'flex';
|
||||
setTimeout(() => el('reset-confirm-input').focus(), 50);
|
||||
}
|
||||
|
||||
function closeResetModal() {
|
||||
el('reset-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
async function executeReset() {
|
||||
if (el('reset-confirm-input').value.trim() !== 'RESET') {
|
||||
showToast('Type RESET exactly to confirm.', true);
|
||||
return;
|
||||
}
|
||||
closeResetModal();
|
||||
try {
|
||||
await api('POST', '/auth/reset');
|
||||
showToast('Progress reset. Reloading…');
|
||||
setTimeout(() => window.location.reload(), 1500);
|
||||
} catch (e) { showToast(e.message, true); }
|
||||
}
|
||||
|
||||
// ── Achievements ─────────────────────────────────────────────────────────────
|
||||
|
||||
const ACH_RARITY_COLOR = { common: '#8b949e', rare: '#58a6ff', epic: '#bc8cff', legendary: '#f5a623' };
|
||||
@@ -2220,12 +2360,16 @@ async function markAllNotificationsRead() {
|
||||
// ── Init ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
loadClub();
|
||||
// Load notification badge count silently
|
||||
api('GET', '/notifications').then(d => {
|
||||
const unread = d.unread_count ?? (d.notifications || []).filter(n => !n.is_read).length;
|
||||
el('notif-badge').textContent = unread > 0 ? ` (${unread})` : '';
|
||||
}).catch(() => {});
|
||||
checkOnboarding().then(() => {
|
||||
// Only load club data if a profile exists (overlay hides header/nav if not)
|
||||
if (el('onboarding-overlay').style.display === 'none') {
|
||||
loadClub();
|
||||
api('GET', '/notifications').then(d => {
|
||||
const unread = d.unread_count ?? (d.notifications || []).filter(n => !n.is_read).length;
|
||||
el('notif-badge').textContent = unread > 0 ? ` (${unread})` : '';
|
||||
}).catch(() => {});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user