added read-only mode + visual improvements

This commit is contained in:
daniel31x13 2024-07-16 20:33:33 -04:00
parent 6d30912812
commit 9c5226ee51
25 changed files with 172 additions and 16 deletions

View File

@ -21,6 +21,7 @@ BROWSER_TIMEOUT=
IGNORE_UNAUTHORIZED_CA=
IGNORE_HTTPS_ERRORS=
IGNORE_URL_SIZE_LIMIT=
DEMO_MODE=
NEXT_PUBLIC_ADMIN=
NEXT_PUBLIC_MAX_FILE_BUFFER=
MONOLITH_MAX_BUFFER=

View File

@ -81,12 +81,15 @@ const LinkListOptions = ({
toast.dismiss(load);
response.ok &&
if (response.ok) {
toast.success(
selectedLinks.length === 1
? t("link_deleted")
: t("links_deleted", { count: selectedLinks.length })
);
} else {
toast.error(response.data as string);
}
};
return (

View File

@ -55,8 +55,11 @@ export default function LinkActions({
toast.dismiss(load);
response.ok &&
if (response.ok) {
toast.success(isAlreadyPinned ? t("link_unpinned") : t("link_unpinned"));
} else {
toast.error(response.data as string);
}
};
const deleteLink = async () => {
@ -66,7 +69,11 @@ export default function LinkActions({
toast.dismiss(load);
response.ok && toast.success(t("deleted"));
if (response.ok) {
toast.success(t("deleted"));
} else {
toast.error(response.data as string);
}
};
return (

View File

@ -157,7 +157,12 @@ export default function LinkCardCompact({
// linkInfo={showInfo}
/>
</div>
<div className="divider my-0 last:hidden h-[1px]"></div>
<div
className="last:hidden rounded-none"
style={{
borderTop: "1px solid var(--fallback-bc,oklch(var(--bc)/0.1))",
}}
></div>
</>
);
}

View File

@ -30,7 +30,11 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
toast.dismiss(load);
response.ok && toast.success(t("deleted"));
if (response.ok) {
toast.success(t("deleted"));
} else {
toast.error(response.data as string);
}
if (router.pathname.startsWith("/links/[id]")) {
router.push("/dashboard");

View File

@ -20,7 +20,11 @@ export default function DeleteUserModal({ onClose, userId }: Props) {
toast.dismiss(load);
response.ok && toast.success(t("user_deleted"));
if (response.ok) {
toast.success(t("user_deleted"));
} else {
toast.error(response.data as string);
}
onClose();
};

View File

@ -30,6 +30,8 @@ export default function DeleteTokenModal({ onClose, activeToken }: Props) {
if (response.ok) {
toast.success(t("token_revoked"));
} else {
toast.error(response.data as string);
}
onClose();

View File

@ -12,7 +12,7 @@ export default function PageHeader({
return (
<div className="flex items-center gap-3">
<i
className={`${icon} text-primary text-3xl sm:text-4xl drop-shadow`}
className={`${icon} text-primary sm:text-3xl text-2xl drop-shadow`}
></i>
<div>
<p className="text-3xl capitalize font-thin">{title}</p>

View File

@ -77,6 +77,12 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
return res.send(file);
}
} else if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const user = await verifyUser({ req, res });
if (!user) return;
@ -86,14 +92,18 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
});
if (!collectionPermissions)
return { response: "Collection is not accessible.", status: 400 };
return res.status(400).json({
response: "Collection is not accessible.",
});
const memberHasAccess = collectionPermissions.members.some(
(e: UsersAndCollections) => e.userId === user.id && e.canCreate
);
if (!(collectionPermissions.ownerId === user.id || memberHasAccess))
return { response: "Collection is not accessible.", status: 400 };
return res.status(400).json({
response: "Collection is not accessible.",
});
// await uploadHandler(linkId, )
@ -108,10 +118,10 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
});
if (numberOfLinksTheUserHas > MAX_LINKS_PER_USER)
return {
response: `Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.`,
status: 400,
};
return res.status(400).json({
response:
"Each collection owner can only have a maximum of ${MAX_LINKS_PER_USER} Links.",
});
const NEXT_PUBLIC_MAX_FILE_BUFFER = Number(
process.env.NEXT_PUBLIC_MAX_FILE_BUFFER || 10

View File

@ -7,6 +7,12 @@ export default async function forgotPassword(
res: NextApiResponse
) {
if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const email = req.body.email;
if (!email) {

View File

@ -7,6 +7,12 @@ export default async function resetPassword(
res: NextApiResponse
) {
if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const token = req.body.token;
const password = req.body.password;

View File

@ -7,6 +7,12 @@ export default async function verifyEmail(
res: NextApiResponse
) {
if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const token = req.query.token;
if (!token || typeof token !== "string") {

View File

@ -19,9 +19,21 @@ export default async function collections(
.status(collections.status)
.json({ response: collections.response });
} else if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const updated = await updateCollectionById(user.id, collectionId, req.body);
return res.status(updated.status).json({ response: updated.response });
} else if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const deleted = await deleteCollectionById(user.id, collectionId);
return res.status(deleted.status).json({ response: deleted.response });
}

View File

@ -16,6 +16,12 @@ export default async function collections(
.status(collections.status)
.json({ response: collections.response });
} else if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const newCollection = await postCollection(req.body, user.id);
return res
.status(newCollection.status)

View File

@ -29,6 +29,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
});
if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
if (
link?.lastPreserved &&
getTimezoneDifferenceInMinutes(new Date(), link?.lastPreserved) <

View File

@ -14,6 +14,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
response: updated.response,
});
} else if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const updated = await updateLinkById(
user.id,
Number(req.query.id),
@ -23,6 +29,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
response: updated.response,
});
} else if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const deleted = await deleteLinkById(user.id, Number(req.query.id));
return res.status(deleted.status).json({
response: deleted.response,

View File

@ -37,11 +37,23 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
const links = await getLinks(user.id, convertedData);
return res.status(links.status).json({ response: links.response });
} else if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const newlink = await postLink(req.body, user.id);
return res.status(newlink.status).json({
response: newlink.response,
});
} else if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const updated = await updateLinks(
user.id,
req.body.links,
@ -52,6 +64,12 @@ export default async function links(req: NextApiRequest, res: NextApiResponse) {
response: updated.response,
});
} else if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const deleted = await deleteLinksById(user.id, req.body.linkIds);
return res.status(deleted.status).json({
response: deleted.response,

View File

@ -30,6 +30,12 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
.status(data.status)
.json(data.response);
} else if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const request: MigrationRequest = JSON.parse(req.body);
let data;

View File

@ -10,9 +10,21 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) {
const tagId = Number(req.query.id);
if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const tags = await updeteTagById(user.id, tagId, req.body);
return res.status(tags.status).json({ response: tags.response });
} else if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const tags = await deleteTagById(user.id, tagId);
return res.status(tags.status).json({ response: tags.response });
}

View File

@ -7,6 +7,12 @@ export default async function token(req: NextApiRequest, res: NextApiResponse) {
if (!user) return;
if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const deleted = await deleteToken(user.id, Number(req.query.id) as number);
return res.status(deleted.status).json({ response: deleted.response });
}

View File

@ -11,6 +11,12 @@ export default async function tokens(
if (!user) return;
if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const token = await postToken(JSON.parse(req.body), user.id);
return res.status(token.status).json({ response: token.response });
} else if (req.method === "GET") {

View File

@ -58,9 +58,21 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
}
if (req.method === "PUT") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const updated = await updateUserById(userId, req.body);
return res.status(updated.status).json({ response: updated.response });
} else if (req.method === "DELETE") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const updated = await deleteUserById(userId, req.body, isServerAdmin);
return res.status(updated.status).json({ response: updated.response });
}

View File

@ -5,6 +5,12 @@ import verifyUser from "@/lib/api/verifyUser";
export default async function users(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
if (process.env.DEMO_MODE === "true")
return res.status(400).json({
response:
"This action is disabled because this is a read-only demo of Linkwarden.",
});
const response = await postUser(req, res);
return res.status(response.status).json({ response: response.response });
} else if (req.method === "GET") {

View File

@ -128,7 +128,7 @@ export default function Index() {
style={{ color: activeCollection?.color }}
></i>
<p className="sm:text-4xl text-3xl capitalize w-full py-1 break-words hyphens-auto font-thin">
<p className="sm:text-3xl text-2xl capitalize w-full py-1 break-words hyphens-auto font-thin">
{activeCollection?.name}
</p>
</div>

View File

@ -145,7 +145,7 @@ export default function Index() {
<input
type="text"
autoFocus
className="sm:text-4xl text-3xl capitalize bg-transparent h-10 w-3/4 outline-none border-b border-b-neutral-content"
className="sm:text-3xl text-2xl capitalize bg-transparent h-10 w-3/4 outline-none border-b border-b-neutral-content"
value={newTagName}
onChange={(e) => setNewTagName(e.target.value)}
/>
@ -167,7 +167,7 @@ export default function Index() {
</>
) : (
<>
<p className="sm:text-4xl text-3xl capitalize">
<p className="sm:text-3xl text-2xl capitalize">
{activeTag?.name}
</p>
<div className="relative">