mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 18:33:01 +00:00
SessionProvider: centralised state management
this replaces auth-provider, centralising user data can be extended to keep additional data allows for user data to propogate components throughout the app provides useSession and useAuthenticatedSession()
This commit is contained in:
@@ -16,6 +16,7 @@ import { OrganisationSelect } from "@/components/organisation-select";
|
||||
import OrganisationsDialog from "@/components/organisations-dialog";
|
||||
import { ProjectSelect } from "@/components/project-select";
|
||||
import { ServerConfigurationDialog } from "@/components/server-configuration-dialog";
|
||||
import { useAuthenticatedSession } from "@/components/session-provider";
|
||||
import SmallUserDisplay from "@/components/small-user-display";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -30,10 +31,8 @@ import { issue, organisation, project } from "@/lib/server";
|
||||
|
||||
const BREATHING_ROOM = 1;
|
||||
|
||||
function Index() {
|
||||
const userData = JSON.parse(localStorage.getItem("user") || "{}") as UserRecord;
|
||||
|
||||
const [user, setUser] = useState<UserRecord>(userData);
|
||||
export default function App() {
|
||||
const { user } = useAuthenticatedSession();
|
||||
|
||||
const organisationsRef = useRef(false);
|
||||
const [organisations, setOrganisations] = useState<OrganisationResponse[]>([]);
|
||||
@@ -47,11 +46,6 @@ function Index() {
|
||||
|
||||
const [members, setMembers] = useState<UserRecord[]>([]);
|
||||
|
||||
const refetchUser = async () => {
|
||||
const userData = JSON.parse(localStorage.getItem("user") || "{}") as UserRecord;
|
||||
setUser(userData);
|
||||
};
|
||||
|
||||
const refetchOrganisations = async (options?: { selectOrganisationId?: number }) => {
|
||||
try {
|
||||
await organisation.byUser({
|
||||
@@ -260,11 +254,7 @@ function Index() {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align={"end"}>
|
||||
<DropdownMenuItem asChild className="flex items-end justify-end">
|
||||
<AccountDialog
|
||||
onUpdate={async () => {
|
||||
refetchUser();
|
||||
}}
|
||||
/>
|
||||
<AccountDialog />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem asChild className="flex items-end justify-end">
|
||||
<OrganisationsDialog
|
||||
@@ -334,5 +324,3 @@ function Index() {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default Index;
|
||||
@@ -1,45 +1,16 @@
|
||||
import type { UserRecord } from "@issue/shared";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useSession } from "@/components/session-provider";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { clearAuth, getServerURL, setCsrfToken } from "@/lib/utils";
|
||||
|
||||
type AuthState = "unknown" | "authenticated" | "unauthenticated";
|
||||
|
||||
export default function Landing() {
|
||||
const [authState, setAuthState] = useState<AuthState>("unknown");
|
||||
const verifiedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (verifiedRef.current) return;
|
||||
verifiedRef.current = true;
|
||||
|
||||
fetch(`${getServerURL()}/auth/me`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as { user: UserRecord; csrfToken: string };
|
||||
localStorage.setItem("user", JSON.stringify(data.user));
|
||||
setCsrfToken(data.csrfToken);
|
||||
setAuthState("authenticated");
|
||||
} else {
|
||||
clearAuth();
|
||||
setAuthState("unauthenticated");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
clearAuth();
|
||||
setAuthState("unauthenticated");
|
||||
});
|
||||
}, []);
|
||||
const { user, isLoading } = useSession();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<header className="relative flex items-center justify-center p-2 border-b">
|
||||
<div className="text-3xl font-basteleur font-700">Issue</div>
|
||||
<nav className="absolute right-2 flex items-center gap-4">
|
||||
{authState === "authenticated" ? (
|
||||
{!isLoading && user ? (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to="/app">Open app</Link>
|
||||
</Button>
|
||||
@@ -65,7 +36,7 @@ export default function Landing() {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{authState === "authenticated" ? (
|
||||
{!isLoading && user ? (
|
||||
<Button asChild size="lg">
|
||||
<Link to="/app">Open app</Link>
|
||||
</Button>
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
import type { UserRecord } from "@issue/shared";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import Loading from "@/components/loading";
|
||||
import LogInForm from "@/components/login-form";
|
||||
import { clearAuth, getServerURL, setCsrfToken } from "@/lib/utils";
|
||||
import { useSession } from "@/components/session-provider";
|
||||
|
||||
export default function Login() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [checking, setChecking] = useState(true);
|
||||
const checkedRef = useRef(false);
|
||||
const { user, isLoading } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (checkedRef.current) return;
|
||||
checkedRef.current = true;
|
||||
if (!isLoading && user) {
|
||||
const next = searchParams.get("next") || "/app";
|
||||
navigate(next, { replace: true });
|
||||
}
|
||||
}, [user, isLoading, navigate, searchParams]);
|
||||
|
||||
fetch(`${getServerURL()}/auth/me`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then(async (res) => {
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as { user: UserRecord; csrfToken: string };
|
||||
setCsrfToken(data.csrfToken);
|
||||
localStorage.setItem("user", JSON.stringify(data.user));
|
||||
const next = searchParams.get("next") || "/app";
|
||||
navigate(next, { replace: true });
|
||||
} else {
|
||||
clearAuth();
|
||||
setChecking(false);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setChecking(false);
|
||||
});
|
||||
}, [navigate, searchParams]);
|
||||
|
||||
if (checking) {
|
||||
if (isLoading) {
|
||||
return <Loading message="Checking authentication" />;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
return <Loading message="Redirecting" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 w-full h-[100vh]">
|
||||
<LogInForm />
|
||||
|
||||
Reference in New Issue
Block a user