fantasy-manager/pages/usermenu.tsx
Lukas Schaefer d3a5fe88f9
switch to kysely (#863)
Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>
2025-05-09 19:12:32 -04:00

378 lines
11 KiB
TypeScript

import { signOut } from "next-auth/react";
import { ChangeEvent, Key, useContext, useState } from "react";
import Menu from "../components/Menu";
import Head from "next/head";
import {
Button,
Icon,
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
TextField,
useTheme,
} from "@mui/material";
import {
GetServerSideProps,
GetServerSidePropsContext,
InferGetServerSidePropsType,
} from "next";
import { Session, getServerSession } from "next-auth";
import {
NotifyContext,
NotifyType,
TranslateContext,
UserContext,
} from "../Modules/context";
import { getProviders, Providers } from "../types/providers";
import db from "../Modules/database";
import { useRouter } from "next/router";
import { authOptions } from "#/pages/api/auth/[...nextauth]";
import { MUIThemeCodetoJSONString } from "#/components/theme";
import Link from "#components/Link";
import { getData } from "./api/theme";
interface ProviderProps {
provider: Providers;
notify: NotifyType;
user: Session["user"];
}
// Shows the ways to connect and disconnect from a provider
function ProviderShow({ provider, notify, user }: ProviderProps) {
const t = useContext(TranslateContext);
const [email, setEmail] = useState(user[provider]);
const [input, setInput] = useState("");
function handleInputChange(
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
setInput(e.target.value);
}
// Used to connect to the provider
function connect() {
notify(t("Connecting to {provider}", { provider }));
fetch(`/api/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
provider,
email: input,
}),
}).then(async (response) => {
notify(
t(await response.text(), { provider }),
response.ok ? "success" : "error",
);
setEmail(input);
});
}
// Used to disconnect from the provider
function disconnect() {
notify(t("Disconnecting from {provider}", { provider }));
fetch(`/api/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
provider,
email: "",
}),
}).then(async (response) => {
notify(
t(await response.text(), { provider }),
response.ok ? "success" : "error",
);
setInput("");
setEmail("");
});
}
// Checks if connected or not
if (email === "") {
return (
<>
<br></br>
<TextField
type="email"
variant="outlined"
size="small"
label={t("Email")}
value={input}
onChange={handleInputChange}
helperText={t("Email used with {provider}", { provider })}
/>
<Button onClick={connect} variant="outlined">
{t("Connect to {provider}", { provider })}
</Button>
</>
);
} else {
return (
<>
<br></br>
<Button variant="outlined" onClick={disconnect}>
{t("Disconnect from {provider}", { provider })}
</Button>
</>
);
}
}
// A place to change your username and other settings
export default function Home({
user,
providers,
setColorMode,
deleteable,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
const t = useContext(TranslateContext);
const [getUser] = useContext(UserContext);
const [username, setUsername] = useState(user.username);
const [password, setPassword] = useState("");
const [passwordExists, setPasswordExists] = useState(user.password);
const [customTheme, setCustomTheme] = useState("");
const theme = useTheme();
const notify = useContext(NotifyContext);
const oppositeColor = theme.palette.mode === "dark" ? "light" : "dark";
// Alternates the color mode
function alternateColorMode() {
setColorMode(oppositeColor, true);
localStorage.theme = oppositeColor;
}
// Resets the color mode
function resetColorMode() {
setColorMode(theme.palette.mode, true);
localStorage.theme = theme.palette.mode;
}
// Used to change the users username
function changeUsername() {
notify(t("Saving"));
fetch(`/api/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username,
}),
}).then(async (response) => {
notify(t(await response.text()), response.ok ? "success" : "error");
// Makes sure to update the username
getUser(user.id, true);
});
}
// Used to change the users password
function changePassword() {
notify(t("Saving"));
fetch(`/api/user`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
password: password,
}),
}).then(async (response) => {
notify(t(await response.text()), response.ok ? "success" : "error");
setPasswordExists(password !== "");
});
}
// Used to change the locale
function changeLocale(event: SelectChangeEvent<string>) {
const locale = event.target.value;
notify(t("Saving"));
fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
locale: locale,
}),
}).then(async (response) => {
notify(t(await response.text()), response.ok ? "success" : "error");
localStorage.locale = locale;
const event = new Event("visibilitychange");
document.dispatchEvent(event);
});
}
// Used to delete the user
const router = useRouter();
function deleteUser() {
notify(t("Deleting user"));
fetch(`/api/user`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user: user.id,
}),
}).then(async (response) => {
notify(t(await response.text()), response.ok ? "success" : "error");
signOut();
});
}
function saveCustomTheme() {
setColorMode(customTheme, true);
localStorage.theme = customTheme;
}
// Parses custom theme when pasted
if (customTheme.includes("import")) {
setCustomTheme(MUIThemeCodetoJSONString(customTheme));
}
return (
<>
<Head>
<title>{t("Usermenu")}</title>
</Head>
<Menu />
<h1>{t("Usermenu")}</h1>
<p>Your userID is {user.id}.</p>
<Button variant="contained" onClick={alternateColorMode}>
{t("Switch to {theme} mode", { theme: t(oppositeColor) })}{" "}
<Icon>{oppositeColor + "_mode"}</Icon>
</Button>
<InputLabel htmlFor="locale">{t("Language")}</InputLabel>
<Select value={router.locale} onChange={changeLocale} id="locale">
{router.locales?.map((val) => (
<MenuItem key={val} value={val}>
{val}
</MenuItem>
))}
</Select>
<br />
<br />
<TextField
error={username === ""}
id="username"
variant="outlined"
size="small"
label={t("Username")}
onChange={(e) => {
// Used to change the username
setUsername(e.target.value);
}}
value={username}
/>
<Button variant="contained" onClick={changeUsername}>
{t("Change Username")}
</Button>
<p>
{t("Password Auth is currently {enabled}. ", {
enabled: passwordExists ? t("enabled") : t("disabled"),
})}
{t(
"It is recommended against using password authorization unless strictly necessary. ",
)}
</p>
<TextField
type="password"
id="password"
variant="outlined"
size="small"
label={t("Password")}
helperText={t("If empty this will disable password auth")}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
<Button
disabled={password === "" && !passwordExists}
variant="contained"
onClick={changePassword}
>
{password === "" ? t("Disable password auth") : t("Change password")}
</Button>
{!deleteable && (
<p>
{t("You can not be in any leagues if you want to delete your user. ")}
</p>
)}
{deleteable && (
<>
<br></br>
<Button onClick={deleteUser} color="error" variant="contained">
{t("Delete user")} <Icon>delete</Icon>
</Button>
</>
)}
<h2>{t("OAuth Providers")}</h2>
{providers.map((provider: Providers) => (
<ProviderShow
key={provider as Key}
provider={provider}
notify={notify}
user={user}
/>
))}
<h2>{t("Advanced Customization")}</h2>
<p>
{t(
"You can customize the theme here, but note that this is only for advanced users. ",
)}
<Link
href="https://zenoo.github.io/mui-theme-creator/"
rel="noopener noreferrer"
target="_blank"
>
{t("First you will want to go to the MUI Theme Creator. ")}
</Link>
{t(
"There you can customize the theme using their UI, and copy the code to paste it below. Note that clicking the switch to light/dark mode button will reset your theme and that this textbox is cleared on page refresh. ",
)}
</p>
<TextField
value={customTheme}
onChange={(e) => setCustomTheme(e.target.value)}
multiline
fullWidth
></TextField>
<Button
variant="outlined"
disabled={customTheme === ""}
color="success"
onClick={saveCustomTheme}
>
Save
</Button>
<Button variant="outlined" onClick={resetColorMode} color="error">
{t("Reset Theme")}
</Button>
<br />
</>
);
}
// Returns all the user data if logged in and if not logged in redirects to the login page
export const getServerSideProps: GetServerSideProps = async (
ctx: GetServerSidePropsContext,
) => {
const session = await getServerSession(ctx.req, ctx.res, authOptions);
if (session) {
const user = session.user;
// Checks if the user is in any leagues
const anyLeagues =
(await db
.selectFrom("leagueUsers")
.select("user")
.where("user", "=", user.id)
.executeTakeFirst()) !== undefined;
// Checks what providers are supported
return {
props: {
user,
providers: getProviders(),
deleteable: !anyLeagues,
t: await getData(ctx),
},
};
} else {
return {
redirect: {
destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(
ctx.resolvedUrl,
)}`,
permanent: false,
},
};
}
};