This commit is contained in:
Maieul BOYER 2025-12-01 19:26:22 +01:00
parent e5b71a5cc1
commit c898fe8d32
No known key found for this signature in database
30 changed files with 1148 additions and 95 deletions

View file

@ -0,0 +1,66 @@
<div class="grid h-full place-items-center">
<div class="bg-white shadow-lg rounded-2xl p-8 w-full max-w-md">
<h1 class="text-2xl font-semibold mb-6 text-gray-700">Edit Profile</h1>
<div id="isGuestBox" class="border-red-600 rounded-2xl border-2" hidden>
<h2 class="text-2xl font-semibold text-red-600"> This is a guest Account</h2>
<span class="text-red-600"> You can't change anything here </span>
</div>
<!-- Login Name -->
<div id="loginNameWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Login Name</label>
<div id="loginNameBox" class="font-medium mb-1 text-gray-700 rounded-sm border-2 outline-lime-100"></div>
</div>
<!-- Display Name -->
<div id="displayNameWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Display Name</label>
<input id="displayNameBox" type="text" placeholder="Display Name" name="DisplayName"
class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button id="displayNameButton" class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
Update
</button>
</div>
<!-- Password -->
<div id="passwordWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Change Password</label>
<input id="passwordBox" type="password" placeholder="New Password" name="Password"
class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button id="passwordButton" class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
Update
</button>
</div>
<!-- TOTP -->
<div class="border rounded p-4">
<h2 class="font-semibold text-lg mb-2">Two-Factor Authentication (TOTP)</h2>
<div class="flex items-center justify-between">
<span id="totpStatusText" class="font-medium text-gray-700">Status: Disabled</span>
<div class="flex gap-2">
<button id="enableTotp" type="button"
class="bg-green-600 text-white-700 px-3 py-1 rounded hover:bg-green-700">
Enable
</button>
<button id="disableTotp" type="button"
class="bg-red-600 text-white-700 px-3 py-1 rounded hover:bg-red-700 hidden">
Disable
</button>
<button id="showSecret" type="button"
class="bg-blue-600 text-white-700 px-3 py-1 rounded hover:bg-blue-700 hidden">
Show Secret
</button>
</div>
</div>
<p id="totpSecretBox" class="mt-3 text-sm bg-gray-100 border p-2 rounded hidden"></p>
</div>
</div>
</div>

View file

@ -0,0 +1,159 @@
import { addRoute, navigateTo, setTitle } from "@app/routing";
import { showError } from "@app/toast";
import page from './profile.html?raw'
import { updateUser } from "@app/auth";
import { isNullish } from "@app/utils";
import client from "@app/api";
async function route(url: string, _args: { [k: string]: string }) {
setTitle('Edit Profile')
return {
html: page, postInsert: async (app: HTMLElement | undefined) => {
const user = await updateUser();
if (isNullish(user))
return showError('No User');
if (isNullish(app))
return showError('Failed to render');
let totpState = await (async () => {
let res = await client.statusOtp();
if (res.kind === "success")
return {
enabled: (res.msg as string) === "statusOtp.success.enabled",
secret: ((res.msg as string) === "statusOtp.success.enabled") ? res.payload.secret : null,
};
else {
showError('Failed to get OTP status')
return {
enabled: false, secret: null,
}
}
})()
// ---- Simulated State ----
let totpEnabled = totpState.enabled;
let totpSecret = totpState.secret; // would come from backend
let guestBox = app.querySelector<HTMLDivElement>("#isGuestBox")!;
let displayNameWrapper = app.querySelector<HTMLDivElement>("#displayNameWrapper")!;
let displayNameBox = app.querySelector<HTMLInputElement>("#displayNameBox")!;
let displayNameButton = app.querySelector<HTMLButtonElement>("#displayNameButton")!;
let loginNameWrapper = app.querySelector<HTMLDivElement>("#loginNameWrapper")!;
let loginNameBox = app.querySelector<HTMLDivElement>("#loginNameBox")!;
let passwordWrapper = app.querySelector<HTMLDivElement>("#passwordWrapper")!;
let passwordBox = app.querySelector<HTMLInputElement>("#passwordBox")!;
let passwordButton = app.querySelector<HTMLButtonElement>("#passwordButton")!;
if (!isNullish(user.selfInfo?.loginName))
loginNameBox.innerText = user.selfInfo?.loginName;
else
loginNameBox.innerHTML = '<span class="text-red-600 font-bold mb-1">You don\'t have a login name</span>';
displayNameBox.value = user.name;
guestBox.hidden = !user.guest;
// ---- DOM Elements ----
const totpStatusText = app.querySelector("#totpStatusText")!;
const enableBtn = app.querySelector<HTMLButtonElement>("#enableTotp")!;
const disableBtn = app.querySelector<HTMLButtonElement>("#disableTotp")!;
const showSecretBtn = app.querySelector<HTMLButtonElement>("#showSecret")!;
const secretBox = app.querySelector("#totpSecretBox")!;
if (user.guest) {
for (let c of passwordButton.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
passwordButton.classList.remove(c);
}
passwordButton.disabled = true;
passwordButton.classList.add('bg-gray-700', 'hover:bg-gray-700');
passwordBox.disabled = true;
passwordBox.classList.add('color-white');
for (let c of displayNameButton.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
displayNameButton.classList.remove(c);
}
displayNameButton.disabled = true;
displayNameButton.classList.add('bg-gray-700');
displayNameButton.classList.add('color-white');
displayNameBox.disabled = true;
displayNameBox.classList.add('color-white');
for (let c of enableBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
enableBtn.classList.remove(c);
}
for (let c of disableBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
disableBtn.classList.remove(c);
}
for (let c of showSecretBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
showSecretBtn.classList.remove(c);
}
enableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
disableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
showSecretBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
enableBtn.disabled = true;
disableBtn.disabled = true;
showSecretBtn.disabled = true;
}
// ---- Update UI ----
function refreshTotpUI() {
if (totpEnabled) {
totpStatusText.textContent = "Status: Enabled";
enableBtn.classList.add("hidden");
disableBtn.classList.remove("hidden");
showSecretBtn.classList.remove("hidden");
} else {
totpStatusText.textContent = "Status: Disabled";
enableBtn.classList.remove("hidden");
disableBtn.classList.add("hidden");
showSecretBtn.classList.add("hidden");
secretBox.classList.add("hidden");
}
}
// ---- Button Events ----
enableBtn.onclick = async () => {
let res = await client.enableOtp();
if (res.kind === "success") {
navigateTo(url);
}
else {
showError(`failed to activate OTP: ${res.msg}`);
}
};
disableBtn.onclick = async () => {
let res = await client.disableOtp();
if (res.kind === "success") {
navigateTo(url);
}
else {
showError(`failed to deactivate OTP: ${res.msg}`);
}
};
showSecretBtn.onclick = () => {
secretBox.textContent = `TOTP Secret: ${totpSecret}`;
secretBox.classList.toggle("hidden");
};
// Initialize UI state
refreshTotpUI();
}
};
}
addRoute('/profile', route)