better looking detail modal
This commit is contained in:
parent
6498ae794b
commit
2d0e52f65b
|
@ -46,7 +46,7 @@ export default function Drawer({
|
||||||
data-testid="mobile-modal-container"
|
data-testid="mobile-modal-container"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-neutral mb-5"
|
className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-neutral mb-5 relative z-20"
|
||||||
data-testid="mobile-modal-slider"
|
data-testid="mobile-modal-slider"
|
||||||
/>
|
/>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
readabilityAvailable,
|
readabilityAvailable,
|
||||||
monolithAvailable,
|
monolithAvailable,
|
||||||
screenshotAvailable,
|
screenshotAvailable,
|
||||||
|
previewAvailable,
|
||||||
} from "@/lib/shared/getArchiveValidity";
|
} from "@/lib/shared/getArchiveValidity";
|
||||||
import PreservedFormatRow from "@/components/PreserverdFormatRow";
|
import PreservedFormatRow from "@/components/PreserverdFormatRow";
|
||||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||||
|
@ -22,13 +23,22 @@ import CopyButton from "./CopyButton";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { IconWeight } from "@phosphor-icons/react";
|
import { IconWeight } from "@phosphor-icons/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
|
standalone?: boolean;
|
||||||
|
editMode?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkDetails({ className, link }: Props) {
|
export default function LinkDetails({
|
||||||
|
className,
|
||||||
|
link,
|
||||||
|
standalone,
|
||||||
|
editMode,
|
||||||
|
}: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const getLink = useGetLink();
|
const getLink = useGetLink();
|
||||||
|
@ -126,192 +136,241 @@ export default function LinkDetails({ className, link }: Props) {
|
||||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} data-vaul-no-drag>
|
<div className={clsx(className)} data-vaul-no-drag>
|
||||||
<div className="mx-auto w-fit">
|
<div
|
||||||
<LinkIcon link={link} hideBackground />
|
className={clsx(
|
||||||
</div>
|
standalone && "sm:border sm:border-neutral-content sm:rounded-2xl p-5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"overflow-hidden select-none relative h-32 opacity-80 bg-opacity-80",
|
||||||
|
standalone
|
||||||
|
? "sm:max-w-xl -mx-5 -mt-5 sm:rounded-t-2xl"
|
||||||
|
: "-mx-4 -mt-4"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{previewAvailable(link) ? (
|
||||||
|
<Image
|
||||||
|
src={`/api/v1/archives/${link.id}?format=${ArchivedFormat.jpeg}&preview=true`}
|
||||||
|
width={1280}
|
||||||
|
height={720}
|
||||||
|
alt=""
|
||||||
|
className="object-cover scale-105"
|
||||||
|
style={{
|
||||||
|
filter: "blur(1px)",
|
||||||
|
}}
|
||||||
|
onError={(e) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
target.style.display = "none";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : link.preview === "unavailable" ? (
|
||||||
|
<div className="bg-gray-50 duration-100 h-32"></div>
|
||||||
|
) : (
|
||||||
|
<div className="duration-100 h-32 skeleton rounded-b-none"></div>
|
||||||
|
)}
|
||||||
|
<div className="absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center rounded-md">
|
||||||
|
<LinkIcon link={link} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{link.name && <p className="text-xl text-center mt-2">{link.name}</p>}
|
<div className="max-w-xl sm:px-8 p-5 pb-8 pt-2">
|
||||||
|
{link.name && <p className="text-xl text-center mt-2">{link.name}</p>}
|
||||||
|
|
||||||
|
{link.url && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14">
|
||||||
|
<Link href={link.url} title={link.url} target="_blank">
|
||||||
|
{link.url}
|
||||||
|
</Link>
|
||||||
|
<div className="absolute right-0 px-2 bg-base-200">
|
||||||
|
<CopyButton text={link.url} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{link.url && (
|
|
||||||
<>
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<p className="text-sm mb-2 text-neutral">{t("link")}</p>
|
<p className="text-sm mb-2 text-neutral">{t("collection")}</p>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14">
|
<Link
|
||||||
<Link href={link.url} title={link.url} target="_blank">
|
href={
|
||||||
{link.url}
|
isPublicRoute
|
||||||
</Link>
|
? `/public/collections/${link.collection.id}`
|
||||||
|
: `/collections/${link.collection.id}`
|
||||||
|
}
|
||||||
|
className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14"
|
||||||
|
>
|
||||||
|
<p>{link.collection.name}</p>
|
||||||
<div className="absolute right-0 px-2 bg-base-200">
|
<div className="absolute right-0 px-2 bg-base-200">
|
||||||
<CopyButton text={link.url} />
|
{link.collection.icon ? (
|
||||||
|
<Icon
|
||||||
|
icon={link.collection.icon}
|
||||||
|
size={30}
|
||||||
|
weight={
|
||||||
|
(link.collection.iconWeight || "regular") as IconWeight
|
||||||
|
}
|
||||||
|
color={link.collection.color || "#0ea5e9"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<i
|
||||||
|
className="bi-folder-fill text-2xl"
|
||||||
|
style={{ color: link.collection.color || "#0ea5e9" }}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<p className="text-sm mb-2 text-neutral">{t("collection")}</p>
|
|
||||||
|
|
||||||
<div className="relative">
|
|
||||||
<Link
|
|
||||||
href={
|
|
||||||
isPublicRoute
|
|
||||||
? `/public/collections/${link.collection.id}`
|
|
||||||
: `/collections/${link.collection.id}`
|
|
||||||
}
|
|
||||||
className="rounded-lg p-2 bg-base-200 hide-scrollbar overflow-x-auto whitespace-nowrap flex justify-between items-center gap-2 pr-14"
|
|
||||||
>
|
|
||||||
<p>{link.collection.name}</p>
|
|
||||||
<div className="absolute right-0 px-2 bg-base-200">
|
|
||||||
{link.collection.icon ? (
|
|
||||||
<Icon
|
|
||||||
icon={link.collection.icon}
|
|
||||||
size={30}
|
|
||||||
weight={(link.collection.iconWeight || "regular") as IconWeight}
|
|
||||||
color={link.collection.color || "#0ea5e9"}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<i
|
|
||||||
className="bi-folder-fill text-2xl"
|
|
||||||
style={{ color: link.collection.color || "#0ea5e9" }}
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{link.tags[0] && (
|
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="text-sm mb-2 text-neutral">{t("tags")}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
{link.tags[0] && (
|
||||||
{link.tags.map((tag) =>
|
<>
|
||||||
isPublicRoute ? (
|
<br />
|
||||||
<div key={tag.id} className="rounded-lg px-3 py-1 bg-base-200">
|
|
||||||
{tag.name}
|
<div>
|
||||||
|
<p className="text-sm mb-2 text-neutral">{t("tags")}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{link.tags.map((tag) =>
|
||||||
|
isPublicRoute ? (
|
||||||
|
<div
|
||||||
|
key={tag.id}
|
||||||
|
className="rounded-lg px-3 py-1 bg-base-200"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={"/tags/" + tag.id}
|
||||||
|
key={tag.id}
|
||||||
|
className="rounded-lg px-3 py-1 bg-base-200"
|
||||||
|
>
|
||||||
|
{tag.name}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{link.description && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm mb-2 text-neutral">{t("notes")}</p>
|
||||||
|
|
||||||
|
<div className="rounded-lg p-2 bg-base-200 hyphens-auto">
|
||||||
|
<p>{link.description}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<Link
|
</>
|
||||||
href={"/tags/" + tag.id}
|
)}
|
||||||
key={tag.id}
|
|
||||||
className="rounded-lg px-3 py-1 bg-base-200"
|
|
||||||
>
|
|
||||||
{tag.name}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{link.description && (
|
|
||||||
<>
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div>
|
<p
|
||||||
<p className="text-sm mb-2 text-neutral">{t("notes")}</p>
|
className="text-sm mb-2 text-neutral"
|
||||||
|
title={t("available_formats")}
|
||||||
<div className="rounded-lg p-2 bg-base-200 hyphens-auto">
|
|
||||||
<p>{link.description}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<p className="text-sm mb-2 text-neutral" title={t("available_formats")}>
|
|
||||||
{link.url ? t("preserved_formats") : t("file")}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className={`flex flex-col gap-3`}>
|
|
||||||
{monolithAvailable(link) ? (
|
|
||||||
<PreservedFormatRow
|
|
||||||
name={t("webpage")}
|
|
||||||
icon={"bi-filetype-html"}
|
|
||||||
format={ArchivedFormat.monolith}
|
|
||||||
link={link}
|
|
||||||
downloadable={true}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
{screenshotAvailable(link) ? (
|
|
||||||
<PreservedFormatRow
|
|
||||||
name={t("screenshot")}
|
|
||||||
icon={"bi-file-earmark-image"}
|
|
||||||
format={
|
|
||||||
link?.image?.endsWith("png")
|
|
||||||
? ArchivedFormat.png
|
|
||||||
: ArchivedFormat.jpeg
|
|
||||||
}
|
|
||||||
link={link}
|
|
||||||
downloadable={true}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
{pdfAvailable(link) ? (
|
|
||||||
<PreservedFormatRow
|
|
||||||
name={t("pdf")}
|
|
||||||
icon={"bi-file-earmark-pdf"}
|
|
||||||
format={ArchivedFormat.pdf}
|
|
||||||
link={link}
|
|
||||||
downloadable={true}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
{readabilityAvailable(link) ? (
|
|
||||||
<PreservedFormatRow
|
|
||||||
name={t("readable")}
|
|
||||||
icon={"bi-file-earmark-text"}
|
|
||||||
format={ArchivedFormat.readability}
|
|
||||||
link={link}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
{!isReady() && !atLeastOneFormatAvailable() ? (
|
|
||||||
<div className={`w-full h-full flex flex-col justify-center p-10`}>
|
|
||||||
<BeatLoader
|
|
||||||
color="oklch(var(--p))"
|
|
||||||
className="mx-auto mb-3"
|
|
||||||
size={30}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p className="text-center text-2xl">{t("preservation_in_queue")}</p>
|
|
||||||
<p className="text-center text-lg">{t("check_back_later")}</p>
|
|
||||||
</div>
|
|
||||||
) : link.url && !isReady() && atLeastOneFormatAvailable() ? (
|
|
||||||
<div className={`w-full h-full flex flex-col justify-center p-5`}>
|
|
||||||
<BeatLoader
|
|
||||||
color="oklch(var(--p))"
|
|
||||||
className="mx-auto mb-3"
|
|
||||||
size={20}
|
|
||||||
/>
|
|
||||||
<p className="text-center">{t("there_are_more_formats")}</p>
|
|
||||||
<p className="text-center text-sm">{t("check_back_later")}</p>
|
|
||||||
</div>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
{link.url && (
|
|
||||||
<Link
|
|
||||||
href={`https://web.archive.org/web/${link?.url?.replace(
|
|
||||||
/(^\w+:|^)\/\//,
|
|
||||||
""
|
|
||||||
)}`}
|
|
||||||
target="_blank"
|
|
||||||
className="text-neutral mx-auto duration-100 hover:opacity-60 flex gap-2 w-1/2 justify-center items-center text-sm"
|
|
||||||
>
|
>
|
||||||
<p className="whitespace-nowrap">{t("view_latest_snapshot")}</p>
|
{link.url ? t("preserved_formats") : t("file")}
|
||||||
<i className="bi-box-arrow-up-right" />
|
</p>
|
||||||
</Link>
|
|
||||||
)}
|
<div className={`flex flex-col gap-3`}>
|
||||||
|
{monolithAvailable(link) ? (
|
||||||
|
<PreservedFormatRow
|
||||||
|
name={t("webpage")}
|
||||||
|
icon={"bi-filetype-html"}
|
||||||
|
format={ArchivedFormat.monolith}
|
||||||
|
link={link}
|
||||||
|
downloadable={true}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
{screenshotAvailable(link) ? (
|
||||||
|
<PreservedFormatRow
|
||||||
|
name={t("screenshot")}
|
||||||
|
icon={"bi-file-earmark-image"}
|
||||||
|
format={
|
||||||
|
link?.image?.endsWith("png")
|
||||||
|
? ArchivedFormat.png
|
||||||
|
: ArchivedFormat.jpeg
|
||||||
|
}
|
||||||
|
link={link}
|
||||||
|
downloadable={true}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
{pdfAvailable(link) ? (
|
||||||
|
<PreservedFormatRow
|
||||||
|
name={t("pdf")}
|
||||||
|
icon={"bi-file-earmark-pdf"}
|
||||||
|
format={ArchivedFormat.pdf}
|
||||||
|
link={link}
|
||||||
|
downloadable={true}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
{readabilityAvailable(link) ? (
|
||||||
|
<PreservedFormatRow
|
||||||
|
name={t("readable")}
|
||||||
|
icon={"bi-file-earmark-text"}
|
||||||
|
format={ArchivedFormat.readability}
|
||||||
|
link={link}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
{!isReady() && !atLeastOneFormatAvailable() ? (
|
||||||
|
<div
|
||||||
|
className={`w-full h-full flex flex-col justify-center p-10`}
|
||||||
|
>
|
||||||
|
<BeatLoader
|
||||||
|
color="oklch(var(--p))"
|
||||||
|
className="mx-auto mb-3"
|
||||||
|
size={30}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="text-center text-2xl">
|
||||||
|
{t("preservation_in_queue")}
|
||||||
|
</p>
|
||||||
|
<p className="text-center text-lg">{t("check_back_later")}</p>
|
||||||
|
</div>
|
||||||
|
) : link.url && !isReady() && atLeastOneFormatAvailable() ? (
|
||||||
|
<div className={`w-full h-full flex flex-col justify-center p-5`}>
|
||||||
|
<BeatLoader
|
||||||
|
color="oklch(var(--p))"
|
||||||
|
className="mx-auto mb-3"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<p className="text-center">{t("there_are_more_formats")}</p>
|
||||||
|
<p className="text-center text-sm">{t("check_back_later")}</p>
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
{link.url && (
|
||||||
|
<Link
|
||||||
|
href={`https://web.archive.org/web/${link?.url?.replace(
|
||||||
|
/(^\w+:|^)\/\//,
|
||||||
|
""
|
||||||
|
)}`}
|
||||||
|
target="_blank"
|
||||||
|
className="text-neutral mx-auto duration-100 hover:opacity-60 flex gap-2 w-1/2 justify-center items-center text-sm"
|
||||||
|
>
|
||||||
|
<p className="whitespace-nowrap">{t("view_latest_snapshot")}</p>
|
||||||
|
<i className="bi-box-arrow-up-right" />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -222,6 +222,7 @@ export default function LinkActions({
|
||||||
<LinkDetailModal
|
<LinkDetailModal
|
||||||
onClose={() => setLinkDetailModal(false)}
|
onClose={() => setLinkDetailModal(false)}
|
||||||
onEdit={() => setEditLinkModal(true)}
|
onEdit={() => setEditLinkModal(true)}
|
||||||
|
onDelete={() => setDeleteLinkModal(true)}
|
||||||
link={link}
|
link={link}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -172,7 +172,7 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
|
||||||
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
||||||
)}
|
)}
|
||||||
{show.icon && (
|
{show.icon && (
|
||||||
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md">
|
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center rounded-md">
|
||||||
<LinkIcon link={link} />
|
<LinkIcon link={link} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default function LinkIcon({
|
||||||
hideBackground?: boolean;
|
hideBackground?: boolean;
|
||||||
}) {
|
}) {
|
||||||
let iconClasses: string = clsx(
|
let iconClasses: string = clsx(
|
||||||
"rounded flex item-center justify-center select-none z-10 w-12 h-12",
|
"rounded flex item-center justify-center shadow-md select-none z-10 w-12 h-12",
|
||||||
!hideBackground && "rounded-md bg-white backdrop-blur-lg bg-opacity-50 p-1",
|
!hideBackground && "rounded-md bg-white backdrop-blur-lg bg-opacity-50 p-1",
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
|
|
|
@ -162,7 +162,7 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
|
||||||
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
<div className="duration-100 h-40 bg-opacity-80 skeleton rounded-none"></div>
|
||||||
)}
|
)}
|
||||||
{show.icon && (
|
{show.icon && (
|
||||||
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md">
|
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center rounded-md">
|
||||||
<LinkIcon link={link} />
|
<LinkIcon link={link} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,22 +1,38 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
import {
|
||||||
|
ArchivedFormat,
|
||||||
|
LinkIncludingShortenedCollectionAndTags,
|
||||||
|
} from "@/types/global";
|
||||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { useUser } from "@/hooks/store/user";
|
import { useUser } from "@/hooks/store/user";
|
||||||
import { useGetLink } from "@/hooks/store/links";
|
import { useDeleteLink, useGetLink, useUpdateLink } from "@/hooks/store/links";
|
||||||
import Drawer from "../Drawer";
|
import Drawer from "../Drawer";
|
||||||
import LinkDetails from "../LinkDetails";
|
import LinkDetails from "../LinkDetails";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { previewAvailable } from "@/lib/shared/getArchiveValidity";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { dropdownTriggerer } from "@/lib/client/utils";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import EditLinkModal from "./EditLinkModal";
|
||||||
|
import DeleteLinkModal from "./DeleteLinkModal";
|
||||||
|
import PreservedFormatsModal from "./PreservedFormatsModal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
onEdit: Function;
|
onEdit: Function;
|
||||||
|
onDelete: Function;
|
||||||
link: LinkIncludingShortenedCollectionAndTags;
|
link: LinkIncludingShortenedCollectionAndTags;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LinkDetailModal({ onClose, onEdit, link }: Props) {
|
export default function LinkDetailModal({
|
||||||
|
onClose,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
link,
|
||||||
|
}: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const getLink = useGetLink();
|
const getLink = useGetLink();
|
||||||
const { data: user = {} } = useUser();
|
const { data: user = {} } = useUser();
|
||||||
|
@ -105,39 +121,180 @@ export default function LinkDetailModal({ onClose, onEdit, link }: Props) {
|
||||||
|
|
||||||
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
const isPublicRoute = router.pathname.startsWith("/public") ? true : false;
|
||||||
|
|
||||||
|
const updateLink = useUpdateLink();
|
||||||
|
const deleteLink = useDeleteLink();
|
||||||
|
|
||||||
|
const pinLink = async () => {
|
||||||
|
const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0] ? true : false;
|
||||||
|
|
||||||
|
const load = toast.loading(t("updating"));
|
||||||
|
|
||||||
|
await updateLink.mutateAsync(
|
||||||
|
{
|
||||||
|
...link,
|
||||||
|
pinnedBy: isAlreadyPinned ? undefined : [{ id: user.id }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSettled: (data, error) => {
|
||||||
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.success(
|
||||||
|
isAlreadyPinned ? t("link_unpinned") : t("link_pinned")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateArchive = async () => {
|
||||||
|
const load = toast.loading(t("sending_request"));
|
||||||
|
|
||||||
|
const response = await fetch(`/api/v1/links/${link?.id}/archive`, {
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
await getLink.mutateAsync({ id: link.id as number });
|
||||||
|
|
||||||
|
toast.success(t("link_being_archived"));
|
||||||
|
} else toast.error(data.response);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [editLinkModal, setEditLinkModal] = useState(false);
|
||||||
|
const [deleteLinkModal, setDeleteLinkModal] = useState(false);
|
||||||
|
const [preservedFormatsModal, setPreservedFormatsModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer toggleDrawer={onClose} className="sm:h-screen sm:flex relative">
|
<Drawer toggleDrawer={onClose} className="sm:h-screen sm:flex relative">
|
||||||
<div
|
<div
|
||||||
className="bi-x text-xl text-neutral btn btn-sm btn-square btn-ghost hidden sm:block absolute top-3 left-3"
|
className="bi-x text-xl btn btn-sm btn-circle text-base-content opacity-50 hover:opacity-100 absolute top-3 left-3 z-10"
|
||||||
onClick={() => onClose()}
|
onClick={() => onClose()}
|
||||||
></div>
|
></div>
|
||||||
<Link
|
|
||||||
href={isPublicRoute ? `/public/links/${link.id}` : `/links/${link.id}`}
|
|
||||||
target="_blank"
|
|
||||||
className="bi-box-arrow-up-right text-xl text-neutral btn btn-sm btn-square btn-ghost absolute top-3 right-3 select-none"
|
|
||||||
></Link>
|
|
||||||
|
|
||||||
<div className="sm:m-auto p-10 w-full max-w-xl">
|
<div className={`dropdown dropdown-end absolute top-3 right-12 z-20`}>
|
||||||
<LinkDetails link={link} />
|
<div
|
||||||
|
tabIndex={0}
|
||||||
{(permissions === true || permissions?.canUpdate) && (
|
role="button"
|
||||||
<>
|
onMouseDown={dropdownTriggerer}
|
||||||
<br />
|
className="btn btn-sm btn-circle text-base-content opacity-50 hover:opacity-100 z-10"
|
||||||
<br />
|
>
|
||||||
|
<i title="More" className="bi-three-dots text-xl" />
|
||||||
<div className="mx-auto text-center">
|
</div>
|
||||||
|
<ul
|
||||||
|
className={`dropdown-content z-[20] menu shadow bg-base-200 border border-neutral-content rounded-box`}
|
||||||
|
>
|
||||||
|
{(permissions === true || permissions?.canUpdate) && (
|
||||||
|
<li>
|
||||||
<div
|
<div
|
||||||
className="btn btn-sm btn-ghost"
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onEdit();
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
onClose();
|
pinLink();
|
||||||
}}
|
}}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{link?.pinnedBy && link.pinnedBy[0]
|
||||||
|
? t("unpin")
|
||||||
|
: t("pin_to_dashboard")}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{(permissions === true || permissions?.canUpdate) && (
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
|
setEditLinkModal(true);
|
||||||
|
}}
|
||||||
|
className="whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{t("edit_link")}
|
{t("edit_link")}
|
||||||
</div>
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{link.type === "url" && permissions === true && (
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => {
|
||||||
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
|
updateArchive();
|
||||||
|
}}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{t("refresh_preserved_formats")}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
{(permissions === true || permissions?.canDelete) && (
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={async (e) => {
|
||||||
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
|
console.log(e.shiftKey);
|
||||||
|
if (e.shiftKey) {
|
||||||
|
const load = toast.loading(t("deleting"));
|
||||||
|
|
||||||
|
await deleteLink.mutateAsync(link.id as number, {
|
||||||
|
onSettled: (data, error) => {
|
||||||
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error(error.message);
|
||||||
|
} else {
|
||||||
|
toast.success(t("deleted"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
onDelete();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{t("delete")}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href={isPublicRoute ? `/public/links/${link.id}` : `/links/${link.id}`}
|
||||||
|
target="_blank"
|
||||||
|
className="bi-box-arrow-up-right btn-circle text-base-content opacity-50 hover:opacity-100 btn btn-sm absolute top-3 right-3 select-none z-10"
|
||||||
|
></Link>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<LinkDetails link={link} className="sm:mt-0 -mt-11" />
|
||||||
|
|
||||||
|
{/* {(permissions === true || permissions?.canUpdate) && (
|
||||||
|
<div className="mx-auto text-center">
|
||||||
|
<div
|
||||||
|
className="btn btn-sm btn-ghost"
|
||||||
|
onClick={() => {
|
||||||
|
onEdit();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("edit_link")}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,14 +17,13 @@ const Index = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen py-20">
|
<div className="flex h-screen">
|
||||||
{getLink.data ? (
|
{getLink.data ? (
|
||||||
<div className="m-auto py-20 w-full">
|
<LinkDetails
|
||||||
<LinkDetails
|
link={getLink.data}
|
||||||
link={getLink.data}
|
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
||||||
className="max-w-xl mx-auto p-5 w-full"
|
standalone
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
|
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
|
||||||
<div className="w-20 h-20 skeleton rounded-xl"></div>
|
<div className="w-20 h-20 skeleton rounded-xl"></div>
|
||||||
|
|
|
@ -17,12 +17,11 @@ const Index = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
{getLink.data ? (
|
{getLink.data ? (
|
||||||
<div className="m-auto py-20 w-full">
|
<LinkDetails
|
||||||
<LinkDetails
|
link={getLink.data}
|
||||||
link={getLink.data}
|
className="sm:max-w-xl sm:m-auto sm:p-5 w-full"
|
||||||
className="max-w-xl mx-auto p-5 w-full"
|
standalone
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
|
<div className="max-w-xl p-5 m-auto w-full flex flex-col items-center gap-5">
|
||||||
<div className="w-20 h-20 skeleton rounded-xl"></div>
|
<div className="w-20 h-20 skeleton rounded-xl"></div>
|
||||||
|
|
Ŝarĝante…
Reference in New Issue