join (or register) with invite link

This commit is contained in:
2026-01-03 17:02:44 +01:00
parent aff3b9f7be
commit bb41513571
7 changed files with 107 additions and 46 deletions

View File

@@ -104,7 +104,7 @@ function TypeDnD({ user, teams, players }: PlayerInfoProps) {
useEffect(() => {
handleGet();
}, [players]);
}, [players, teams]);
const [dialog, setDialog] = useState("dialog");
const dialogRef = useRef<HTMLDialogElement>(null);
@@ -245,7 +245,7 @@ function MVPDnD({ user, teams, players }: PlayerInfoProps) {
const activeTeam = teams.teams.find((team) => team.id == teams.activeTeam);
activeTeam && setMixed(activeTeam.mixed);
handleGet();
}, [players]);
}, [players, teams]);
useEffect(() => {
handleGet();
@@ -364,7 +364,7 @@ function ChemistryDnDMobile({ user, teams, players }: PlayerInfoProps) {
useEffect(() => {
handleGet();
}, [players]);
}, [players, teams]);
const [dialog, setDialog] = useState("dialog");
const dialogRef = useRef<HTMLDialogElement>(null);

View File

@@ -1,14 +1,17 @@
import { jwtDecode } from "jwt-decode";
import { PassToken } from "./api";
import { apiAuth, PassToken } from "./api";
import { useEffect, useState } from "react";
import { UsersIcon } from "lucide-react";
import { useNavigate } from "react-router";
import { useSession } from "./Session";
export const Join = () => {
const [token, setToken] = useState("");
const { user } = useSession();
const [teamName, setTeamName] = useState("");
const [teamID, setTeamID] = useState<number>();
const [error, setError] = useState("");
const { teams, setTeams } = useSession();
const navigate = useNavigate();
useEffect(() => {
const params = new URLSearchParams(window.location.search);
@@ -17,7 +20,7 @@ export const Join = () => {
setToken(token);
try {
const payload = jwtDecode<PassToken>(token);
if (payload.sub === "register") {
if (payload.sub === "join") {
if (payload.team_id) setTeamID(payload.team_id);
} else {
setError("not a valid token for joining");
@@ -29,15 +32,32 @@ export const Join = () => {
} else setError("no token found");
}, []);
async function handleJoin() {
const r = await apiAuth(
`player/${teamID}/join`,
{ token: token, team_id: teamID },
"POST"
);
if (r.detail) setError(r.detail);
else {
setTeams({ ...teams, activeTeam: teamID });
navigate("/", { replace: true });
}
}
return (
<section className="section is-medium">
<div className="container is-max-tablet">
<div className="container is-max-tablet has-text-centered">
<h1 className="title">Join "{teamName}"</h1>
<div className="field is-grouped is-grouped-centered">
<button className="button is-primary is-large is-outlined">
join
<button className="button is-dark is-large" onClick={handleJoin}>
<span className="icon">
<UsersIcon />
</span>
<span> join team</span>
</button>
</div>
<span className="help is-danger">{error}</span>
</div>
</section>
);

View File

@@ -1,8 +1,7 @@
import { jwtDecode } from "jwt-decode";
import { FormEvent, useEffect, useState } from "react";
import { baseUrl, Gender, PassToken } from "./api";
import { Link, useLocation, useNavigate } from "react-router";
import { TriangleAlert } from "lucide-react";
import { useLocation, useNavigate } from "react-router";
export const Register = () => {
const [name, setName] = useState("");
@@ -28,7 +27,7 @@ export const Register = () => {
setToken(token);
try {
const payload = jwtDecode<PassToken>(token);
if (payload.sub === "register") {
if (payload.sub === "join") {
if (payload.team_id) setTeamID(payload.team_id);
} else {
setError("not a valid token for registration");
@@ -107,17 +106,6 @@ export const Register = () => {
<h1 className="title">
Register {teamName && `in team "${teamName}"`}
</h1>
<div className="notification is-warning">
<div className="icon-text">
<span className="icon">
<TriangleAlert />
</span>
<span>
If you already have an account,{" "}
<a href={`/join${location.search}&login=1`}>login</a> first.
</span>
</div>
</div>
<form onSubmit={handleSubmit}>
<div className="field">
<div className="field">

View File

@@ -7,15 +7,10 @@ import {
} from "react";
import { apiAuth, currentUser, loadPlayers, logout, User } from "./api";
import { Login } from "./Login";
import Header from "./Header";
import { Team } from "./types";
import Loading from "./Loading";
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from "react-router";
import { Link, useLocation } from "react-router";
import { TriangleAlert } from "lucide-react";
export interface SessionProviderProps {
children: ReactNode;
@@ -53,8 +48,6 @@ export function SessionProvider(props: SessionProviderProps) {
const [error, setError] = useState<unknown>(null);
const [loading, setLoading] = useState(false);
const location = useLocation();
let [searchParams] = useSearchParams();
const navigate = useNavigate();
function loadUser() {
setLoading(true);
@@ -71,8 +64,12 @@ export function SessionProvider(props: SessionProviderProps) {
}
async function loadTeam() {
const teams: Team[] = await apiAuth("player/me/teams", null, "GET");
if (teams) setTeams({ teams: teams, activeTeam: teams[0].id });
const loaded_teams: Team[] = await apiAuth("player/me/teams", null, "GET");
if (loaded_teams)
setTeams({
teams: loaded_teams,
activeTeam: teams?.activeTeam || loaded_teams[0].id,
});
}
async function reloadPlayers() {
@@ -110,8 +107,6 @@ export function SessionProvider(props: SessionProviderProps) {
if (loading || (!error && !user))
content = <section className="section is-medium">{Loading}</section>;
else if (error) {
if (location.pathname === "/join" && !searchParams.get("login"))
navigate(`/register${location.search}`);
content = (
<section className="section is-medium">
<div className="container is-max-tablet">
@@ -125,6 +120,19 @@ export function SessionProvider(props: SessionProviderProps) {
/>
</p>
</div>
{location.pathname === "/join" && (
<div className="notification is-warning">
<div className="icon-text">
<span className="icon">
<TriangleAlert />
</span>
<span>
If you don't already have an account,{" "}
<Link to={`/register${location.search}`}>register here</Link>.
</span>
</div>
</div>
)}
<Login onLogin={onLogin} />
</div>
</section>