gluestick/src/app/me/AboutMe.tsx

299 lines
8.7 KiB
TypeScript

/* eslint-disable @next/next/no-img-element */
"use client";
import { UserInfo } from "@/ldap";
import React from "react";
import styles from "./AboutMe.module.css";
import AvatarChanger from "@/components/AvatarChanger";
import Input, { Label } from "@/components/Input";
import { Form, Formik, FormikHelpers } from "formik";
import {
AboutMeFormValues,
PasswordUpdateFormValues,
aboutMeSchema,
passwordUpdateSchema
} from "@/schemas";
import PrettyForm from "@/components/PrettyForm";
import Toast from "@/components/Toast";
import { AuthProviderState } from "@/auth/AuthProvider";
import Connection from "@/components/Connection";
import DiscordIcon from "@/components/icons/DiscordIcon";
import GitHubIcon from "@/components/icons/GitHubIcon";
import TailscaleIcon from "@/components/icons/TailscaleIcon";
import MigaduIcon from "@/components/icons/MigaduIcon";
type UpdateResponse = {
ok: boolean;
error?: string;
};
export default function AboutMe({
info,
providers: [discordState, githubState]
}: {
info: UserInfo;
providers: AuthProviderState[];
}) {
// TODO: Reimplement password changing.
const [globalError, setGlobalError] = React.useState<string | null>(null);
const [madeProfileChanges, setMadeChanges] = React.useState(false);
const [madePasswordChanges, setMadePasswordChanges] = React.useState(false);
const initialValues: AboutMeFormValues = {
username: info.username,
displayName: info.displayName,
email: info.email,
avatar: info.avatar
};
async function handleFormSubmit(
{ displayName, email, avatar }: AboutMeFormValues,
{ setSubmitting }: FormikHelpers<AboutMeFormValues>
) {
setMadeChanges(false);
setSubmitting(true);
const req = await fetch("/api/update", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
displayName,
email,
avatar: avatar != null ? avatar.split(",")[1] : null
})
});
setSubmitting(false);
try {
const res: UpdateResponse = await req.json();
if (!res.ok && res.error !== null) {
switch (res.error) {
case "avatarBig":
break;
}
}
setMadeChanges(true);
} catch {
console.error(req);
}
}
const [passwordError, setPasswordError] = React.useState<string | null>(null);
const initialPasswordValues: PasswordUpdateFormValues = {
password: "",
newPassword: "",
confirmPassword: ""
};
async function handlePasswordSubmit(
{ password, newPassword }: PasswordUpdateFormValues,
{ setFieldError, setSubmitting }: FormikHelpers<PasswordUpdateFormValues>
) {
setMadePasswordChanges(false);
setSubmitting(true);
const req = await fetch("/api/changePassword", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
currentPassword: password,
newPassword: newPassword
})
});
setSubmitting(false);
try {
const res: UpdateResponse = await req.json();
if (!res.ok && res.error !== null) {
switch (res.error) {
case "incorrectPassword":
setFieldError("password", "Incorrect password.");
break;
}
}
setMadePasswordChanges(true);
} catch {
console.error(req);
}
}
return (
<div className={styles.content}>
<PrettyForm globalError={globalError}>
<Formik
initialValues={initialValues}
onSubmit={handleFormSubmit}
validationSchema={aboutMeSchema}
>
{({ isSubmitting }) => (
<Form className={styles.profileGrid}>
<div className={styles.profileTower}>
<Input
type="file"
name="avatar"
customRender={(fieldProps) => (
<AvatarChanger
currentAvatarBlob={fieldProps.field.value}
onChange={(newBlob) =>
fieldProps.form.setFieldValue("avatar", newBlob)
}
vertical
/>
)}
/>
</div>
<div>
<h2 className={styles.userName}>{info.username}</h2>
<div className={styles.rightGrid}>
<div>
{madeProfileChanges ? (
<Toast>Saved your changes.</Toast>
) : null}
<Input
type="text"
name="username"
label="Username"
defaultValue={info.username}
disabled
hint="This can&rsquo;t be changed."
/>
<Input
type="text"
name="displayName"
label="Display name"
defaultValue={info.displayName}
/>
<Input
type="email"
name="email"
label="Email"
defaultValue={info.email}
/>
<hr className={styles.spacer} />
<input
type="submit"
value="Save"
className={styles.fancyInput}
disabled={isSubmitting}
/>
<div className={styles.multiButtons}>
<button type="button">Change Password</button>
<button
type="button"
onClick={async () => {
document.cookie =
"ticket=; expires=" +
new Date().toUTCString() +
"; path=/";
window.location.href = "/";
}}
>
Log out
</button>
</div>
</div>
<div className={styles.connections}>
<Label>Connections</Label>
<Connection
service="Discord"
authState={discordState}
icon={DiscordIcon}
/>
<Connection
service="GitHub"
authState={githubState}
icon={GitHubIcon}
/>
<Connection
service="Tailscale"
icon={TailscaleIcon}
unavailable
/>
<Connection
service="Migadu"
icon={MigaduIcon}
unavailable
/>
</div>
</div>
</div>
</Form>
)}
</Formik>
</PrettyForm>
{/*<PrettyForm globalError={passwordError}>
<Formik
initialValues={initialPasswordValues}
onSubmit={handlePasswordSubmit}
validationSchema={passwordUpdateSchema}
>
{({ isSubmitting }) => (
<Form>
{madePasswordChanges ? (
<Toast>Changed your password.</Toast>
) : null}
<Input
type="password"
name="password"
label="Current"
minLength={12}
required
/>
<Input
type="password"
name="newPassword"
label="New"
minLength={12}
required
/>
<Input
type="password"
name="confirmPassword"
label="Confirm"
minLength={12}
required
/>
<input
type="submit"
value="Save"
className={styles.fancyInput}
disabled={isSubmitting}
/>
</Form>
)}
</Formik>
</PrettyForm>
<h2 className={styles.header}>Connections</h2>
<div className={styles.authProviderList}>
{providers.map((provider) => (
<AuthProviderEntry provider={provider} key={provider.name} />
))}
</div>
<input
type="button"
value="Log out"
className={styles.logout}
onClick={async () => {
document.cookie =
"ticket=; expires=" + new Date().toUTCString() + "; path=/";
window.location.href = "/";
}}
/>*/}
</div>
);
}