274 lines
10 KiB
Plaintext
274 lines
10 KiB
Plaintext
---
|
|
import SiteFooter from "../../../components/royal-pop/SiteFooter.astro";
|
|
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
|
import styles from "../client.module.scss";
|
|
|
|
const apiBaseUrl = process.env.API_BASE_URL || (import.meta.env.PROD ? "" : "http://localhost:8081");
|
|
const dangerListClass = styles.dangerList;
|
|
const dangerPanelClass = styles.dangerPanel;
|
|
const dangerActionClass = styles.dangerAction;
|
|
const dangerNoteClass = styles.dangerNote;
|
|
---
|
|
|
|
<BaseLayout
|
|
title="Royal Pop Client Reseed"
|
|
description="Private Royal Pop reseed tool for backup and reset operations."
|
|
noindex={true}
|
|
>
|
|
<main class={styles.clientPage} data-client-reseed-page>
|
|
<header class={styles.topbar}>
|
|
<div class={styles.topbarInner}>
|
|
<a href="/client/" class={styles.backLink}>Royal Pop</a>
|
|
|
|
<div class={styles.progress} aria-label="Client tools">
|
|
<span class={styles.progressActive}>Client reseed</span>
|
|
</div>
|
|
|
|
<div class={styles.topbarActions}>
|
|
<p class={styles.helpText}>Local only</p>
|
|
<button type="button" class={styles.logoutButton} data-client-logout hidden>Log out</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<section class={styles.hero}>
|
|
<div class={styles.heroInner}>
|
|
<div>
|
|
<p class={styles.eyebrow}>Client reseed</p>
|
|
<h1 class={styles.heroTitle}>Back up the local database, then clear it back to empty.</h1>
|
|
<p class={styles.heroBody} data-reseed-hero-body>
|
|
This private tool creates a timestamped SQL backup first, then wipes orders, order items, and inventory rows while keeping the schema intact.
|
|
</p>
|
|
</div>
|
|
|
|
<div class={styles.heroMeta}>
|
|
<div>
|
|
<strong>Backup first</strong>
|
|
<span>A SQL snapshot is written before anything is truncated so you have a recovery point.</span>
|
|
</div>
|
|
<div>
|
|
<strong>App data only</strong>
|
|
<span>Orders, order items, and inventory rows are cleared. Tables, indexes, and routes remain in place.</span>
|
|
</div>
|
|
<div>
|
|
<strong>No shortcuts</strong>
|
|
<span>Use this only when you truly want a clean local state for demos, testing, or re-seeding.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class={styles.dashboardSection}>
|
|
<div class={styles.dashboardShell}>
|
|
<section class={styles.loginPanel} data-client-login-panel>
|
|
<div class={styles.panelHeader}>
|
|
<p class={styles.sectionKicker}>Sign in</p>
|
|
<h2>Unlock the reseed tool.</h2>
|
|
<p>Use the same private client credentials as the other dashboard pages.</p>
|
|
</div>
|
|
|
|
<form class={styles.loginForm} data-client-login-form>
|
|
<label class={styles.field}>
|
|
<span>Username</span>
|
|
<input type="text" name="username" autocomplete="username" required />
|
|
</label>
|
|
<label class={styles.field}>
|
|
<span>Password</span>
|
|
<input type="password" name="password" autocomplete="current-password" required />
|
|
</label>
|
|
|
|
<div class={styles.loginActions}>
|
|
<button type="submit" class={styles.primaryCta} data-client-login-submit>Unlock reseed</button>
|
|
</div>
|
|
|
|
<p class={styles.formStatus} data-client-auth-feedback data-state="info">
|
|
Sign in to create a backup and reset the local database.
|
|
</p>
|
|
</form>
|
|
</section>
|
|
|
|
<section class={dangerPanelClass} data-client-reseed-panel hidden>
|
|
<div class={styles.panelHeader}>
|
|
<p class={styles.sectionKicker}>Danger zone</p>
|
|
<h2>Backup and reseed the local database</h2>
|
|
<p>This action writes a SQL backup file first, then empties the app tables so the local client pages come back up with a clean state.</p>
|
|
</div>
|
|
|
|
<ul class={dangerListClass}>
|
|
<li>Creates a timestamped SQL backup file under <strong>Backend/Backups/reseed/</strong>.</li>
|
|
<li>Clears <strong>orders</strong>, <strong>order_items</strong>, and <strong>inventory_levels</strong>.</li>
|
|
<li>Keeps the schema, migrations, and app code intact.</li>
|
|
</ul>
|
|
|
|
<div class={dangerActionClass}>
|
|
<button type="button" class={styles.primaryCta} data-reseed-trigger>Backup and reseed database</button>
|
|
<p class={dangerNoteClass}>There is no second step on this page. Once you press the button, the local reset runs immediately after the backup file is written.</p>
|
|
</div>
|
|
|
|
<p class={styles.dashboardStatus} data-client-reseed-feedback data-state="info">Reseed tool ready.</p>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<SiteFooter />
|
|
|
|
<script define:vars={{ apiBaseUrl }}>
|
|
const normalizedApiBaseUrl = String(apiBaseUrl || "").replace(/\/$/, "");
|
|
|
|
const initClientReseedPage = () => {
|
|
const root = document.querySelector("[data-client-reseed-page]");
|
|
if (!root || root.dataset.initialized === "true") return;
|
|
root.dataset.initialized = "true";
|
|
|
|
const loginPanel = root.querySelector("[data-client-login-panel]");
|
|
const reseedPanel = root.querySelector("[data-client-reseed-panel]");
|
|
const loginForm = root.querySelector("[data-client-login-form]");
|
|
const loginSubmit = root.querySelector("[data-client-login-submit]");
|
|
const authFeedback = root.querySelector("[data-client-auth-feedback]");
|
|
const reseedFeedback = root.querySelector("[data-client-reseed-feedback]");
|
|
const reseedTrigger = root.querySelector("[data-reseed-trigger]");
|
|
const logoutButton = root.querySelector("[data-client-logout]");
|
|
const heroBody = root.querySelector("[data-reseed-hero-body]");
|
|
|
|
const setFeedback = (node, message, state = "info") => {
|
|
if (!node) return;
|
|
node.textContent = message;
|
|
node.dataset.state = state;
|
|
};
|
|
|
|
const setAuthenticatedUi = (authenticated, username = "") => {
|
|
if (loginPanel) loginPanel.hidden = authenticated;
|
|
if (reseedPanel) reseedPanel.hidden = !authenticated;
|
|
if (logoutButton) logoutButton.hidden = !authenticated;
|
|
if (heroBody) {
|
|
heroBody.textContent = authenticated
|
|
? `Signed in as ${username || "client user"}. This tool will create a backup file first and then reset the local database to an empty state.`
|
|
: "Sign in to create a backup and reset the local database to an empty state.";
|
|
}
|
|
};
|
|
|
|
const syncSession = async () => {
|
|
try {
|
|
const response = await fetch(`${normalizedApiBaseUrl}/v1/client/session`, { credentials: "include" });
|
|
const payload = await response.json().catch(() => null);
|
|
const session = payload?.data;
|
|
|
|
if (!response.ok || !session?.configured) {
|
|
setAuthenticatedUi(false);
|
|
setFeedback(authFeedback, session?.configured === false ? "Client dashboard credentials are not configured for this environment yet." : "Unable to verify dashboard access.", session?.configured === false ? "error" : "info");
|
|
return;
|
|
}
|
|
|
|
if (!session.authenticated) {
|
|
setAuthenticatedUi(false);
|
|
setFeedback(authFeedback, "Sign in to create a backup and reset the local database.", "info");
|
|
return;
|
|
}
|
|
|
|
setAuthenticatedUi(true, session.username);
|
|
setFeedback(reseedFeedback, "Reseed tool ready.", "success");
|
|
} catch (error) {
|
|
setAuthenticatedUi(false);
|
|
setFeedback(authFeedback, error instanceof Error ? error.message : "Unable to verify dashboard access.", "error");
|
|
}
|
|
};
|
|
|
|
loginForm?.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
const formData = new FormData(loginForm);
|
|
const username = String(formData.get("username") || "").trim();
|
|
const password = String(formData.get("password") || "");
|
|
|
|
if (!username || !password) {
|
|
setFeedback(authFeedback, "Enter both the username and password.", "error");
|
|
return;
|
|
}
|
|
|
|
if (loginSubmit) {
|
|
loginSubmit.disabled = true;
|
|
loginSubmit.textContent = "Unlocking…";
|
|
}
|
|
|
|
setFeedback(authFeedback, "Checking credentials…", "info");
|
|
|
|
try {
|
|
const response = await fetch(`${normalizedApiBaseUrl}/v1/client/login`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
const payload = await response.json().catch(() => null);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(payload?.error?.message || "The client dashboard username or password is incorrect.");
|
|
}
|
|
|
|
loginForm.reset();
|
|
setAuthenticatedUi(true, payload?.data?.username || username);
|
|
setFeedback(reseedFeedback, "Reseed tool ready.", "success");
|
|
} catch (error) {
|
|
setAuthenticatedUi(false);
|
|
setFeedback(authFeedback, error instanceof Error ? error.message : "Unable to unlock the reseed tool.", "error");
|
|
} finally {
|
|
if (loginSubmit) {
|
|
loginSubmit.disabled = false;
|
|
loginSubmit.textContent = "Unlock reseed";
|
|
}
|
|
}
|
|
});
|
|
|
|
logoutButton?.addEventListener("click", async () => {
|
|
logoutButton.disabled = true;
|
|
try {
|
|
await fetch(`${normalizedApiBaseUrl}/v1/client/logout`, { method: "POST", credentials: "include" });
|
|
} finally {
|
|
logoutButton.disabled = false;
|
|
setAuthenticatedUi(false);
|
|
setFeedback(authFeedback, "Signed out. Sign in again to use the reseed tool.", "info");
|
|
}
|
|
});
|
|
|
|
reseedTrigger?.addEventListener("click", async () => {
|
|
if (!window.confirm("This will back up the local database and then clear orders, order items, and inventory. Continue?")) {
|
|
return;
|
|
}
|
|
|
|
reseedTrigger.disabled = true;
|
|
reseedTrigger.textContent = "Backing up and reseeding…";
|
|
setFeedback(reseedFeedback, "Creating backup and clearing the local database…", "info");
|
|
|
|
try {
|
|
const response = await fetch(`${normalizedApiBaseUrl}/v1/client/reseed`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
});
|
|
const payload = await response.json().catch(() => null);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(payload?.error?.message || "The database backup or reset could not be completed.");
|
|
}
|
|
|
|
const data = payload?.data || {};
|
|
setFeedback(
|
|
reseedFeedback,
|
|
`Backup saved to ${data.backupPath || "Backups/reseed"}. Reset cleared ${data.ordersCount || 0} orders, ${data.orderItemsCount || 0} order items, and ${data.inventoryCount || 0} inventory rows.`,
|
|
"success",
|
|
);
|
|
} catch (error) {
|
|
setFeedback(reseedFeedback, error instanceof Error ? error.message : "The database backup or reset could not be completed.", "error");
|
|
} finally {
|
|
reseedTrigger.disabled = false;
|
|
reseedTrigger.textContent = "Backup and reseed database";
|
|
}
|
|
});
|
|
|
|
syncSession();
|
|
};
|
|
|
|
initClientReseedPage();
|
|
document.addEventListener("astro:page-load", initClientReseedPage);
|
|
</script>
|
|
</main>
|
|
</BaseLayout>
|