UI upadte, added host system, better flex scaling, improved login.

This commit is contained in:
LukeGus
2025-08-15 01:01:04 -05:00
parent 07367b24b6
commit b854a4956c
11 changed files with 669 additions and 152 deletions

View File

@@ -6,6 +6,10 @@ import {HomepageAlertManager} from "@/ui/Homepage/HomepageAlertManager.tsx";
interface HomepageProps {
onSelectView: (view: string) => void;
isAuthenticated: boolean;
authLoading: boolean;
onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void;
isTopbarOpen?: boolean;
}
function getCookie(name: string) {
@@ -26,51 +30,51 @@ const API = axios.create({
baseURL: apiBase,
});
export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(false);
export function Homepage({onSelectView, isAuthenticated, authLoading, onAuthSuccess, isTopbarOpen = true}: HomepageProps): React.ReactElement {
const [loggedIn, setLoggedIn] = useState(isAuthenticated);
const [isAdmin, setIsAdmin] = useState(false);
const [username, setUsername] = useState<string | null>(null);
const [userId, setUserId] = useState<string | null>(null);
const [authLoading, setAuthLoading] = useState(true);
const [dbError, setDbError] = useState<string | null>(null);
// Update local state when props change
useEffect(() => {
const jwt = getCookie("jwt");
setLoggedIn(isAuthenticated);
}, [isAuthenticated]);
if (jwt) {
setAuthLoading(true);
Promise.all([
API.get("/me", {headers: {Authorization: `Bearer ${jwt}`}}),
API.get("/db-health")
])
.then(([meRes]) => {
setLoggedIn(true);
setIsAdmin(!!meRes.data.is_admin);
setUsername(meRes.data.username || null);
setUserId(meRes.data.userId || null);
setDbError(null);
})
.catch((err) => {
setLoggedIn(false);
setIsAdmin(false);
setUsername(null);
setUserId(null);
setCookie("jwt", "", -1);
if (err?.response?.data?.error?.includes("Database")) {
setDbError("Could not connect to the database. Please try again later.");
} else {
useEffect(() => {
if (isAuthenticated) {
const jwt = getCookie("jwt");
if (jwt) {
Promise.all([
API.get("/me", {headers: {Authorization: `Bearer ${jwt}`}}),
API.get("/db-health")
])
.then(([meRes]) => {
setIsAdmin(!!meRes.data.is_admin);
setUsername(meRes.data.username || null);
setUserId(meRes.data.userId || null);
setDbError(null);
}
})
.finally(() => setAuthLoading(false));
} else {
setAuthLoading(false);
})
.catch((err) => {
setIsAdmin(false);
setUsername(null);
setUserId(null);
if (err?.response?.data?.error?.includes("Database")) {
setDbError("Could not connect to the database. Please try again later.");
} else {
setDbError(null);
}
});
}
}
}, []);
}, [isAuthenticated]);
return (
<div className="w-full min-h-svh grid place-items-center">
<div className="flex flex-row items-center justify-center gap-8">
<div className={`w-full min-h-svh grid place-items-center relative transition-[padding-top] duration-200 ease-linear ${
isTopbarOpen ? 'pt-[66px]' : 'pt-2'
}`}>
<div className="flex flex-row items-center justify-center gap-8 relative z-[10000]">
<HomepageAuth
setLoggedIn={setLoggedIn}
setIsAdmin={setIsAdmin}
@@ -80,6 +84,7 @@ export function Homepage({onSelectView}: HomepageProps): React.ReactElement {
authLoading={authLoading}
dbError={dbError}
setDbError={setDbError}
onAuthSuccess={onAuthSuccess}
/>
<HomepageUpdateLog
loggedIn={loggedIn}

View File

@@ -121,19 +121,6 @@ export function HomepageAlertManager({userId, loggedIn}: AlertManagerProps): Rea
return null;
}
if (loading) {
return (
<div className="fixed top-4 right-4 z-50">
<div className="bg-background border rounded-lg p-3 shadow-lg">
<div className="flex items-center gap-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary"></div>
<span className="text-sm text-muted-foreground">Loading alerts...</span>
</div>
</div>
</div>
);
}
if (alerts.length === 0) {
return null;
}
@@ -152,7 +139,7 @@ export function HomepageAlertManager({userId, loggedIn}: AlertManagerProps): Rea
const hasMultipleAlerts = alerts.length > 1;
return (
<div className="fixed inset-0 flex items-center justify-center bg-background/80 backdrop-blur-sm z-10">
<div className="fixed inset-0 flex items-center justify-center bg-background/80 backdrop-blur-sm z-[99999]">
<div className="relative w-full max-w-2xl mx-4">
<HomepageAlertCard
alert={currentAlert}

View File

@@ -33,6 +33,7 @@ interface HomepageAuthProps extends React.ComponentProps<"div"> {
authLoading: boolean;
dbError: string | null;
setDbError: (error: string | null) => void;
onAuthSuccess: (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => void;
}
export function HomepageAuth({
@@ -45,6 +46,7 @@ export function HomepageAuth({
authLoading,
dbError,
setDbError,
onAuthSuccess,
...props
}: HomepageAuthProps) {
const [tab, setTab] = useState<"login" | "signup" | "external" | "reset">("login");
@@ -147,6 +149,14 @@ export function HomepageAuth({
setUsername(meRes.data.username || null);
setUserId(meRes.data.id || null);
setDbError(null);
// Call onAuthSuccess to update App state
onAuthSuccess({
isAdmin: !!meRes.data.is_admin,
username: meRes.data.username || null,
userId: meRes.data.id || null
});
// Update internal state immediately
setInternalLoggedIn(true);
if (tab === "signup") {
setSignupConfirmPassword("");
}
@@ -255,10 +265,6 @@ export function HomepageAuth({
setError(null);
}
async function resetPassword() {
}
async function handleOIDCLogin() {
setError(null);
setOidcLoading(true);
@@ -303,6 +309,14 @@ export function HomepageAuth({
setUsername(meRes.data.username || null);
setUserId(meRes.data.id || null);
setDbError(null);
// Call onAuthSuccess to update App state
onAuthSuccess({
isAdmin: !!meRes.data.is_admin,
username: meRes.data.username || null,
userId: meRes.data.id || null
});
// Update internal state immediately
setInternalLoggedIn(true);
window.history.replaceState({}, document.title, window.location.pathname);
})
.catch(err => {
@@ -331,12 +345,13 @@ export function HomepageAuth({
return (
<div
className={cn(
"",
className
)}
{...props}
>
<div
className={`w-[420px] max-w-full bg-background rounded-xl shadow-lg p-6 flex flex-col ${internalLoggedIn ? '' : 'border border-border'}`}>
className={`w-[420px] max-w-full bg-background/95 backdrop-blur-md rounded-xl shadow-2xl p-6 flex flex-col relative ${internalLoggedIn ? '' : 'ring-1 ring-border/50'} focus-within:ring-2 focus-within:ring-primary/50 transition-all duration-200`}>
{dbError && (
<Alert variant="destructive" className="mb-4">
<AlertTitle>Error</AlertTitle>
@@ -369,16 +384,16 @@ export function HomepageAuth({
</AlertDescription>
</Alert>
)}
{(internalLoggedIn || (authLoading && getCookie("jwt"))) && (
{(internalLoggedIn || getCookie("jwt")) && (
<div className="flex flex-col items-center gap-4">
<Alert className="my-2">
<AlertTitle>Logged in!</AlertTitle>
<AlertDescription>
<div className="my-2 text-center bg-muted/50 border border-border rounded-lg p-4 w-full">
<h3 className="text-lg font-semibold mb-2">Logged in!</h3>
<p className="text-muted-foreground">
You are logged in! Use the sidebar to access all available tools. To get started,
create an SSH Host in the SSH Manager tab. Once created, you can connect to that
host using the other apps in the sidebar.
</AlertDescription>
</Alert>
</p>
</div>
<div className="flex flex-row items-center gap-2">
<Button
@@ -607,17 +622,18 @@ export function HomepageAuth({
<p>Enter your new password for
user: <strong>{localUsername}</strong></p>
</div>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<Label htmlFor="new-password">New Password</Label>
<Input
id="new-password"
type="password"
required
className="h-11 text-base"
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={newPassword}
onChange={e => setNewPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
</div>
<div className="flex flex-col gap-2">
@@ -626,10 +642,11 @@ export function HomepageAuth({
id="confirm-password"
type="password"
required
className="h-11 text-base"
className="h-11 text-base focus:ring-2 focus:ring-primary/50 transition-all duration-200"
value={confirmPassword}
onChange={e => setConfirmPassword(e.target.value)}
disabled={resetLoading}
autoComplete="new-password"
/>
</div>
<Button
@@ -719,4 +736,4 @@ export function HomepageAuth({
</div>
</div>
);
}
}