refactored how avatars are being handled
This commit is contained in:
parent
f9eedadb9f
commit
cdcfabec0b
|
@ -68,7 +68,7 @@ export default function CollectionCard({ collection, className }: Props) {
|
|||
return (
|
||||
<ProfilePhoto
|
||||
key={i}
|
||||
src={`/api/v1/avatar/${e.userId}?${Date.now()}`}
|
||||
src={e.user.image ? e.user.image : undefined}
|
||||
className="-mr-3 border-[3px]"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -238,7 +238,7 @@ export default function TeamManagement({
|
|||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<ProfilePhoto
|
||||
src={`/api/v1/avatar/${e.userId}?${Date.now()}`}
|
||||
src={e.user.image ? e.user.image : undefined}
|
||||
className="border-[3px]"
|
||||
/>
|
||||
<div>
|
||||
|
@ -425,7 +425,7 @@ export default function TeamManagement({
|
|||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ProfilePhoto
|
||||
src={`/api/v1/avatar/${collection.ownerId}?${Date.now()}`}
|
||||
src={`uploads/avatar/${collection.ownerId}.jpg`}
|
||||
className="border-[3px]"
|
||||
/>
|
||||
<div>
|
||||
|
|
|
@ -83,7 +83,7 @@ export default function Navbar() {
|
|||
id="profile-dropdown"
|
||||
>
|
||||
<ProfilePhoto
|
||||
src={account.profilePic}
|
||||
src={account.image ? account.image : undefined}
|
||||
priority={true}
|
||||
className="sm:group-hover:h-8 sm:group-hover:w-8 duration-100 border-[3px]"
|
||||
/>
|
||||
|
|
|
@ -2,38 +2,28 @@ import React, { useEffect, useState } from "react";
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faUser } from "@fortawesome/free-solid-svg-icons";
|
||||
import Image from "next/image";
|
||||
import avatarExists from "@/lib/client/avatarExists";
|
||||
|
||||
type Props = {
|
||||
src: string;
|
||||
src?: string;
|
||||
className?: string;
|
||||
emptyImage?: boolean;
|
||||
status?: Function;
|
||||
priority?: boolean;
|
||||
};
|
||||
|
||||
export default function ProfilePhoto({
|
||||
src,
|
||||
className,
|
||||
emptyImage,
|
||||
status,
|
||||
priority,
|
||||
}: Props) {
|
||||
const [error, setError] = useState<boolean>(emptyImage || true);
|
||||
|
||||
const checkAvatarExistence = async () => {
|
||||
const canPass = await avatarExists(src);
|
||||
|
||||
setError(!canPass);
|
||||
};
|
||||
export default function ProfilePhoto({ src, className, priority }: Props) {
|
||||
const [image, setImage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (src) checkAvatarExistence();
|
||||
console.log(src);
|
||||
if (src && !src?.includes("base64"))
|
||||
setImage(`/api/v1/${src.replace("uploads/", "").replace(".jpg", "")}`);
|
||||
else if (!src) setImage("");
|
||||
else {
|
||||
setImage(src);
|
||||
}
|
||||
}, [src]);
|
||||
|
||||
status && status(error || !src);
|
||||
}, [src, error]);
|
||||
|
||||
return error || !src ? (
|
||||
return !image ? (
|
||||
<div
|
||||
className={`bg-sky-600 dark:bg-sky-600 text-white h-10 w-10 aspect-square shadow rounded-full border border-slate-200 dark:border-neutral-700 flex items-center justify-center ${className}`}
|
||||
>
|
||||
|
@ -42,7 +32,7 @@ export default function ProfilePhoto({
|
|||
) : (
|
||||
<Image
|
||||
alt=""
|
||||
src={src}
|
||||
src={image}
|
||||
height={112}
|
||||
width={112}
|
||||
priority={priority}
|
||||
|
|
|
@ -18,6 +18,7 @@ export default async function getCollection(userId: number) {
|
|||
select: {
|
||||
username: true,
|
||||
name: true,
|
||||
image: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -42,6 +42,7 @@ export default async function getPublicUserById(
|
|||
const data = {
|
||||
name: lessSensitiveInfo.name,
|
||||
username: lessSensitiveInfo.username,
|
||||
image: lessSensitiveInfo.image,
|
||||
};
|
||||
|
||||
return { response: data, status: 200 };
|
||||
|
|
|
@ -89,12 +89,10 @@ export default async function updateUserById(
|
|||
|
||||
// Avatar Settings
|
||||
|
||||
const profilePic = data.profilePic;
|
||||
|
||||
if (profilePic.startsWith("data:image/jpeg;base64")) {
|
||||
if (data.profilePic.length < 1572864) {
|
||||
if (data.image?.startsWith("data:image/jpeg;base64")) {
|
||||
if (data.image.length < 1572864) {
|
||||
try {
|
||||
const base64Data = profilePic.replace(/^data:image\/jpeg;base64,/, "");
|
||||
const base64Data = data.image.replace(/^data:image\/jpeg;base64,/, "");
|
||||
|
||||
createFolder({ filePath: `uploads/avatar` });
|
||||
|
||||
|
@ -113,7 +111,7 @@ export default async function updateUserById(
|
|||
status: 400,
|
||||
};
|
||||
}
|
||||
} else if (profilePic == "") {
|
||||
} else if (data.image == "") {
|
||||
removeFile({ filePath: `uploads/avatar/${sessionUser.id}.jpg` });
|
||||
}
|
||||
|
||||
|
@ -131,6 +129,7 @@ export default async function updateUserById(
|
|||
username: data.username.toLowerCase().trim(),
|
||||
email: data.email?.toLowerCase().trim(),
|
||||
isPrivate: data.isPrivate,
|
||||
image: data.image ? `uploads/avatar/${sessionUser.id}.jpg` : "",
|
||||
archiveAsScreenshot: data.archiveAsScreenshot,
|
||||
archiveAsPDF: data.archiveAsPDF,
|
||||
archiveAsWaybackMachine: data.archiveAsWaybackMachine,
|
||||
|
@ -197,7 +196,7 @@ export default async function updateUserById(
|
|||
const response: Omit<AccountSettings, "password"> = {
|
||||
...userInfo,
|
||||
whitelistedUsers: newWhitelistedUsernames,
|
||||
profilePic: `/api/v1/avatar/${userInfo.id}?${Date.now()}`,
|
||||
image: userInfo.image ? `${userInfo.image}?${Date.now()}` : "",
|
||||
};
|
||||
|
||||
return { response, status: 200 };
|
||||
|
|
|
@ -70,7 +70,7 @@ async function migrateToV2() {
|
|||
if (res) {
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { imagePath: path },
|
||||
data: { image: path },
|
||||
});
|
||||
console.log(`Updated avatar for avatar ${user.id}`);
|
||||
} else {
|
||||
|
|
|
@ -9,14 +9,12 @@ import s3Client from "./s3Client";
|
|||
import util from "util";
|
||||
|
||||
type ReturnContentTypes =
|
||||
| "text/html"
|
||||
| "text/plain"
|
||||
| "image/jpeg"
|
||||
| "image/png"
|
||||
| "application/pdf";
|
||||
|
||||
export default async function readFile(filePath: string) {
|
||||
const isRequestingAvatar = filePath.startsWith("uploads/avatar");
|
||||
|
||||
let contentType: ReturnContentTypes;
|
||||
|
||||
if (s3Client) {
|
||||
|
@ -41,12 +39,12 @@ export default async function readFile(filePath: string) {
|
|||
try {
|
||||
await headObjectAsync(bucketParams);
|
||||
} catch (err) {
|
||||
contentType = "text/html";
|
||||
contentType = "text/plain";
|
||||
|
||||
returnObject = {
|
||||
file: isRequestingAvatar ? "File not found." : fileNotFoundTemplate,
|
||||
file: "File not found.",
|
||||
contentType,
|
||||
status: isRequestingAvatar ? 200 : 400,
|
||||
status: 400,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -71,9 +69,9 @@ export default async function readFile(filePath: string) {
|
|||
} catch (err) {
|
||||
console.log("Error:", err);
|
||||
|
||||
contentType = "text/html";
|
||||
contentType = "text/plain";
|
||||
return {
|
||||
file: "An internal occurred, please contact support.",
|
||||
file: "An internal occurred, please contact the support team.",
|
||||
contentType,
|
||||
};
|
||||
}
|
||||
|
@ -92,9 +90,9 @@ export default async function readFile(filePath: string) {
|
|||
|
||||
if (!fs.existsSync(creationPath))
|
||||
return {
|
||||
file: isRequestingAvatar ? "File not found." : fileNotFoundTemplate,
|
||||
contentType: "text/html",
|
||||
status: isRequestingAvatar ? 200 : 400,
|
||||
file: "File not found.",
|
||||
contentType: "text/plain",
|
||||
status: 400,
|
||||
};
|
||||
else {
|
||||
const file = fs.readFileSync(creationPath);
|
||||
|
|
|
@ -35,6 +35,7 @@ const addMemberToCollection = async (
|
|||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
image: user.image,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
const avatarCache = new Map();
|
||||
|
||||
export default async function avatarExists(fileUrl: string): Promise<boolean> {
|
||||
if (avatarCache.has(fileUrl)) {
|
||||
return avatarCache.get(fileUrl);
|
||||
}
|
||||
|
||||
const response = await fetch(fileUrl, { method: "HEAD" });
|
||||
const exists = !(response.headers.get("content-type") === "text/html");
|
||||
|
||||
avatarCache.set(fileUrl, exists);
|
||||
return exists;
|
||||
}
|
|
@ -5,6 +5,8 @@ export default async function getPublicUserData(id: number | string) {
|
|||
|
||||
const data = await response.json();
|
||||
|
||||
console.log(data);
|
||||
|
||||
if (!response.ok) toast.error(data.response);
|
||||
|
||||
return data.response;
|
||||
|
|
|
@ -3,6 +3,7 @@ const nextConfig = {
|
|||
reactStrictMode: true,
|
||||
images: {
|
||||
domains: ["t2.gstatic.com"],
|
||||
minimumCacheTTL: 10,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
if (!userId || !username)
|
||||
return res
|
||||
.setHeader("Content-Type", "text/html")
|
||||
.setHeader("Content-Type", "text/plain")
|
||||
.status(401)
|
||||
.send("You must be logged in.");
|
||||
else if (session?.user?.isSubscriber === false)
|
||||
|
@ -24,7 +24,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
if (!queryId)
|
||||
return res
|
||||
.setHeader("Content-Type", "text/html")
|
||||
.setHeader("Content-Type", "text/plain")
|
||||
.status(401)
|
||||
.send("Invalid parameters.");
|
||||
|
||||
|
@ -44,8 +44,9 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
if (targetUser?.isPrivate && !whitelistedUsernames?.includes(username)) {
|
||||
return res
|
||||
.setHeader("Content-Type", "text/html")
|
||||
.send("This profile is private.");
|
||||
.setHeader("Content-Type", "text/plain")
|
||||
.status(400)
|
||||
.send("File not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ export default function Index() {
|
|||
return (
|
||||
<ProfilePhoto
|
||||
key={i}
|
||||
src={`/api/v1/avatar/${e.userId}?${Date.now()}`}
|
||||
src={e.user.image ? e.user.image : undefined}
|
||||
className="-mr-3 border-[3px]"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -15,11 +15,8 @@ import useModalStore from "@/store/modals";
|
|||
import SortDropdown from "@/components/SortDropdown";
|
||||
import { Sort } from "@/types/global";
|
||||
import useSort from "@/hooks/useSort";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
export default function Collections() {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const { collections } = useCollectionStore();
|
||||
const [expandDropdown, setExpandDropdown] = useState(false);
|
||||
const [sortDropdown, setSortDropdown] = useState(false);
|
||||
|
|
|
@ -21,13 +21,8 @@ export default function Account() {
|
|||
|
||||
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
|
||||
|
||||
const [profileStatus, setProfileStatus] = useState(true);
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
|
||||
const handleProfileStatus = (e: boolean) => {
|
||||
setProfileStatus(!e);
|
||||
};
|
||||
|
||||
const { account, updateAccount } = useAccountStore();
|
||||
|
||||
const [user, setUser] = useState<AccountSettings>(
|
||||
|
@ -40,12 +35,11 @@ export default function Account() {
|
|||
username: "",
|
||||
email: "",
|
||||
emailVerified: null,
|
||||
image: null,
|
||||
image: "",
|
||||
isPrivate: true,
|
||||
// @ts-ignore
|
||||
createdAt: null,
|
||||
whitelistedUsers: [],
|
||||
profilePic: "",
|
||||
} as unknown as AccountSettings)
|
||||
);
|
||||
|
||||
|
@ -54,6 +48,7 @@ export default function Account() {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(account);
|
||||
if (!objectIsEmpty(account)) setUser({ ...account });
|
||||
}, [account]);
|
||||
|
||||
|
@ -68,7 +63,7 @@ export default function Account() {
|
|||
) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
setUser({ ...user, profilePic: reader.result as string });
|
||||
setUser({ ...user, image: reader.result as string });
|
||||
};
|
||||
reader.readAsDataURL(resizedFile);
|
||||
} else {
|
||||
|
@ -220,16 +215,15 @@ export default function Account() {
|
|||
<div className="w-28 h-28 flex items-center justify-center rounded-full relative">
|
||||
<ProfilePhoto
|
||||
priority={true}
|
||||
src={user.profilePic}
|
||||
src={user.image ? user.image : undefined}
|
||||
className="h-auto border-none w-28"
|
||||
status={handleProfileStatus}
|
||||
/>
|
||||
{profileStatus && (
|
||||
{user.image && (
|
||||
<div
|
||||
onClick={() =>
|
||||
setUser({
|
||||
...user,
|
||||
profilePic: "",
|
||||
image: "",
|
||||
})
|
||||
}
|
||||
className="absolute top-1 left-1 w-5 h-5 flex items-center justify-center border p-1 border-slate-200 dark:border-neutral-700 rounded-full bg-white dark:bg-neutral-800 text-center select-none cursor-pointer duration-100 hover:text-red-500"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE "User" RENAME COLUMN "imagePath" TO "image";
|
|
@ -15,7 +15,7 @@ model User {
|
|||
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
imagePath String?
|
||||
image String?
|
||||
|
||||
password String
|
||||
collections Collection[]
|
||||
|
|
|
@ -19,9 +19,7 @@ const useAccountStore = create<AccountStore>()((set) => ({
|
|||
|
||||
const data = await response.json();
|
||||
|
||||
const profilePic = `/api/v1/avatar/${data.response.id}?${Date.now()}`;
|
||||
|
||||
if (response.ok) set({ account: { ...data.response, profilePic } });
|
||||
if (response.ok) set({ account: { ...data.response } });
|
||||
},
|
||||
updateAccount: async (user) => {
|
||||
const response = await fetch(`/api/v1/users/${user.id}`, {
|
||||
|
|
|
@ -35,7 +35,6 @@ export interface CollectionIncludingMembersAndLinkCount
|
|||
}
|
||||
|
||||
export interface AccountSettings extends User {
|
||||
profilePic: string;
|
||||
newPassword?: string;
|
||||
whitelistedUsers: string[];
|
||||
}
|
||||
|
@ -79,7 +78,7 @@ interface CollectionIncludingLinks extends Collection {
|
|||
links: LinksIncludingTags[];
|
||||
}
|
||||
|
||||
export interface Backup extends Omit<User, "password" | "id" | "image"> {
|
||||
export interface Backup extends Omit<User, "password" | "id"> {
|
||||
collections: CollectionIncludingLinks[];
|
||||
}
|
||||
|
||||
|
|
Ŝarĝante…
Reference in New Issue