299 lines
8.7 KiB
TypeScript
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’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>
|
|
);
|
|
}
|