refactor code to improve readability and maintainability + redesigned announcement bar

This commit is contained in:
daniel31x13 2024-05-24 17:12:47 -04:00
parent a498f3a10d
commit d262041f33
21 changed files with 191 additions and 175 deletions

View File

@ -9,12 +9,12 @@ export default function dashboardItem({
}) { }) {
return ( return (
<div className="flex items-center"> <div className="flex items-center">
<div className="w-[4.7rem] aspect-square flex justify-center items-center bg-primary/20 rounded-xl select-none"> <div className="w-[4rem] aspect-square flex justify-center items-center bg-primary/20 rounded-xl select-none">
<i className={`${icon} text-primary text-4xl drop-shadow`}></i> <i className={`${icon} text-primary text-3xl drop-shadow`}></i>
</div> </div>
<div className="ml-4 flex flex-col justify-center"> <div className="ml-4 flex flex-col justify-center">
<p className="text-neutral text-xs tracking-wider">{name}</p> <p className="text-neutral text-xs tracking-wider">{name}</p>
<p className="font-thin text-6xl text-primary mt-0.5">{value}</p> <p className="font-thin text-5xl text-primary mt-0.5">{value}</p>
</div> </div>
</div> </div>
); );

View File

@ -13,7 +13,7 @@ export default function CardView({
isLoading?: boolean; isLoading?: boolean;
}) { }) {
return ( return (
<div className="grid min-[1900px]:grid-cols-4 xl:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5"> <div className="grid min-[1901px]:grid-cols-5 min-[1501px]:grid-cols-4 min-[881px]:grid-cols-3 min-[551px]:grid-cols-2 grid-cols-1 gap-5 pb-5">
{links.map((e, i) => { {links.map((e, i) => {
return ( return (
<LinkCard <LinkCard

View File

@ -128,11 +128,12 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
} }
> >
<div <div
className="rounded-2xl cursor-pointer" className="rounded-2xl cursor-pointer h-full flex flex-col justify-between"
onClick={() => onClick={() =>
!editMode && window.open(generateLinkHref(link, account), "_blank") !editMode && window.open(generateLinkHref(link, account), "_blank")
} }
> >
<div>
<div className="relative rounded-t-2xl h-40 overflow-hidden"> <div className="relative rounded-t-2xl h-40 overflow-hidden">
{previewAvailable(link) ? ( {previewAvailable(link) ? (
<Image <Image
@ -141,7 +142,9 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
height={720} height={720}
alt="" alt=""
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105" className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
style={{ filter: "blur(2px)" }} style={
link.type !== "image" ? { filter: "blur(1px)" } : undefined
}
draggable="false" draggable="false"
onError={(e) => { onError={(e) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@ -153,25 +156,30 @@ 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>
)} )}
{link.type !== "image" && (
<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 shadow rounded-md">
<LinkIcon link={link} /> <LinkIcon link={link} />
</div> </div>
)}
</div> </div>
<hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" /> <hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" />
</div>
<div className="p-3 mt-1"> <div className="flex flex-col justify-between h-full">
<p className="truncate w-full pr-8 text-primary"> <div className="p-3 flex flex-col gap-2">
{unescapeString(link.name || link.description) || link.url} <p className="truncate w-full pr-8 text-primary text-sm">
{unescapeString(link.name)}
</p> </p>
<LinkTypeBadge link={link} /> <LinkTypeBadge link={link} />
</div> </div>
<div>
<hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" /> <hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" />
<div className="flex justify-between text-xs text-neutral px-3 pb-1"> <div className="flex justify-between text-xs text-neutral px-3 pb-1 gap-2">
<div className="cursor-pointer w-fit"> <div className="cursor-pointer truncate">
{collection && ( {collection && (
<LinkCollection link={link} collection={collection} /> <LinkCollection link={link} collection={collection} />
)} )}
@ -179,6 +187,8 @@ export default function LinkCard({ link, flipDropdown, editMode }: Props) {
<LinkDate link={link} /> <LinkDate link={link} />
</div> </div>
</div> </div>
</div>
</div>
{showInfo && ( {showInfo && (
<div className="p-3 absolute z-30 top-0 left-0 right-0 bottom-0 bg-base-200 rounded-2xl fade-in overflow-y-auto"> <div className="p-3 absolute z-30 top-0 left-0 right-0 bottom-0 bg-base-200 rounded-2xl fade-in overflow-y-auto">

View File

@ -25,14 +25,12 @@ export default function LinkTypeBadge({
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
className="flex gap-1 item-center select-none text-neutral mt-1 hover:opacity-70 duration-100" className="flex gap-1 item-center select-none text-neutral hover:opacity-70 duration-100"
> >
<i className="bi-link-45deg text-lg leading-none"></i> <i className="bi-link-45deg text-lg leading-none"></i>
<p className="text-xs truncate">{shortendURL}</p> <p className="text-xs truncate">{shortendURL}</p>
</Link> </Link>
) : ( ) : (
<div className="badge badge-primary badge-sm my-1 select-none"> <div className="badge badge-primary badge-sm select-none">{link.type}</div>
{link.type}
</div>
); );
} }

View File

@ -30,7 +30,6 @@ type Props = {
}; };
export default function LinkMasonry({ link, flipDropdown, editMode }: Props) { export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
const viewMode = localStorage.getItem("viewMode") || "card";
const { collections } = useCollectionStore(); const { collections } = useCollectionStore();
const { account } = useAccountStore(); const { account } = useAccountStore();
@ -141,6 +140,9 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
height={720} height={720}
alt="" alt=""
className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105" className="rounded-t-2xl select-none object-cover z-10 h-40 w-full shadow opacity-80 scale-105"
style={
link.type !== "image" ? { filter: "blur(1px)" } : undefined
}
draggable="false" draggable="false"
onError={(e) => { onError={(e) => {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
@ -150,28 +152,29 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
) : link.preview === "unavailable" ? null : ( ) : link.preview === "unavailable" ? null : (
<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>
)} )}
{link.type !== "image" && (
<div className="absolute top-0 left-0 right-0 bottom-0 rounded-t-2xl flex items-center justify-center shadow rounded-md">
<LinkIcon link={link} />
</div>
)}
</div> </div>
{link.preview !== "unavailable" && ( {link.preview !== "unavailable" && (
<hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" /> <hr className="divider my-0 last:hidden border-t border-neutral-content h-[1px]" />
)} )}
<div className="p-3 mt-1 flex flex-col gap-2"> <div className="p-3 flex flex-col gap-2">
<div className="w-full pr-8"> <p className="hyphens-auto w-full pr-8 text-primary text-sm">
<div {unescapeString(link.name)}
className={`rounded-t-2xl flex items-center justify-center shadow rounded-md float-left mr-2 ${ </p>
link.type === "url" ? "" : "hidden"
}`} <LinkTypeBadge link={link} />
>
<LinkIcon link={link} size="small" className="mt-1" />
</div>
<p className="text-primary text-sm">{unescapeString(link.name)}</p>
{link.description && ( {link.description && (
<p className="text-sm">{unescapeString(link.description)}</p> <p className="hyphens-auto text-sm">
{unescapeString(link.description)}
</p>
)} )}
<LinkTypeBadge link={link} />
</div>
{link.tags[0] && ( {link.tags[0] && (
<div className="flex gap-1 items-center flex-wrap"> <div className="flex gap-1 items-center flex-wrap">
@ -193,12 +196,8 @@ export default function LinkMasonry({ link, flipDropdown, editMode }: Props) {
<hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" /> <hr className="divider mt-2 mb-1 last:hidden border-t border-neutral-content h-[1px]" />
<div className="flex justify-between gap-1 text-xs text-neutral px-3 pb-1"> <div className="flex flex-wrap justify-between text-xs text-neutral px-3 pb-1 w-full gap-x-2">
<div className="cursor-pointer w-fit truncate"> {collection && <LinkCollection link={link} collection={collection} />}
{collection && (
<LinkCollection link={link} collection={collection} />
)}
</div>
<LinkDate link={link} /> <LinkDate link={link} />
</div> </div>
</div> </div>

View File

@ -4,6 +4,7 @@ import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import Modal from "../Modal"; import Modal from "../Modal";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Button from "../ui/Button";
type Props = { type Props = {
onClose: Function; onClose: Function;
@ -59,13 +60,10 @@ export default function DeleteLinkModal({ onClose, activeLink }: Props) {
&apos;Delete&apos; to bypass this confirmation in the future. &apos;Delete&apos; to bypass this confirmation in the future.
</p> </p>
<button <Button className="ml-auto" intent="destructive" onClick={deleteLink}>
className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer`}
onClick={deleteLink}
>
<i className="bi-trash text-xl" /> <i className="bi-trash text-xl" />
Delete Delete
</button> </Button>
</div> </div>
</Modal> </Modal>
); );

View File

@ -1,6 +1,7 @@
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import Modal from "../Modal"; import Modal from "../Modal";
import useUserStore from "@/store/admin/users"; import useUserStore from "@/store/admin/users";
import Button from "../ui/Button";
type Props = { type Props = {
onClose: Function; onClose: Function;
@ -38,13 +39,10 @@ export default function DeleteUserModal({ onClose, userId }: Props) {
</span> </span>
</div> </div>
<button <Button className="ml-auto" intent="destructive" onClick={deleteUser}>
className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer`}
onClick={deleteUser}
>
<i className="bi-trash text-xl" /> <i className="bi-trash text-xl" />
Delete, I know what I&apos;m doing Delete, I know what I&apos;m doing
</button> </Button>
</div> </div>
</Modal> </Modal>
); );

View File

@ -30,12 +30,11 @@ export default function EmailChangeVerificationModal({
"Updating this field will change your billing email on Stripe as well."} "Updating this field will change your billing email on Stripe as well."}
</p> </p>
{process.env.NEXT_PUBLIC_GOOGLE_ENABLED === "true" && (
<p> <p>
If you change your email address, any existing Google SSO If you change your email address, any existing{" "}
{process.env.NEXT_PUBLIC_GOOGLE_ENABLED === "true" && "Google"} SSO
connections will be removed. connections will be removed.
</p> </p>
)}
<div> <div>
<p>Old Email</p> <p>Old Email</p>

View File

@ -5,6 +5,7 @@ import toast from "react-hot-toast";
import Modal from "../Modal"; import Modal from "../Modal";
import useTokenStore from "@/store/tokens"; import useTokenStore from "@/store/tokens";
import { dropdownTriggerer } from "@/lib/client/utils"; import { dropdownTriggerer } from "@/lib/client/utils";
import Button from "../ui/Button";
type Props = { type Props = {
onClose: Function; onClose: Function;
@ -90,18 +91,19 @@ export default function NewTokenModal({ onClose }: Props) {
<p className="mb-2">Expires in</p> <p className="mb-2">Expires in</p>
<div className="dropdown dropdown-bottom dropdown-end w-full"> <div className="dropdown dropdown-bottom dropdown-end w-full">
<div <Button
tabIndex={0} tabIndex={0}
role="button" role="button"
intent="secondary"
onMouseDown={dropdownTriggerer} onMouseDown={dropdownTriggerer}
className="btn btn-outline w-full sm:w-36 flex items-center btn-sm h-10" className="whitespace-nowrap w-32"
> >
{token.expires === TokenExpiry.sevenDays && "7 Days"} {token.expires === TokenExpiry.sevenDays && "7 Days"}
{token.expires === TokenExpiry.oneMonth && "30 Days"} {token.expires === TokenExpiry.oneMonth && "30 Days"}
{token.expires === TokenExpiry.twoMonths && "60 Days"} {token.expires === TokenExpiry.twoMonths && "60 Days"}
{token.expires === TokenExpiry.threeMonths && "90 Days"} {token.expires === TokenExpiry.threeMonths && "90 Days"}
{token.expires === TokenExpiry.never && "No Expiration"} {token.expires === TokenExpiry.never && "No Expiration"}
</div> </Button>
<ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-xl w-full sm:w-52 mt-1"> <ul className="dropdown-content z-[30] menu shadow bg-base-200 border border-neutral-content rounded-xl w-full sm:w-52 mt-1">
<li> <li>
<label <label

View File

@ -4,7 +4,7 @@ import Sidebar from "@/components/Sidebar";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import SearchBar from "@/components/SearchBar"; import SearchBar from "@/components/SearchBar";
import useWindowDimensions from "@/hooks/useWindowDimensions"; import useWindowDimensions from "@/hooks/useWindowDimensions";
import ToggleDarkMode from "./ui/ToggleDarkMode"; import ToggleDarkMode from "./ToggleDarkMode";
import NewLinkModal from "./ModalContent/NewLinkModal"; import NewLinkModal from "./ModalContent/NewLinkModal";
import NewCollectionModal from "./ModalContent/NewCollectionModal"; import NewCollectionModal from "./ModalContent/NewCollectionModal";
import UploadFileModal from "./ModalContent/UploadFileModal"; import UploadFileModal from "./ModalContent/UploadFileModal";

View File

@ -12,14 +12,15 @@ const buttonVariants = cva(
primary: "bg-primary text-primary-content hover:bg-primary/80", primary: "bg-primary text-primary-content hover:bg-primary/80",
secondary: secondary:
"bg-neutral-content text-secondary-foreground hover:bg-neutral-content/80 border border-neutral/30", "bg-neutral-content text-secondary-foreground hover:bg-neutral-content/80 border border-neutral/30",
destructive: "bg-error text-white hover:bg-error/80", destructive:
"bg-error text-white hover:bg-error/80 border border-neutral/60",
outline: outline:
"border border-input bg-background hover:bg-accent hover:text-accent-content", "border border-input bg-background hover:bg-accent hover:text-accent-content",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
small: "h-9 px-3", small: "h-7 px-2",
medium: "h-10 px-4 py-2", medium: "h-10 px-4 py-2",
full: "px-4 py-2 w-full", full: "px-4 py-2 w-full",
icon: "h-10 w-10", icon: "h-10 w-10",

View File

@ -40,6 +40,8 @@ export default function AuthRedirect({ children }: Props) {
{ path: "/collections", isProtected: true }, { path: "/collections", isProtected: true },
{ path: "/links", isProtected: true }, { path: "/links", isProtected: true },
{ path: "/tags", isProtected: true }, { path: "/tags", isProtected: true },
{ path: "/preserved", isProtected: true },
{ path: "/admin", isProtected: true },
]; ];
if (isPublicPage) { if (isPublicPage) {

View File

@ -1,5 +1,5 @@
import Navbar from "@/components/Navbar"; import Navbar from "@/components/Navbar";
import Announcement from "@/components/Announcement"; import Announcement from "@/components/AnnouncementBar";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
import { ReactNode, useEffect, useState } from "react"; import { ReactNode, useEffect, useState } from "react";
import getLatestVersion from "@/lib/client/getLatestVersion"; import getLatestVersion from "@/lib/client/getLatestVersion";

View File

@ -94,10 +94,11 @@ export default async function deleteUserById(
// Delete subscription // Delete subscription
if (process.env.STRIPE_SECRET_KEY) if (process.env.STRIPE_SECRET_KEY)
await prisma.subscription.delete({ await prisma.subscription
.delete({
where: { userId }, where: { userId },
}); })
// .catch((err) => console.log(err)); .catch((err) => console.log(err));
await prisma.usersAndCollections.deleteMany({ await prisma.usersAndCollections.deleteMany({
where: { where: {

View File

@ -120,8 +120,7 @@ export default function Dashboard() {
<ViewDropdown viewMode={viewMode} setViewMode={setViewMode} /> <ViewDropdown viewMode={viewMode} setViewMode={setViewMode} />
</div> </div>
<div> <div className="flex justify-evenly flex-col xl:flex-row xl:items-center gap-2 xl:w-full h-full rounded-2xl p-5 border border-neutral-content bg-base-200">
<div className="flex justify-evenly flex-col xl:flex-row xl:items-center gap-2 xl:w-full h-full rounded-2xl p-8 border border-neutral-content bg-base-200">
<DashboardItem <DashboardItem
name={numberOfLinks === 1 ? "Link" : "Links"} name={numberOfLinks === 1 ? "Link" : "Links"}
value={numberOfLinks} value={numberOfLinks}
@ -144,7 +143,6 @@ export default function Dashboard() {
icon={"bi-hash"} icon={"bi-hash"}
/> />
</div> </div>
</div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">

View File

@ -12,7 +12,7 @@ import Head from "next/head";
import useLinks from "@/hooks/useLinks"; import useLinks from "@/hooks/useLinks";
import useLinkStore from "@/store/links"; import useLinkStore from "@/store/links";
import ProfilePhoto from "@/components/ProfilePhoto"; import ProfilePhoto from "@/components/ProfilePhoto";
import ToggleDarkMode from "@/components/ui/ToggleDarkMode"; import ToggleDarkMode from "@/components/ToggleDarkMode";
import getPublicUserData from "@/lib/client/getPublicUserData"; import getPublicUserData from "@/lib/client/getPublicUserData";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";

View File

@ -13,6 +13,7 @@ import Link from "next/link";
import Checkbox from "@/components/Checkbox"; import Checkbox from "@/components/Checkbox";
import { dropdownTriggerer } from "@/lib/client/utils"; import { dropdownTriggerer } from "@/lib/client/utils";
import EmailChangeVerificationModal from "@/components/ModalContent/EmailChangeVerificationModal"; import EmailChangeVerificationModal from "@/components/ModalContent/EmailChangeVerificationModal";
import Button from "@/components/ui/Button";
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
@ -203,28 +204,29 @@ export default function Account() {
<div className="sm:row-span-2 sm:justify-self-center my-3"> <div className="sm:row-span-2 sm:justify-self-center my-3">
<p className="mb-2 sm:text-center">Profile Photo</p> <p className="mb-2 sm:text-center">Profile Photo</p>
<div className="w-28 h-28 flex items-center justify-center rounded-full relative"> <div className="w-28 h-28 flex gap-3 sm:flex-col items-center">
<ProfilePhoto <ProfilePhoto
priority={true} priority={true}
src={user.image ? user.image : undefined} src={user.image ? user.image : undefined}
large={true} large={true}
/> />
{user.image && (
<div <div className="dropdown dropdown-bottom">
onClick={() => <Button
setUser({ tabIndex={0}
...user, role="button"
image: "", size="small"
}) intent="secondary"
} onMouseDown={dropdownTriggerer}
className="absolute top-1 left-1 btn btn-xs btn-circle btn-neutral btn-outline bg-base-100" className="text-sm"
> >
<i className="bi-x"></i> <i className="bi-pencil-square text-md duration-100"></i>
</div> Edit
)} </Button>
<div className="absolute -bottom-3 left-0 right-0 mx-auto w-fit text-center"> <ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60">
<label className="btn btn-xs btn-neutral btn-outline bg-base-100"> <li>
Browse... <label tabIndex={0} role="button">
Upload a new photo...
<input <input
type="file" type="file"
name="photo" name="photo"
@ -234,6 +236,24 @@ export default function Account() {
onChange={handleImageUpload} onChange={handleImageUpload}
/> />
</label> </label>
</li>
{user.image && (
<li>
<div
tabIndex={0}
role="button"
onClick={() =>
setUser({
...user,
image: "",
})
}
>
Remove Photo
</div>
</li>
)}
</ul>
</div> </div>
</div> </div>
</div> </div>
@ -293,16 +313,18 @@ export default function Account() {
<div> <div>
<p className="mb-2">Import your data from other platforms.</p> <p className="mb-2">Import your data from other platforms.</p>
<div className="dropdown dropdown-bottom"> <div className="dropdown dropdown-bottom">
<div <Button
tabIndex={0} tabIndex={0}
role="button" role="button"
intent="secondary"
onMouseDown={dropdownTriggerer} onMouseDown={dropdownTriggerer}
className="flex gap-2 text-sm btn btn-outline btn-neutral group" className="text-sm"
id="import-dropdown" id="import-dropdown"
> >
<i className="bi-cloud-upload text-xl duration-100"></i> <i className="bi-cloud-upload text-xl duration-100"></i>
<p>Import From</p> Import From
</div> </Button>
<ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60"> <ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box mt-1 w-60">
<li> <li>
<label <label
@ -351,7 +373,7 @@ export default function Account() {
<div> <div>
<p className="mb-2">Download your data instantly.</p> <p className="mb-2">Download your data instantly.</p>
<Link className="w-fit" href="/api/v1/migration"> <Link className="w-fit" href="/api/v1/migration">
<div className="flex w-fit gap-2 text-sm btn btn-outline btn-neutral group"> <div className="select-none relative duration-200 rounded-lg text-sm text-center w-fit flex justify-center items-center gap-2 disabled:pointer-events-none disabled:opacity-50 bg-neutral-content text-secondary-foreground hover:bg-neutral-content/80 border border-neutral/30 h-10 px-4 py-2">
<i className="bi-cloud-download text-xl duration-100"></i> <i className="bi-cloud-download text-xl duration-100"></i>
<p>Export Data</p> <p>Export Data</p>
</div> </div>
@ -374,17 +396,12 @@ export default function Account() {
archived data you own.{" "} archived data you own.{" "}
{process.env.NEXT_PUBLIC_STRIPE {process.env.NEXT_PUBLIC_STRIPE
? "It will also cancel your subscription. " ? "It will also cancel your subscription. "
: undefined}{" "} : undefined}
You will be prompted to enter your password before the deletion
process.
</p> </p>
</div> </div>
<Link <Link href="/settings/delete" className="underline">
href="/settings/delete" Account deletion page
className="text-white w-full sm:w-fit flex items-center gap-2 py-2 px-4 rounded-md text-lg tracking-wide select-none font-semibold duration-100 bg-red-500 hover:bg-red-400 cursor-pointer"
>
<p className="text-center w-full">Delete Your Account</p>
</Link> </Link>
</div> </div>

View File

@ -21,6 +21,7 @@ export default function Billing() {
<a <a
href={process.env.NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL} href={process.env.NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL}
className="underline" className="underline"
target="_blank"
> >
Billing Portal Billing Portal
</a> </a>
@ -30,10 +31,7 @@ export default function Billing() {
<p className="text-md"> <p className="text-md">
If you still need help or encountered any issues, feel free to reach If you still need help or encountered any issues, feel free to reach
out to us at:{" "} out to us at:{" "}
<a <a className="font-semibold" href="mailto:support@linkwarden.app">
className="font-semibold underline"
href="mailto:support@linkwarden.app"
>
support@linkwarden.app support@linkwarden.app
</a> </a>
</p> </p>

View File

@ -4,6 +4,7 @@ import TextInput from "@/components/TextInput";
import CenteredForm from "@/layouts/CenteredForm"; import CenteredForm from "@/layouts/CenteredForm";
import { signOut, useSession } from "next-auth/react"; import { signOut, useSession } from "next-auth/react";
import Link from "next/link"; import Link from "next/link";
import Button from "@/components/ui/Button";
const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true"; const keycloakEnabled = process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true";
const authentikEnabled = process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true"; const authentikEnabled = process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true";
@ -135,20 +136,14 @@ export default function Delete() {
</fieldset> </fieldset>
) : undefined} ) : undefined}
<button <Button
className={`mx-auto text-white flex items-center gap-2 py-1 px-3 rounded-md text-lg tracking-wide select-none font-semibold duration-100 w-fit ${ className="mx-auto"
submitLoader intent="destructive"
? "bg-red-400 cursor-auto" loading={submitLoader}
: "bg-red-500 hover:bg-red-400 cursor-pointer" onClick={submit}
}`}
onClick={() => {
if (!submitLoader) {
submit();
}
}}
> >
<p className="text-center w-full">Delete Your Account</p> <p className="text-center w-full">Delete Your Account</p>
</button> </Button>
</div> </div>
</CenteredForm> </CenteredForm>
); );