gluestick/src/app/me/AboutMe.tsx

299 lines
8.2 KiB
TypeScript
Raw Normal View History

2023-04-26 13:56:59 -04:00
/* eslint-disable @next/next/no-img-element */
"use client";
import { UserInfo } from "@/ldap";
import React, { HTMLInputTypeAttribute, InputHTMLAttributes } from "react";
import styles from "./AboutMe.module.css";
const fallbackAvatar = "https://i.clong.biz/i/oc4zjlqr.png";
type UpdateResponse = {
ok: boolean;
error?: string;
};
// TODO skip do your magic
type InputProps = {
label: string;
name: string;
type: HTMLInputTypeAttribute;
error?: string;
displayImage?: string;
} & InputHTMLAttributes<HTMLInputElement>;
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
// get console to shut up
const inputProps = { ...props };
delete inputProps.displayImage;
return (
<div className={styles.formRow}>
<label htmlFor={props.id}>{props.label}</label>
{props.displayImage && (
<img
src={props.displayImage}
className={styles.avatar}
alt={"Your avatar"}
width="50px"
height="50px"
/>
)}
<div className={styles.formVert}>
<input {...inputProps} ref={ref} className={styles.fancyInput} />
{props.error != null && <p className={styles.error}>{props.error}</p>}
</div>
</div>
);
});
Input.displayName = "Input";
async function fileAsBase64(f: File) {
const reader = new FileReader();
reader.readAsArrayBuffer(f);
return new Promise<string>((resolve, reject) => {
reader.onload = () => {
const result = reader.result as ArrayBuffer;
const buffer = Buffer.from(result);
resolve(buffer.toString("base64"));
};
reader.onerror = () => reject(reader.error);
});
}
export default function AboutMe({ info }: { info: UserInfo }) {
const displayNameRef = React.useRef<HTMLInputElement>(null);
const emailRef = React.useRef<HTMLInputElement>(null);
const avatarRef = React.useRef<HTMLInputElement>(null);
const submitRef = React.useRef<HTMLInputElement>(null);
const [avatar, setAvatar] = React.useState<string | null>(
info.avatar ?? null
);
const currentPasswordRef = React.useRef<HTMLInputElement>(null);
const newPasswordRef = React.useRef<HTMLInputElement>(null);
const confirmPasswordRef = React.useRef<HTMLInputElement>(null);
const submitPasswordRef = React.useRef<HTMLInputElement>(null);
const [incorrectPassword, setIncorrectPassword] = React.useState(false);
const [passwordMismatch, setPasswordMismatch] = React.useState(false);
const [avatarBig, setAvatarBig] = React.useState(false);
return (
<div className={styles.content}>
<h2 className={styles.header}>User information</h2>
<form
onSubmit={async (e) => {
e.preventDefault();
// turn the data uri into just base64
const avatarChanged = avatar !== null && avatar !== info.avatar;
const avatarData = avatarChanged ? avatar?.split(",")[1] : null;
submitRef.current!.disabled = true;
const req = await fetch("/api/update", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
displayName: displayNameRef.current?.value,
email: emailRef.current?.value,
avatar: avatarData
})
});
submitRef.current!.disabled = false;
try {
const res: UpdateResponse = await req.json();
if (!res.ok && res.error !== null) {
switch (res.error) {
case "avatarBig":
setAvatarBig(true);
break;
}
}
} catch {
console.error(req);
}
}}
>
<Input
type="text"
name="username"
label="Username"
defaultValue={info.username}
disabled
title="You can't change your username."
/>
<Input
type="text"
name="display-name"
label="Display name"
defaultValue={info.displayName}
ref={displayNameRef}
/>
<Input
type="email"
name="email"
label="Email"
defaultValue={info.email}
ref={emailRef}
/>
{/* why, html gods, why? */}
<input
type="file"
name="avatar"
accept="image/png, image/jpeg"
ref={avatarRef}
style={{ display: "none" }}
/>
<Input
type="button"
value="Choose file"
name="avatar"
label="Avatar"
accept="image/png, image/jpeg"
error={avatarBig ? "Avatar is too big." : undefined}
onClick={() => {
avatarRef.current?.click();
const eventListener = async () => {
avatarRef.current?.removeEventListener("change", eventListener);
const file = avatarRef.current?.files?.[0];
if (file == null) return;
if (file.size > 1_000_000) {
setAvatarBig(true);
return;
} else {
setAvatarBig(false);
}
const b64 = await fileAsBase64(file);
setAvatar(`data:${file.type};base64,${b64}`);
};
avatarRef.current?.addEventListener("change", eventListener);
}}
displayImage={avatar ?? fallbackAvatar}
/>
<div className={styles.formRow}>
<input
type="submit"
value="Save"
ref={submitRef}
className={styles.fancyInput}
/>
</div>
</form>
<hr className={styles.divider} />
<h2 className={styles.header}>Change password</h2>
<form
onSubmit={async (e) => {
e.preventDefault();
setIncorrectPassword(false);
setPasswordMismatch(false);
if (
newPasswordRef.current?.value !== confirmPasswordRef.current?.value
) {
setPasswordMismatch(true);
return;
}
submitPasswordRef.current!.disabled = true;
const req = await fetch("/api/changePassword", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
currentPassword: currentPasswordRef.current?.value,
newPassword: newPasswordRef.current?.value
})
});
submitPasswordRef.current!.disabled = false;
try {
const res: UpdateResponse = await req.json();
if (!res.ok && res.error !== null) {
switch (res.error) {
case "incorrectPassword":
setIncorrectPassword(true);
break;
}
}
} catch {
console.error(req);
}
}}
>
<Input
type="password"
name="current-password"
label="Current"
minLength={12}
required
ref={currentPasswordRef}
error={incorrectPassword ? "Incorrect password." : undefined}
/>
<Input
type="password"
name="new-password"
label="New"
minLength={12}
required
ref={newPasswordRef}
/>
<Input
type="password"
name="confirm-password"
label="Confirm"
ref={confirmPasswordRef}
minLength={12}
required
error={passwordMismatch ? "Passwords do not match." : undefined}
/>
<div className={styles.formRow}>
<input
type="submit"
value="Change password"
ref={submitPasswordRef}
className={styles.fancyInput}
/>
</div>
</form>
2023-04-26 21:21:28 -04:00
<hr className={styles.divider} />
<div className={styles.formRow}>
<input
type="button"
value="Log out"
className={styles.fancyInput}
onClick={async () => {
document.cookie =
"ticket=; expires=" + new Date().toUTCString() + "; path=/";
window.location.href = "/";
}}
/>
</div>
2023-04-26 13:56:59 -04:00
</div>
);
}