refactored/cleaned up API + added support for renaming tags
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ export default function App({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
<SessionProvider session={pageProps.session} basePath="/api/v1/auth">
|
||||
<Head>
|
||||
<title>Linkwarden</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const emailEnabled =
|
||||
process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;
|
||||
|
||||
interface Data {
|
||||
response: string | object;
|
||||
}
|
||||
|
||||
interface User {
|
||||
name: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export default async function Index(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
if (process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true") {
|
||||
return res.status(400).json({ response: "Registration is disabled." });
|
||||
}
|
||||
|
||||
const body: User = req.body;
|
||||
|
||||
const checkHasEmptyFields = emailEnabled
|
||||
? !body.password || !body.name || !body.email
|
||||
: !body.username || !body.password || !body.name;
|
||||
|
||||
if (checkHasEmptyFields)
|
||||
return res
|
||||
.status(400)
|
||||
.json({ response: "Please fill out all the fields." });
|
||||
|
||||
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
|
||||
|
||||
if (!emailEnabled && !checkUsername.test(body.username?.toLowerCase() || ""))
|
||||
return res.status(400).json({
|
||||
response:
|
||||
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
|
||||
});
|
||||
|
||||
const checkIfUserExists = await prisma.user.findFirst({
|
||||
where: emailEnabled
|
||||
? {
|
||||
email: body.email?.toLowerCase(),
|
||||
emailVerified: { not: null },
|
||||
}
|
||||
: {
|
||||
username: (body.username as string).toLowerCase(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!checkIfUserExists) {
|
||||
const saltRounds = 10;
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(body.password, saltRounds);
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
name: body.name,
|
||||
username: emailEnabled
|
||||
? undefined
|
||||
: (body.username as string).toLowerCase(),
|
||||
email: emailEnabled ? body.email?.toLowerCase() : undefined,
|
||||
password: hashedPassword,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(201).json({ response: "User successfully created." });
|
||||
} else if (checkIfUserExists) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ response: "Username and/or Email already exists." });
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import getUsers from "@/lib/api/controllers/users/getUsers";
|
||||
import updateUser from "@/lib/api/controllers/users/updateUser";
|
||||
|
||||
export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session?.user.id) {
|
||||
return res.status(401).json({ response: "You must be logged in." });
|
||||
} else if (session?.user?.isSubscriber === false)
|
||||
res.status(401).json({
|
||||
response:
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
const lookupUsername = (req.query.username as string) || undefined;
|
||||
const lookupId = Number(req.query.id) || undefined;
|
||||
const isSelf =
|
||||
session.user.username === lookupUsername || session.user.id === lookupId
|
||||
? true
|
||||
: false;
|
||||
|
||||
if (req.method === "GET") {
|
||||
const users = await getUsers({
|
||||
params: {
|
||||
lookupUsername,
|
||||
lookupId,
|
||||
},
|
||||
isSelf,
|
||||
username: session.user.username,
|
||||
});
|
||||
return res.status(users.status).json({ response: users.response });
|
||||
} else if (req.method === "PUT") {
|
||||
const updated = await updateUser(req.body, session.user);
|
||||
return res.status(updated.status).json({ response: updated.response });
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import getPermission from "@/lib/api/getPermission";
|
||||
import readFile from "@/lib/api/storage/readFile";
|
||||
|
||||
@@ -21,10 +21,10 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
const collectionIsAccessible = await getPermission(
|
||||
session.user.id,
|
||||
Number(collectionId)
|
||||
);
|
||||
const collectionIsAccessible = await getPermission({
|
||||
userId: session.user.id,
|
||||
collectionId: Number(collectionId),
|
||||
});
|
||||
|
||||
if (!collectionIsAccessible)
|
||||
return res
|
||||
@@ -88,8 +88,7 @@ export const authOptions: AuthOptions = {
|
||||
|
||||
return session;
|
||||
},
|
||||
// Using the `...rest` parameter to be able to narrow down the type based on `trigger`
|
||||
async jwt({ token, trigger, session, user }) {
|
||||
async jwt({ token, trigger, user }) {
|
||||
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
|
||||
|
||||
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import { prisma } from "@/lib/api/db";
|
||||
import readFile from "@/lib/api/storage/readFile";
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import updateCollectionById from "@/lib/api/controllers/collections/collectionId/updateCollectionById";
|
||||
import deleteCollectionById from "@/lib/api/controllers/collections/collectionId/deleteCollectionById";
|
||||
|
||||
export default async function collections(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return res.status(401).json({ response: "You must be logged in." });
|
||||
} else if (session?.user?.isSubscriber === false)
|
||||
res.status(401).json({
|
||||
response:
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
if (req.method === "PUT") {
|
||||
const updated = await updateCollectionById(
|
||||
session.user.id,
|
||||
Number(req.query.id) as number,
|
||||
req.body
|
||||
);
|
||||
return res.status(updated.status).json({ response: updated.response });
|
||||
} else if (req.method === "DELETE") {
|
||||
const deleted = await deleteCollectionById(
|
||||
session.user.id,
|
||||
Number(req.query.id) as number
|
||||
);
|
||||
return res.status(deleted.status).json({ response: deleted.response });
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import getCollections from "@/lib/api/controllers/collections/getCollections";
|
||||
import postCollection from "@/lib/api/controllers/collections/postCollection";
|
||||
import updateCollection from "@/lib/api/controllers/collections/updateCollection";
|
||||
import deleteCollection from "@/lib/api/controllers/collections/deleteCollection";
|
||||
|
||||
export default async function collections(
|
||||
req: NextApiRequest,
|
||||
@@ -30,11 +28,5 @@ export default async function collections(
|
||||
return res
|
||||
.status(newCollection.status)
|
||||
.json({ response: newCollection.response });
|
||||
} else if (req.method === "PUT") {
|
||||
const updated = await updateCollection(req.body, session.user.id);
|
||||
return res.status(updated.status).json({ response: updated.response });
|
||||
} else if (req.method === "DELETE") {
|
||||
const deleted = await deleteCollection(req.body, session.user.id);
|
||||
return res.status(deleted.status).json({ response: deleted.response });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// For future...
|
||||
// import { getToken } from "next-auth/jwt";
|
||||
|
||||
// export default async (req, res) => {
|
||||
// // If you don't have NEXTAUTH_SECRET set, you will have to pass your secret as `secret` to `getToken`
|
||||
// console.log({ req });
|
||||
// const token = await getToken({ req, raw: true });
|
||||
// if (token) {
|
||||
// // Signed in
|
||||
// console.log("JSON Web Token", JSON.stringify(token, null, 2));
|
||||
// } else {
|
||||
// // Not Signed in
|
||||
// res.status(401);
|
||||
// }
|
||||
// res.end();
|
||||
// };
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import deleteLinkById from "@/lib/api/controllers/links/linkId/deleteLinkById";
|
||||
import updateLinkById from "@/lib/api/controllers/links/linkId/updateLinkById";
|
||||
|
||||
export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return res.status(401).json({ response: "You must be logged in." });
|
||||
} else if (session?.user?.isSubscriber === false)
|
||||
res.status(401).json({
|
||||
response:
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
if (req.method === "PUT") {
|
||||
const updated = await updateLinkById(
|
||||
session.user.id,
|
||||
Number(req.query.id),
|
||||
req.body
|
||||
);
|
||||
return res.status(updated.status).json({
|
||||
response: updated.response,
|
||||
});
|
||||
} else if (req.method === "DELETE") {
|
||||
const deleted = await deleteLinkById(session.user.id, Number(req.query.id));
|
||||
return res.status(deleted.status).json({
|
||||
response: deleted.response,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import getLinks from "@/lib/api/controllers/links/getLinks";
|
||||
import postLink from "@/lib/api/controllers/links/postLink";
|
||||
import deleteLink from "@/lib/api/controllers/links/deleteLink";
|
||||
import updateLink from "@/lib/api/controllers/links/updateLink";
|
||||
|
||||
export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
@@ -25,15 +23,5 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
||||
return res.status(newlink.status).json({
|
||||
response: newlink.response,
|
||||
});
|
||||
} else if (req.method === "PUT") {
|
||||
const updated = await updateLink(req.body, session.user.id);
|
||||
return res.status(updated.status).json({
|
||||
response: updated.response,
|
||||
});
|
||||
} else if (req.method === "DELETE") {
|
||||
const deleted = await deleteLink(req.body, session.user.id);
|
||||
return res.status(deleted.status).json({
|
||||
response: deleted.response,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import exportData from "@/lib/api/controllers/migration/exportData";
|
||||
import importFromHTMLFile from "@/lib/api/controllers/migration/importFromHTMLFile";
|
||||
import importFromLinkwarden from "@/lib/api/controllers/migration/importFromLinkwarden";
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import paymentCheckout from "@/lib/api/paymentCheckout";
|
||||
import { Plan } from "@/types/global";
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import updateTag from "@/lib/api/controllers/tags/tagId/updeteTagById";
|
||||
|
||||
export default async function tags(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session?.user?.username) {
|
||||
return res.status(401).json({ response: "You must be logged in." });
|
||||
} else if (session?.user?.isSubscriber === false)
|
||||
res.status(401).json({
|
||||
response:
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
const tagId = Number(req.query.id);
|
||||
|
||||
if (req.method === "PUT") {
|
||||
const tags = await updateTag(session.user.id, tagId, req.body);
|
||||
return res.status(tags.status).json({ response: tags.response });
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import getTags from "@/lib/api/controllers/tags/getTags";
|
||||
|
||||
export default async function tags(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "@/pages/api/v1/auth/[...nextauth]";
|
||||
import getUserById from "@/lib/api/controllers/users/userId/getUserById";
|
||||
import getPublicUserById from "@/lib/api/controllers/users/userId/getPublicUserById";
|
||||
import updateUserById from "@/lib/api/controllers/users/userId/updateUserById";
|
||||
|
||||
export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
const userId = session?.user.id;
|
||||
const username = session?.user.username;
|
||||
|
||||
const lookupId = req.query.id as string;
|
||||
const isSelf =
|
||||
userId === Number(lookupId) || username === lookupId ? true : false;
|
||||
|
||||
// Check if "lookupId" is the user "id" or their "username"
|
||||
const isId = lookupId.split("").every((e) => Number.isInteger(parseInt(e)));
|
||||
|
||||
if (req.method === "GET" && !isSelf) {
|
||||
const users = await getPublicUserById(lookupId, isId, username);
|
||||
return res.status(users.status).json({ response: users.response });
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ response: "You must be logged in." });
|
||||
} else if (session?.user?.isSubscriber === false)
|
||||
res.status(401).json({
|
||||
response:
|
||||
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||
});
|
||||
|
||||
if (req.method === "GET") {
|
||||
const users = await getUserById(session.user.id);
|
||||
return res.status(users.status).json({ response: users.response });
|
||||
} else if (req.method === "PUT") {
|
||||
const updated = await updateUserById(session.user, req.body);
|
||||
return res.status(updated.status).json({ response: updated.response });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import postUser from "@/lib/api/controllers/users/postUser";
|
||||
|
||||
export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === "POST") {
|
||||
const response = await postUser(req, res);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export default function Index() {
|
||||
return (
|
||||
<ProfilePhoto
|
||||
key={i}
|
||||
src={`/api/avatar/${e.userId}?${Date.now()}`}
|
||||
src={`/api/v1/avatar/${e.userId}?${Date.now()}`}
|
||||
className="-mr-3 border-[3px]"
|
||||
/>
|
||||
);
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ export default function Register() {
|
||||
|
||||
const load = toast.loading("Creating Account...");
|
||||
|
||||
const response = await fetch("/api/auth/register", {
|
||||
const response = await fetch("/api/v1/users", {
|
||||
body: JSON.stringify(request),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function Account() {
|
||||
data: request,
|
||||
};
|
||||
|
||||
const response = await fetch("/api/migration", {
|
||||
const response = await fetch("/api/v1/migration", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
@@ -333,7 +333,7 @@ export default function Account() {
|
||||
<p className="text-sm text-black dark:text-white mb-2">
|
||||
Download your data instantly.
|
||||
</p>
|
||||
<Link className="w-fit" href="/api/migration">
|
||||
<Link className="w-fit" href="/api/v1/migration">
|
||||
<div className="border w-fit border-slate-200 dark:border-neutral-700 rounded-md bg-white dark:bg-neutral-800 px-2 text-center select-none cursor-pointer duration-100 hover:border-sky-300 hover:dark:border-sky-600">
|
||||
Export Data
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ export default function Subscribe() {
|
||||
|
||||
const redirectionToast = toast.loading("Redirecting to Stripe...");
|
||||
|
||||
const res = await fetch("/api/payment?plan=" + plan);
|
||||
const res = await fetch("/api/v1/payment?plan=" + plan);
|
||||
const data = await res.json();
|
||||
|
||||
router.push(data.response);
|
||||
|
||||
+131
-7
@@ -1,25 +1,38 @@
|
||||
import LinkCard from "@/components/LinkCard";
|
||||
import useLinkStore from "@/store/links";
|
||||
import { faHashtag, faSort } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faCheck,
|
||||
faEllipsis,
|
||||
faHashtag,
|
||||
faSort,
|
||||
faXmark,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import MainLayout from "@/layouts/MainLayout";
|
||||
import { Tag } from "@prisma/client";
|
||||
import useTagStore from "@/store/tags";
|
||||
import SortDropdown from "@/components/SortDropdown";
|
||||
import { Sort } from "@/types/global";
|
||||
import useLinks from "@/hooks/useLinks";
|
||||
import Dropdown from "@/components/Dropdown";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
export default function Index() {
|
||||
const router = useRouter();
|
||||
|
||||
const { links } = useLinkStore();
|
||||
const { tags } = useTagStore();
|
||||
const { tags, updateTag } = useTagStore();
|
||||
|
||||
const [sortDropdown, setSortDropdown] = useState(false);
|
||||
const [sortBy, setSortBy] = useState<Sort>(Sort.DateNewestFirst);
|
||||
|
||||
const [expandDropdown, setExpandDropdown] = useState(false);
|
||||
|
||||
const [renameTag, setRenameTag] = useState(false);
|
||||
const [newTagName, setNewTagName] = useState<string>();
|
||||
|
||||
const [activeTag, setActiveTag] = useState<Tag>();
|
||||
|
||||
useLinks({ tagId: Number(router.query.id), sort: sortBy });
|
||||
@@ -28,19 +41,130 @@ export default function Index() {
|
||||
setActiveTag(tags.find((e) => e.id === Number(router.query.id)));
|
||||
}, [router, tags]);
|
||||
|
||||
useEffect(() => {
|
||||
setNewTagName(activeTag?.name);
|
||||
}, [activeTag]);
|
||||
|
||||
const [submitLoader, setSubmitLoader] = useState(false);
|
||||
|
||||
const cancelUpdateTag = async () => {
|
||||
setNewTagName(activeTag?.name);
|
||||
setRenameTag(false);
|
||||
};
|
||||
|
||||
const submit = async (e?: FormEvent) => {
|
||||
e?.preventDefault();
|
||||
|
||||
if (activeTag?.name === newTagName) return setRenameTag(false);
|
||||
else if (newTagName === "") {
|
||||
return cancelUpdateTag();
|
||||
}
|
||||
|
||||
setSubmitLoader(true);
|
||||
|
||||
const load = toast.loading("Applying...");
|
||||
|
||||
let response;
|
||||
|
||||
if (activeTag && newTagName)
|
||||
response = await updateTag({
|
||||
...activeTag,
|
||||
name: newTagName,
|
||||
});
|
||||
|
||||
toast.dismiss(load);
|
||||
|
||||
if (response?.ok) {
|
||||
toast.success("Tag Renamed!");
|
||||
} else toast.error(response?.data as string);
|
||||
setSubmitLoader(false);
|
||||
setRenameTag(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="p-5 flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-3 items-center justify-between">
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 items-end">
|
||||
<FontAwesomeIcon
|
||||
icon={faHashtag}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500 dark:text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize text-black dark:text-white">
|
||||
{activeTag?.name}
|
||||
</p>
|
||||
{renameTag ? (
|
||||
<>
|
||||
<form onSubmit={submit} className="flex items-end gap-2">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
className="sm:text-4xl text-3xl capitalize text-black dark:text-white bg-transparent h-10 w-3/4 outline-none border-b border-b-sky-100 dark:border-b-neutral-700"
|
||||
value={newTagName}
|
||||
onChange={(e) => setNewTagName(e.target.value)}
|
||||
/>
|
||||
<div
|
||||
onClick={() => submit()}
|
||||
id="expand-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
id="expand-dropdown"
|
||||
className="w-5 h-5 text-gray-500 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => cancelUpdateTag()}
|
||||
id="expand-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faXmark}
|
||||
id="expand-dropdown"
|
||||
className="w-5 h-5 text-gray-500 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="sm:text-4xl text-3xl capitalize text-black dark:text-white">
|
||||
{activeTag?.name}
|
||||
</p>
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setExpandDropdown(!expandDropdown)}
|
||||
id="expand-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faEllipsis}
|
||||
id="expand-dropdown"
|
||||
className="w-5 h-5 text-gray-500 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{expandDropdown ? (
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
name: "Rename Tag",
|
||||
onClick: () => {
|
||||
setRenameTag(true);
|
||||
setExpandDropdown(false);
|
||||
},
|
||||
},
|
||||
]}
|
||||
onClickOutside={(e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.id !== "expand-dropdown")
|
||||
setExpandDropdown(false);
|
||||
}}
|
||||
className="absolute top-8 left-0 w-36"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user