Added user authentication
This commit is contained in:
@@ -3,6 +3,7 @@ import { Tabs } from "@heroui/react";
|
||||
import { Button } from "@heroui/react";
|
||||
import { useNavigate, useLocation } from "react-router";
|
||||
import porning from "../images/porning.svg";
|
||||
import User from "./user";
|
||||
|
||||
export function Header() {
|
||||
const location = useLocation();
|
||||
@@ -42,7 +43,7 @@ export function Header() {
|
||||
<div className="absolute flex left-1/2 -translate-x-1/2">
|
||||
<img className="h-12" src={porning} alt="Porning logo" />
|
||||
</div>
|
||||
<Button>Login</Button>
|
||||
<User />
|
||||
</Surface>
|
||||
</div>
|
||||
);
|
||||
|
||||
40
app/components/user.tsx
Normal file
40
app/components/user.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Button, Avatar, Typography, Dropdown, Label, Separator } from "@heroui/react";
|
||||
import { useAuth } from "~/hooks/auth";
|
||||
|
||||
export default function User() {
|
||||
const {user, login, logout} = useAuth();
|
||||
|
||||
if (user === null) {
|
||||
return <Button onClick={() => login()}>Login</Button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<Button className="pr-0">
|
||||
<Typography.Paragraph>{user.name}</Typography.Paragraph>
|
||||
<Avatar>
|
||||
<Avatar.Image alt="Avatar" src={user.avatar}></Avatar.Image>
|
||||
</Avatar>
|
||||
</Button>
|
||||
<Dropdown.Popover >
|
||||
<Dropdown.Menu onAction={(key) => handleAction(String(key), logout)}>
|
||||
<Dropdown.Item id="settings" textValue="Settings">
|
||||
<Label>Settings</Label>
|
||||
</Dropdown.Item>
|
||||
<Separator variant="tertiary"/>
|
||||
<Dropdown.Item id="logout" textValue="Logout" variant="danger">
|
||||
<Label>Logout</Label>
|
||||
</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
</Dropdown.Popover>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function handleAction(key: string, logout: () => Promise<void>) {
|
||||
if (key === "settings") {
|
||||
console.log("settings");
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
}
|
||||
63
app/hooks/auth.tsx
Normal file
63
app/hooks/auth.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useEffect, useState, createContext, useContext, type PropsWithChildren } from "react";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
type User = {
|
||||
id: string,
|
||||
steamid: string,
|
||||
name: string,
|
||||
avatar: string
|
||||
}
|
||||
|
||||
type AuthContextType = {
|
||||
user: User | null,
|
||||
login: () => void,
|
||||
logout: () => Promise<void>
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({
|
||||
user: null,
|
||||
login: () => {},
|
||||
logout: async () => {}
|
||||
});
|
||||
|
||||
export function AuthProvider({children}: PropsWithChildren) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [searchParams, setSeachParams] = useSearchParams();
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
|
||||
if (searchParams.get("openid.ns") !== null) {
|
||||
let response = await fetch("http://localhost:8080/login?" + searchParams.toString()).then(r => r.text());
|
||||
setSeachParams({}, {replace: true});
|
||||
if (response === "nok") {
|
||||
// TODO: signal error to user
|
||||
}
|
||||
}
|
||||
|
||||
let data = await fetch("http://localhost:8080/me", {credentials: "include"})
|
||||
.then(r => r.json())
|
||||
|
||||
console.log(data);
|
||||
setUser(data);
|
||||
}
|
||||
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const login = () => {
|
||||
location.replace("https://steamcommunity.com/openid/login?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=http://localhost:5173&openid.return_to=http://localhost:5173");
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={{user, login, logout}}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
|
||||
import type { Route } from "./+types/root";
|
||||
import "./app.css";
|
||||
import { AuthProvider } from "./hooks/auth";
|
||||
|
||||
export const links: Route.LinksFunction = () => [
|
||||
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
||||
@@ -33,7 +34,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||
<Links />
|
||||
</head>
|
||||
<body className="bg-background text-foreground">
|
||||
{children}
|
||||
<AuthProvider>
|
||||
{children}
|
||||
</AuthProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user