feat: add Google Sign-In with JWT auth and Neon DB persistence
This commit is contained in:
@@ -25,6 +25,9 @@ const els = {
|
||||
status: document.querySelector("#status-line"),
|
||||
error: document.querySelector("#error-line"),
|
||||
title: document.querySelector("#session-title"),
|
||||
gSignIn: document.querySelector(".g_id_signin"),
|
||||
userInfo: document.querySelector("#user-info"),
|
||||
logoutButton: document.querySelector("#logout-button"),
|
||||
};
|
||||
|
||||
const readinessClassMap = {
|
||||
@@ -359,11 +362,48 @@ function evidenceBlock(evidence = []) {
|
||||
return `<section><h2>Evidence</h2><ul class="small-list">${evidence.map((item) => `<li>${escapeHTML(item.quote || item.id)}</li>`).join("")}</ul></section>`;
|
||||
}
|
||||
|
||||
window.handleCredentialResponse = async (response) => {
|
||||
try {
|
||||
const res = await request("/api/v1/auth/google", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ id_token: response.credential }),
|
||||
});
|
||||
localStorage.setItem("tutor_token", res.token);
|
||||
localStorage.setItem("tutor_user", JSON.stringify(res.user));
|
||||
setStatus(`Signed in as ${res.user.email}`);
|
||||
renderAuth();
|
||||
} catch (err) {
|
||||
showError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
function renderAuth() {
|
||||
const user = JSON.parse(localStorage.getItem("tutor_user") || "null");
|
||||
const token = localStorage.getItem("tutor_token");
|
||||
if (user && token) {
|
||||
els.gSignIn.style.display = "none";
|
||||
els.userInfo.style.display = "block";
|
||||
els.userInfo.textContent = user.email;
|
||||
els.logoutButton.style.display = "inline-block";
|
||||
} else {
|
||||
els.gSignIn.style.display = "block";
|
||||
els.userInfo.style.display = "none";
|
||||
els.logoutButton.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
els.logoutButton.addEventListener("click", () => {
|
||||
localStorage.removeItem("tutor_token");
|
||||
localStorage.removeItem("tutor_user");
|
||||
renderAuth();
|
||||
setStatus("Signed out");
|
||||
});
|
||||
|
||||
async function request(url, options = {}) {
|
||||
const response = await fetch(url, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...options,
|
||||
});
|
||||
const token = localStorage.getItem("tutor_token");
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||
const response = await fetch(url, { headers, ...options });
|
||||
const body = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(body.error || `Request failed: ${response.status}`);
|
||||
@@ -398,3 +438,5 @@ function escapeHTML(value) {
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
}
|
||||
|
||||
renderAuth();
|
||||
|
||||
@@ -7,12 +7,20 @@
|
||||
<link rel="stylesheet" href="/assets/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
<main class="workspace">
|
||||
<aside class="setup-pane" aria-label="Diagnostic setup">
|
||||
<p class="eyebrow">Tutor Platform</p>
|
||||
<h1>Interview practice</h1>
|
||||
<p class="lede">Start a focused backend interview loop and turn one answer into evidence.</p>
|
||||
|
||||
<div id="auth-area" class="auth-area">
|
||||
<div id="g_id_onload" data-client_id="13671390758-bp1ed6psn43bl86r8a9kv81o40nkea90.apps.googleusercontent.com" data-callback="handleCredentialResponse"></div>
|
||||
<div class="g_id_signin" data-type="standard"></div>
|
||||
<div id="user-info" class="user-info" style="display:none;"></div>
|
||||
<button id="logout-button" class="small-button" type="button" style="display:none;">Sign out</button>
|
||||
</div>
|
||||
|
||||
<form id="session-form" class="stacked-form">
|
||||
<label>
|
||||
User ID
|
||||
|
||||
Reference in New Issue
Block a user