added udner construction for file compaction, planning for unbloating
All checks were successful
Deploy / deploy (push) Successful in 1m28s
Security Tests / security-non-db (push) Successful in 20s
Security Tests / security-db (push) Successful in 25s

This commit is contained in:
2026-03-15 14:44:47 -05:00
parent 512e21276c
commit ba549f6c84
14 changed files with 663 additions and 31 deletions

19
web/src/api/siteAccess.ts Normal file
View File

@@ -0,0 +1,19 @@
import { apiGet, apiPost } from "./http";
export type SiteAccessStatus = {
ok: boolean;
enabled: boolean;
unlocked: boolean;
};
export async function getSiteAccessStatus(): Promise<SiteAccessStatus> {
return apiGet<SiteAccessStatus>("/site-access/status");
}
export async function unlockSiteAccess(code: string): Promise<SiteAccessStatus> {
return apiPost<SiteAccessStatus>("/site-access/unlock", { code });
}
export async function lockSiteAccess(): Promise<SiteAccessStatus> {
return apiPost<SiteAccessStatus>("/site-access/lock");
}

View File

@@ -1,5 +1,6 @@
import { type ReactNode, useEffect, useState } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { getSiteAccessStatus } from "../api/siteAccess";
const STORAGE_KEY = "skymoney_beta_access";
@@ -10,16 +11,43 @@ type Props = {
export function BetaGate({ children }: Props) {
const location = useLocation();
const [hasAccess, setHasAccess] = useState<boolean | null>(null);
const [isEnabled, setIsEnabled] = useState<boolean | null>(null);
useEffect(() => {
setHasAccess(localStorage.getItem(STORAGE_KEY) === "true");
let cancelled = false;
(async () => {
try {
const status = await getSiteAccessStatus();
const unlocked = !!status.unlocked;
if (!cancelled) {
setIsEnabled(!!status.enabled);
setHasAccess(!status.enabled || unlocked);
}
if (status.enabled && unlocked) {
localStorage.setItem(STORAGE_KEY, "true");
}
if (status.enabled && !unlocked) {
localStorage.removeItem(STORAGE_KEY);
}
} catch {
// Fallback for temporary API/network issues.
const localFallback = localStorage.getItem(STORAGE_KEY) === "true";
if (!cancelled) {
setIsEnabled(true);
setHasAccess(localFallback);
}
}
})();
return () => {
cancelled = true;
};
}, []);
if (location.pathname === "/beta") {
return <>{children}</>;
}
if (hasAccess === null) {
if (hasAccess === null || isEnabled === null) {
return (
<div className="flex min-h-[40vh] items-center justify-center text-sm muted">
Checking access
@@ -27,7 +55,7 @@ export function BetaGate({ children }: Props) {
);
}
if (!hasAccess) {
if (isEnabled && !hasAccess) {
return <Navigate to="/beta" replace />;
}

View File

@@ -1,26 +1,65 @@
import { useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { betaAccessStorageKey } from "../components/BetaGate";
const ACCESS_CODE = "jodygavemeaccess123";
import { getSiteAccessStatus, lockSiteAccess, unlockSiteAccess } from "../api/siteAccess";
export default function BetaAccessPage() {
const navigate = useNavigate();
const [code, setCode] = useState("");
const [touched, setTouched] = useState(false);
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState(false);
const [enabled, setEnabled] = useState<boolean | null>(null);
const isValid = useMemo(() => code.trim() === ACCESS_CODE, [code]);
const isUnlocked = useMemo(
() => localStorage.getItem(betaAccessStorageKey) === "true",
[]
const [isUnlocked, setIsUnlocked] = useState(
localStorage.getItem(betaAccessStorageKey) === "true"
);
const handleSubmit = (event: React.FormEvent) => {
useEffect(() => {
let cancelled = false;
(async () => {
try {
const status = await getSiteAccessStatus();
if (!cancelled) setEnabled(!!status.enabled);
} catch {
if (!cancelled) setEnabled(true);
}
})();
return () => {
cancelled = true;
};
}, []);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
setTouched(true);
if (!isValid) return;
localStorage.setItem(betaAccessStorageKey, "true");
navigate("/login", { replace: true });
setError("");
if (!code.trim()) return;
setLoading(true);
try {
const result = await unlockSiteAccess(code.trim());
if (!result.unlocked) {
setError("That code doesnt match. Please try again.");
return;
}
localStorage.setItem(betaAccessStorageKey, "true");
setIsUnlocked(true);
navigate("/login", { replace: true });
} catch (err: any) {
setError(err?.message ?? "Unable to unlock access right now.");
} finally {
setLoading(false);
}
};
const handleClearAccess = async () => {
localStorage.removeItem(betaAccessStorageKey);
setIsUnlocked(false);
try {
await lockSiteAccess();
} catch {
// noop
}
};
return (
@@ -28,14 +67,15 @@ export default function BetaAccessPage() {
<div className="w-full max-w-xl card p-8 sm:p-10">
<div className="space-y-3">
<div className="text-xs uppercase tracking-[0.2em] muted">
Private beta
Under construction
</div>
<h1 className="text-3xl sm:text-4xl font-semibold">
Welcome to SkyMoney
SkyMoney maintenance mode
</h1>
<p className="muted">
This build is private. If youve been given access, enter your code
below. If not, reach out to{" "}
Public access is temporarily paused while we ship updates. Enter
your maintenance access code to continue testing. If you need
access, reach out to{" "}
<a
href="https://jodyholt.com"
target="_blank"
@@ -46,6 +86,11 @@ export default function BetaAccessPage() {
</a>{" "}
for access.
</p>
{enabled === false && (
<p className="text-sm text-emerald-500">
Maintenance mode is currently disabled.
</p>
)}
</div>
<form onSubmit={handleSubmit} className="mt-6 space-y-4">
@@ -58,6 +103,7 @@ export default function BetaAccessPage() {
value={code}
onChange={(event) => {
setTouched(true);
setError("");
setCode(event.target.value);
}}
placeholder="Enter your code"
@@ -66,28 +112,37 @@ export default function BetaAccessPage() {
</div>
</label>
{touched && code.length > 0 && !isValid && (
{touched && code.length > 0 && !!error && (
<div className="text-sm text-red-500">
That code doesnt match. Please try again.
{error}
</div>
)}
<button
type="submit"
className="btn primary w-full"
disabled={!isValid}
disabled={!code.trim() || loading}
>
Continue
{loading ? "Checking…" : "Continue"}
</button>
{isUnlocked && (
<button
type="button"
className="btn w-full"
onClick={() => navigate("/login")}
>
I already have access
</button>
<div className="grid gap-2 sm:grid-cols-2">
<button
type="button"
className="btn w-full"
onClick={() => navigate("/login")}
>
I already have access
</button>
<button
type="button"
className="btn w-full"
onClick={handleClearAccess}
>
Clear local access
</button>
</div>
)}
</form>
</div>