added the ability for the users to hide there profile
This commit is contained in:
parent
e774f41d37
commit
240d92aeae
|
@ -10,6 +10,7 @@ import useCollectionStore from "@/store/collections";
|
||||||
import { NewCollection } from "@/types/global";
|
import { NewCollection } from "@/types/global";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import RequiredBadge from "../RequiredBadge";
|
import RequiredBadge from "../RequiredBadge";
|
||||||
|
import getPublicUserDataByEmail from "@/lib/client/getPublicUserDataByEmail";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
toggleCollectionModal: Function;
|
toggleCollectionModal: Function;
|
||||||
|
@ -36,14 +37,6 @@ export default function AddCollection({ toggleCollectionModal }: Props) {
|
||||||
if (response) toggleCollectionModal();
|
if (response) toggleCollectionModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserByEmail = async (email: string) => {
|
|
||||||
const response = await fetch(`/api/routes/users?email=${email}`);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return data.response;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 sm:w-[35rem] w-80">
|
<div className="flex flex-col gap-3 sm:w-[35rem] w-80">
|
||||||
<p className="text-xl text-sky-500 mb-2 text-center">New Collection</p>
|
<p className="text-xl text-sky-500 mb-2 text-center">New Collection</p>
|
||||||
|
@ -107,7 +100,7 @@ export default function AddCollection({ toggleCollectionModal }: Props) {
|
||||||
memberEmail.trim() !== ownerEmail
|
memberEmail.trim() !== ownerEmail
|
||||||
) {
|
) {
|
||||||
// Lookup, get data/err, list ...
|
// Lookup, get data/err, list ...
|
||||||
const user = await getUserByEmail(memberEmail.trim());
|
const user = await getPublicUserDataByEmail(memberEmail.trim());
|
||||||
|
|
||||||
if (user.email) {
|
if (user.email) {
|
||||||
const newMember = {
|
const newMember = {
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
import { AccountSettings } from "@/types/global";
|
import { AccountSettings } from "@/types/global";
|
||||||
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
togglePasswordFormModal: Function;
|
togglePasswordFormModal: Function;
|
||||||
|
@ -70,8 +68,7 @@ export default function AddCollection({
|
||||||
className="mx-auto mt-2 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none font-bold duration-100 bg-sky-500 hover:bg-sky-400 cursor-pointer"
|
className="mx-auto mt-2 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none font-bold duration-100 bg-sky-500 hover:bg-sky-400 cursor-pointer"
|
||||||
onClick={submit}
|
onClick={submit}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faPenToSquare} className="h-5" />
|
Save
|
||||||
Change Password
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,6 +29,9 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
||||||
profilePic: null,
|
profilePic: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [whitelistedUsersTextbox, setWhiteListedUsersTextbox] = useState(
|
||||||
|
user.whitelistedUsers.join(", ")
|
||||||
|
);
|
||||||
const [passwordFormModal, setPasswordFormModal] = useState(false);
|
const [passwordFormModal, setPasswordFormModal] = useState(false);
|
||||||
|
|
||||||
const togglePasswordFormModal = () => {
|
const togglePasswordFormModal = () => {
|
||||||
|
@ -49,6 +52,21 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
||||||
determineProfilePicSource();
|
determineProfilePicSource();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUser({
|
||||||
|
...user,
|
||||||
|
whitelistedUsers: stringToArray(whitelistedUsersTextbox),
|
||||||
|
});
|
||||||
|
}, [whitelistedUsersTextbox]);
|
||||||
|
|
||||||
|
const stringToArray = (str: string) => {
|
||||||
|
const stringWithoutSpaces = str.replace(/\s+/g, "");
|
||||||
|
|
||||||
|
const wordsArray = stringWithoutSpaces.split(",");
|
||||||
|
|
||||||
|
return wordsArray;
|
||||||
|
};
|
||||||
|
|
||||||
const handleImageUpload = async (e: any) => {
|
const handleImageUpload = async (e: any) => {
|
||||||
const file: File = e.target.files[0];
|
const file: File = e.target.files[0];
|
||||||
|
|
||||||
|
@ -77,6 +95,8 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
console.log(user);
|
||||||
|
|
||||||
await updateAccount({
|
await updateAccount({
|
||||||
...user,
|
...user,
|
||||||
profilePic:
|
profilePic:
|
||||||
|
@ -134,6 +154,14 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
||||||
Change Password
|
Change Password
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{user.newPassword && user.oldPassword ? (
|
||||||
|
<p className="text-gray-500 text-sm mt-2">
|
||||||
|
Password modified. Please click{" "}
|
||||||
|
<span className=" whitespace-nowrap">"Apply Settings"</span> to
|
||||||
|
apply the changes..
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -204,23 +232,33 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
||||||
<p className="text-sky-600">Privacy Settings</p>
|
<p className="text-sky-600">Privacy Settings</p>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Limit who can add you to other Collections"
|
label="Make profile private"
|
||||||
state={user.collectionProtection}
|
state={user.isPrivate}
|
||||||
className="text-sm sm:text-base"
|
className="text-sm sm:text-base"
|
||||||
onClick={() =>
|
onClick={() => setUser({ ...user, isPrivate: !user.isPrivate })}
|
||||||
setUser({ ...user, collectionProtection: !user.collectionProtection })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{user.collectionProtection ? (
|
|
||||||
<div>
|
|
||||||
<p className="text-gray-500 text-sm mb-3">
|
<p className="text-gray-500 text-sm mb-3">
|
||||||
Please enter the email addresses of the users who are allowed to add
|
This will limit who can find and add you to other Collections.
|
||||||
you to additional collections in the box below, separated by spaces.
|
</p>
|
||||||
|
|
||||||
|
{user.isPrivate ? (
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||||
|
Whitelisted Users
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-500 text-sm mb-3">
|
||||||
|
Please provide the Email addresses of the users you wish to grant
|
||||||
|
visibility to your profile. Separate the addresses with a comma.
|
||||||
|
Users not included will be unable to view your profile.
|
||||||
</p>
|
</p>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full resize-none border rounded-md duration-100 bg-white p-2 outline-none border-sky-100 focus:border-sky-500"
|
className="w-full resize-none border rounded-md duration-100 bg-white p-2 outline-none border-sky-100 focus:border-sky-500"
|
||||||
placeholder="No one can add you to any collections right now..."
|
placeholder="Your profile is hidden from everyone right now..."
|
||||||
|
value={whitelistedUsersTextbox}
|
||||||
|
onChange={(e) => {
|
||||||
|
setWhiteListedUsersTextbox(e.target.value);
|
||||||
|
}}
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -5,14 +5,26 @@
|
||||||
|
|
||||||
import { prisma } from "@/lib/api/db";
|
import { prisma } from "@/lib/api/db";
|
||||||
|
|
||||||
export default async function (lookupEmail: string, isSelf: boolean) {
|
export default async function (
|
||||||
|
lookupEmail: string,
|
||||||
|
isSelf: boolean,
|
||||||
|
userEmail: string
|
||||||
|
) {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
email: lookupEmail,
|
email: lookupEmail,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) return { response: "User not found." || null, status: 404 };
|
if (!user) return { response: "User not found.", status: 404 };
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isSelf &&
|
||||||
|
user?.isPrivate &&
|
||||||
|
!user.whitelistedUsers.includes(userEmail)
|
||||||
|
) {
|
||||||
|
return { response: "This profile is private.", status: 401 };
|
||||||
|
}
|
||||||
|
|
||||||
const { password, ...unsensitiveInfo } = user;
|
const { password, ...unsensitiveInfo } = user;
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default async function (user: AccountSettings, userId: number) {
|
||||||
data: {
|
data: {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
collectionProtection: user.collectionProtection,
|
isPrivate: user.isPrivate,
|
||||||
whitelistedUsers: user.whitelistedUsers,
|
whitelistedUsers: user.whitelistedUsers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,5 +8,7 @@ export default async function (email: string) {
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
return data.response;
|
return data.response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,41 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getServerSession } from "next-auth/next";
|
import { getServerSession } from "next-auth/next";
|
||||||
import { authOptions } from "pages/api/auth/[...nextauth]";
|
import { authOptions } from "pages/api/auth/[...nextauth]";
|
||||||
|
import { prisma } from "@/lib/api/db";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||||
if (!req.query.id)
|
|
||||||
return res.status(401).json({ response: "Invalid parameters." });
|
|
||||||
|
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
if (!session?.user?.email)
|
const userId = session?.user.id;
|
||||||
|
const userEmail = session?.user.email;
|
||||||
|
const queryId = Number(req.query.id);
|
||||||
|
|
||||||
|
if (!queryId)
|
||||||
|
return res.status(401).json({ response: "Invalid parameters." });
|
||||||
|
|
||||||
|
if (!userId || !userEmail)
|
||||||
return res.status(401).json({ response: "You must be logged in." });
|
return res.status(401).json({ response: "You must be logged in." });
|
||||||
|
|
||||||
// TODO: If profile is private, hide it to other users...
|
if (userId !== queryId) {
|
||||||
|
const targetUser = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: queryId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
targetUser?.isPrivate &&
|
||||||
|
!targetUser.whitelistedUsers.includes(userEmail)
|
||||||
|
) {
|
||||||
|
return res.status(401).json({ response: "This profile is private." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filePath = path.join(
|
const filePath = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`data/uploads/avatar/${req.query.id}.jpg`
|
`data/uploads/avatar/${queryId}.jpg`
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(filePath);
|
console.log(filePath);
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||||
const isSelf = session.user.email === lookupEmail ? true : false;
|
const isSelf = session.user.email === lookupEmail ? true : false;
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
const users = await getUsers(lookupEmail, isSelf);
|
const users = await getUsers(lookupEmail, isSelf, session.user.email);
|
||||||
return res.status(users.status).json({ response: users.response });
|
return res.status(users.status).json({ response: users.response });
|
||||||
} else if (req.method === "PUT" && !req.body.password) {
|
} else if (req.method === "PUT" && !req.body.password) {
|
||||||
const updated = await updateUser(req.body, session.user.id);
|
const updated = await updateUser(req.body, session.user.id);
|
||||||
|
|
|
@ -4,7 +4,7 @@ CREATE TABLE "User" (
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"email" TEXT NOT NULL,
|
"email" TEXT NOT NULL,
|
||||||
"password" TEXT NOT NULL,
|
"password" TEXT NOT NULL,
|
||||||
"collectionProtection" BOOLEAN NOT NULL DEFAULT false,
|
"isPrivate" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"whitelistedUsers" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
"whitelistedUsers" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
|
@ -20,7 +20,7 @@ model User {
|
||||||
collections Collection[]
|
collections Collection[]
|
||||||
tags Tag[]
|
tags Tag[]
|
||||||
collectionsJoined UsersAndCollections[]
|
collectionsJoined UsersAndCollections[]
|
||||||
collectionProtection Boolean @default(false)
|
isPrivate Boolean @default(false)
|
||||||
whitelistedUsers String[] @default([])
|
whitelistedUsers String[] @default([])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
Ŝarĝante…
Reference in New Issue