141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
import { Modal } from "../Modal";
|
|
import { useRef, useState } from "preact/hooks";
|
|
import { useQuery } from "react-query";
|
|
import axios from "axios";
|
|
import { fetchUserInfo, uploadImage } from "../../api";
|
|
|
|
export const ImagePicker = ({ alt, url, setImage, ...props }) => {
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
|
|
return (
|
|
<>
|
|
<a
|
|
onClick={() => {
|
|
setModalOpen(true);
|
|
}}
|
|
role={"button"}
|
|
>
|
|
<img {...props} src={url || "/static/img/bg.webp"} alt={alt}></img>
|
|
</a>
|
|
{modalOpen && (
|
|
<ImagePickerModal
|
|
setModalOpen={setModalOpen}
|
|
setImage={setImage}
|
|
url={url}
|
|
></ImagePickerModal>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
const ImagePickerModal = ({ setModalOpen, setImage, url }) => {
|
|
const [value, setValue] = useState(url || "");
|
|
const [uploading, setUploading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [blocked, setBlocked] = useState(false);
|
|
const fileInput = useRef<HTMLInputElement>(null);
|
|
const { data: userInfo } = useQuery(fetchUserInfo());
|
|
|
|
const upload = async (file: File) => {
|
|
setError("");
|
|
if (file.size > 8 * 1024 * 1024) {
|
|
setError("Images must be no larger than 8 MiB.");
|
|
return;
|
|
}
|
|
setUploading(true);
|
|
try {
|
|
setValue(await uploadImage(file));
|
|
} catch (error) {
|
|
if (axios.isAxiosError(error) && error.response?.status === 451) {
|
|
setBlocked(true);
|
|
} else {
|
|
setError(
|
|
axios.isAxiosError(error) && typeof error.response?.data?.error === "string"
|
|
? error.response.data.error
|
|
: "Could not upload image. Please try again.",
|
|
);
|
|
}
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
if (blocked) {
|
|
return (
|
|
<Modal
|
|
setModalOpen={() => setBlocked(false)}
|
|
title="Image upload blocked"
|
|
onSubmit={() => setBlocked(false)}
|
|
onSubmitText="OK"
|
|
>
|
|
<p>
|
|
This image was flagged as probably NSFW (not safe for work) and cannot be
|
|
uploaded. Please choose a different image that does not contain sexual or
|
|
explicit content.
|
|
</p>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
setModalOpen={setModalOpen}
|
|
title={"Enter Image URL"}
|
|
onSubmit={() => {
|
|
setImage(value);
|
|
setModalOpen(false);
|
|
}}
|
|
onSubmitText={"Save"}
|
|
onSubmitDisabled={uploading}
|
|
>
|
|
<p>
|
|
Please note: if you attach an image directly from Discord, it will not be visible in
|
|
the dashboard, but will be visible on reminders. Other image-sharing sites such as
|
|
Imgur don't have this issue.
|
|
</p>
|
|
<input
|
|
class="input"
|
|
id="urlInput"
|
|
placeholder="Image URL..."
|
|
value={value}
|
|
disabled={uploading}
|
|
onInput={(ev) => {
|
|
setValue(ev.currentTarget.value);
|
|
}}
|
|
></input>
|
|
{userInfo?.image_uploads && (
|
|
<div class="mt-4">
|
|
<input
|
|
ref={fileInput}
|
|
type="file"
|
|
accept="image/png,image/jpeg,image/gif,image/webp"
|
|
hidden
|
|
disabled={uploading}
|
|
aria-label="Choose an image to upload"
|
|
onChange={(event) => {
|
|
const file = event.currentTarget.files?.[0];
|
|
event.currentTarget.value = "";
|
|
if (file) void upload(file);
|
|
}}
|
|
/>
|
|
<button
|
|
type="button"
|
|
class={`button is-info ${uploading ? "is-loading" : ""}`}
|
|
disabled={uploading}
|
|
onClick={() => fileInput.current?.click()}
|
|
>
|
|
Upload image
|
|
</button>
|
|
<p class="help">PNG, JPEG, GIF or WebP, up to 8 MiB. Saved as a static PNG.</p>
|
|
{uploading && <p role="status">Checking and uploading image…</p>}
|
|
</div>
|
|
)}
|
|
{error && (
|
|
<p class="help is-danger" role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</Modal>
|
|
);
|
|
};
|