commit
dd7fb973a9
|
@ -18,7 +18,10 @@ EMAIL_FROM=
|
||||||
EMAIL_SERVER=
|
EMAIL_SERVER=
|
||||||
|
|
||||||
# Stripe settings (You don't need these, it's for the cloud instance payments)
|
# Stripe settings (You don't need these, it's for the cloud instance payments)
|
||||||
|
NEXT_PUBLIC_STRIPE_IS_ACTIVE=
|
||||||
STRIPE_SECRET_KEY=
|
STRIPE_SECRET_KEY=
|
||||||
PRICE_ID=
|
PRICE_ID=
|
||||||
TRIAL_PERIOD_DAYS=
|
NEXT_PUBLIC_TRIAL_PERIOD_DAYS=
|
||||||
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL=
|
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL=
|
||||||
|
BASE_URL=http://localhost:3000
|
||||||
|
NEXT_PUBLIC_PRICING=
|
|
@ -12,6 +12,6 @@ First off, we really appreciate the time you spent!
|
||||||
|
|
||||||
If you found a vulnerability, these are the ways you can reach us:
|
If you found a vulnerability, these are the ways you can reach us:
|
||||||
|
|
||||||
Email: [hello@linkwarden.app](mailto:hello@daniel31x13.io)
|
Email: [security@linkwarden.app](mailto:security@linkwarden.app)
|
||||||
|
|
||||||
Or you can directly reach me via Twitter: [@daniel31x13](https://twitter.com/Daniel31X13).
|
Or you can directly DM me via Twitter: [@daniel31x13](https://twitter.com/Daniel31X13).
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default function CollectionSelection({ onChange, defaultValue }: Props) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
isClearable
|
isClearable
|
||||||
placeholder="Unnamed Collection"
|
placeholder="Default: Unnamed Collection"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
options={options}
|
options={options}
|
||||||
styles={styles}
|
styles={styles}
|
||||||
|
|
|
@ -143,18 +143,18 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
<div className="flex flex-col justify-between w-full">
|
<div className="flex flex-col justify-between w-full">
|
||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-baseline gap-1">
|
||||||
<p className="text-sm text-sky-400 font-bold">{count + 1}.</p>
|
<p className="text-sm text-sky-400 font-bold">{count + 1}.</p>
|
||||||
<p className="text-lg text-sky-500 font-bold truncate max-w-[10rem] capitalize">
|
<p className="text-lg text-sky-500 font-bold truncate capitalize w-full pr-8">
|
||||||
{link.name}
|
{link.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3 items-center flex-wrap my-3">
|
<div className="flex gap-3 items-center my-3">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1 w-full pr-20">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faFolder}
|
icon={faFolder}
|
||||||
className="w-4 h-4 mt-1 drop-shadow"
|
className="w-4 h-4 mt-1 drop-shadow"
|
||||||
style={{ color: collection?.color }}
|
style={{ color: collection?.color }}
|
||||||
/>
|
/>
|
||||||
<p className="text-sky-900 truncate max-w-[10rem] capitalize">
|
<p className="text-sky-900 truncate capitalize">
|
||||||
{collection?.name}
|
{collection?.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -163,7 +163,7 @@ export default function TeamManagement({
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
value={member.user.username}
|
value={member.user.username || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMember({
|
setMember({
|
||||||
...member,
|
...member,
|
||||||
|
@ -174,7 +174,7 @@ export default function TeamManagement({
|
||||||
e.key === "Enter" &&
|
e.key === "Enter" &&
|
||||||
addMemberToCollection(
|
addMemberToCollection(
|
||||||
session.data?.user.username as string,
|
session.data?.user.username as string,
|
||||||
member.user.username,
|
member.user.username || "",
|
||||||
collection,
|
collection,
|
||||||
setMemberState
|
setMemberState
|
||||||
)
|
)
|
||||||
|
@ -188,7 +188,7 @@ export default function TeamManagement({
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
addMemberToCollection(
|
addMemberToCollection(
|
||||||
session.data?.user.username as string,
|
session.data?.user.username as string,
|
||||||
member.user.username,
|
member.user.username || "",
|
||||||
collection,
|
collection,
|
||||||
setMemberState
|
setMemberState
|
||||||
)
|
)
|
||||||
|
|
|
@ -151,7 +151,7 @@ export default function LinkDetails({ link }: Props) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap- justify-end drop-shadow">
|
<div className="flex flex-col gap- justify-end drop-shadow">
|
||||||
<p className="text-2xl text-sky-500 capitalize hyphens-auto">
|
<p className="text-2xl text-sky-500 capitalize break-words hyphens-auto">
|
||||||
{link.name}
|
{link.name}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
|
|
|
@ -33,8 +33,8 @@ export default function PaymentPortal() {
|
||||||
<p className="text-md text-gray-500">
|
<p className="text-md text-gray-500">
|
||||||
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 className="font-semibold" href="mailto:hello@linkwarden.app">
|
<a className="font-semibold" href="mailto:support@linkwarden.app">
|
||||||
hello@linkwarden.app
|
support@linkwarden.app
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import { AccountSettings } from "@/types/global";
|
import { AccountSettings } from "@/types/global";
|
||||||
import useAccountStore from "@/store/account";
|
import useAccountStore from "@/store/account";
|
||||||
import { useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
|
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
|
||||||
import SubmitButton from "@/components/SubmitButton";
|
import SubmitButton from "@/components/SubmitButton";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
@ -23,7 +23,7 @@ export default function ChangePassword({
|
||||||
const [submitLoader, setSubmitLoader] = useState(false);
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
|
|
||||||
const { account, updateAccount } = useAccountStore();
|
const { account, updateAccount } = useAccountStore();
|
||||||
const { update } = useSession();
|
const { update, data } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
@ -37,38 +37,45 @@ export default function ChangePassword({
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (newPassword == "" || newPassword2 == "") {
|
if (newPassword == "" || newPassword2 == "") {
|
||||||
toast.error("Please fill all the fields.");
|
toast.error("Please fill all the fields.");
|
||||||
} else if (newPassword === newPassword2) {
|
|
||||||
setSubmitLoader(true);
|
|
||||||
|
|
||||||
const load = toast.loading("Applying...");
|
|
||||||
|
|
||||||
const response = await updateAccount({
|
|
||||||
...user,
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.dismiss(load);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
toast.success("Settings Applied!");
|
|
||||||
togglePasswordFormModal();
|
|
||||||
} else toast.error(response.data as string);
|
|
||||||
|
|
||||||
setSubmitLoader(false);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(user.username !== account.username || user.name !== account.name) &&
|
|
||||||
user.username &&
|
|
||||||
user.email
|
|
||||||
)
|
|
||||||
update({ username: user.username, name: user.name });
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
setUser({ ...user, newPassword: undefined });
|
|
||||||
togglePasswordFormModal();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.error("Passwords do not match.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newPassword !== newPassword2)
|
||||||
|
return toast.error("Passwords do not match.");
|
||||||
|
else if (newPassword.length < 8)
|
||||||
|
return toast.error("Passwords must be at least 8 characters.");
|
||||||
|
|
||||||
|
setSubmitLoader(true);
|
||||||
|
|
||||||
|
const load = toast.loading("Applying...");
|
||||||
|
|
||||||
|
const response = await updateAccount({
|
||||||
|
...user,
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.dismiss(load);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success("Settings Applied!");
|
||||||
|
|
||||||
|
if (user.email !== account.email) {
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
signOut();
|
||||||
|
} else if (
|
||||||
|
user.username !== account.username ||
|
||||||
|
user.name !== account.name
|
||||||
|
)
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
setUser({ ...user, newPassword: undefined });
|
||||||
|
togglePasswordFormModal();
|
||||||
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
|
setSubmitLoader(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
import Checkbox from "../../Checkbox";
|
import Checkbox from "../../Checkbox";
|
||||||
import useAccountStore from "@/store/account";
|
import useAccountStore from "@/store/account";
|
||||||
import { AccountSettings } from "@/types/global";
|
import { AccountSettings } from "@/types/global";
|
||||||
import { useSession } from "next-auth/react";
|
import { signOut, useSession } from "next-auth/react";
|
||||||
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
|
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
|
||||||
import SubmitButton from "../../SubmitButton";
|
import SubmitButton from "../../SubmitButton";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
|
@ -18,7 +18,7 @@ export default function PrivacySettings({
|
||||||
setUser,
|
setUser,
|
||||||
user,
|
user,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { update } = useSession();
|
const { update, data } = useSession();
|
||||||
const { account, updateAccount } = useAccountStore();
|
const { account, updateAccount } = useAccountStore();
|
||||||
|
|
||||||
const [submitLoader, setSubmitLoader] = useState(false);
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
|
@ -59,18 +59,25 @@ export default function PrivacySettings({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success("Settings Applied!");
|
toast.success("Settings Applied!");
|
||||||
toggleSettingsModal();
|
|
||||||
} else toast.error(response.data as string);
|
|
||||||
|
|
||||||
setSubmitLoader(false);
|
if (user.email !== account.email) {
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (user.username !== account.username || user.name !== account.name)
|
signOut();
|
||||||
update({ username: user.username, name: user.name });
|
} else if (
|
||||||
|
user.username !== account.username ||
|
||||||
|
user.name !== account.name
|
||||||
|
)
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
setUser({ ...user, newPassword: undefined });
|
setUser({ ...user, newPassword: undefined });
|
||||||
toggleSettingsModal();
|
toggleSettingsModal();
|
||||||
}
|
} else toast.error(response.data as string);
|
||||||
|
setSubmitLoader(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default function ProfileSettings({
|
||||||
setUser,
|
setUser,
|
||||||
user,
|
user,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { update } = useSession();
|
const { update, data } = useSession();
|
||||||
const { account, updateAccount } = useAccountStore();
|
const { account, updateAccount } = useAccountStore();
|
||||||
const [profileStatus, setProfileStatus] = useState(true);
|
const [profileStatus, setProfileStatus] = useState(true);
|
||||||
|
|
||||||
|
@ -77,21 +77,20 @@ export default function ProfileSettings({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success("Settings Applied!");
|
toast.success("Settings Applied!");
|
||||||
toggleSettingsModal();
|
|
||||||
|
|
||||||
if (
|
if (user.email !== account.email) {
|
||||||
user.username !== account.username ||
|
|
||||||
user.name !== account.name ||
|
|
||||||
user.email !== account.email
|
|
||||||
) {
|
|
||||||
update({
|
update({
|
||||||
username: user.username,
|
id: data?.user.id,
|
||||||
email: user.username,
|
|
||||||
name: user.name,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
signOut();
|
signOut();
|
||||||
}
|
} else if (
|
||||||
|
user.username !== account.username ||
|
||||||
|
user.name !== account.name
|
||||||
|
)
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
setUser({ ...user, newPassword: undefined });
|
setUser({ ...user, newPassword: undefined });
|
||||||
toggleSettingsModal();
|
toggleSettingsModal();
|
||||||
|
@ -124,7 +123,7 @@ export default function ProfileSettings({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="absolute -bottom-2 left-0 right-0 mx-auto w-fit text-center">
|
<div className="absolute -bottom-3 left-0 right-0 mx-auto w-fit text-center">
|
||||||
<label
|
<label
|
||||||
htmlFor="upload-photo"
|
htmlFor="upload-photo"
|
||||||
title="PNG or JPG (Max: 3MB)"
|
title="PNG or JPG (Max: 3MB)"
|
||||||
|
@ -159,7 +158,7 @@ export default function ProfileSettings({
|
||||||
<p className="text-sm text-sky-500 mb-2">Username</p>
|
<p className="text-sm text-sky-500 mb-2">Username</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={user.username}
|
value={user.username || ""}
|
||||||
onChange={(e) => setUser({ ...user, username: e.target.value })}
|
onChange={(e) => setUser({ ...user, username: e.target.value })}
|
||||||
className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
className="w-full rounded-md p-2 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||||
/>
|
/>
|
||||||
|
@ -176,6 +175,12 @@ export default function ProfileSettings({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
|
||||||
|
{user.email !== account.email ? (
|
||||||
|
<p className="text-gray-500">
|
||||||
|
You will need to log back in after you apply this Email.
|
||||||
|
</p>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import React from "react";
|
||||||
|
import useModalStore from "@/store/modals";
|
||||||
|
|
||||||
|
export default function NoLinksFound() {
|
||||||
|
const { setModal } = useModalStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border border-solid border-sky-100 w-full p-10 rounded-2xl">
|
||||||
|
<p className="text-center text-3xl text-sky-500">
|
||||||
|
You haven't created any Links Here
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<div className="text-center text-sky-900 text-sm flex items-baseline justify-center gap-1 w-full">
|
||||||
|
<p>Start by creating a</p>{" "}
|
||||||
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
setModal({
|
||||||
|
modal: "LINK",
|
||||||
|
state: true,
|
||||||
|
method: "CREATE",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="inline-flex gap-1 relative w-[7.2rem] items-center font-semibold select-none cursor-pointer p-2 px-3 rounded-full text-white bg-sky-500 hover:bg-sky-400 duration-100 group"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faPlus}
|
||||||
|
className="w-5 h-5 group-hover:ml-9 absolute duration-100"
|
||||||
|
/>
|
||||||
|
<span className="block group-hover:opacity-0 text-right w-full duration-100">
|
||||||
|
New Link
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -127,29 +127,41 @@ export default function Sidebar({ className }: { className?: string }) {
|
||||||
leaveTo="transform opacity-0 -translate-y-3"
|
leaveTo="transform opacity-0 -translate-y-3"
|
||||||
>
|
>
|
||||||
<Disclosure.Panel className="flex flex-col gap-1">
|
<Disclosure.Panel className="flex flex-col gap-1">
|
||||||
{collections
|
{collections[0] ? (
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
collections
|
||||||
.map((e, i) => {
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
return (
|
.map((e, i) => {
|
||||||
<Link key={i} href={`/collections/${e.id}`}>
|
return (
|
||||||
<div
|
<Link key={i} href={`/collections/${e.id}`}>
|
||||||
className={`${
|
<div
|
||||||
active === `/collections/${e.id}`
|
className={`${
|
||||||
? "bg-sky-200"
|
active === `/collections/${e.id}`
|
||||||
: "hover:bg-slate-200 bg-gray-100"
|
? "bg-sky-200"
|
||||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
: "hover:bg-slate-200 bg-gray-100"
|
||||||
>
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
||||||
<FontAwesomeIcon
|
>
|
||||||
icon={faFolder}
|
<FontAwesomeIcon
|
||||||
className="w-6 h-6 drop-shadow"
|
icon={faFolder}
|
||||||
style={{ color: e.color }}
|
className="w-6 h-6 drop-shadow"
|
||||||
/>
|
style={{ color: e.color }}
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="text-sky-600 truncate w-4/6">{e.name}</p>
|
<p className="text-sky-600 truncate w-full pr-7">
|
||||||
</div>
|
{e.name}
|
||||||
</Link>
|
</p>
|
||||||
);
|
</div>
|
||||||
})}
|
</Link>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`duration-100 py-1 px-2 flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
||||||
|
>
|
||||||
|
<p className="text-gray-500 text-xs font-semibold truncate w-full pr-7">
|
||||||
|
You Have No Collections...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
@ -175,28 +187,40 @@ export default function Sidebar({ className }: { className?: string }) {
|
||||||
leaveTo="transform opacity-0 -translate-y-3"
|
leaveTo="transform opacity-0 -translate-y-3"
|
||||||
>
|
>
|
||||||
<Disclosure.Panel className="flex flex-col gap-1">
|
<Disclosure.Panel className="flex flex-col gap-1">
|
||||||
{tags
|
{tags[0] ? (
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
tags
|
||||||
.map((e, i) => {
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
return (
|
.map((e, i) => {
|
||||||
<Link key={i} href={`/tags/${e.id}`}>
|
return (
|
||||||
<div
|
<Link key={i} href={`/tags/${e.id}`}>
|
||||||
className={`${
|
<div
|
||||||
active === `/tags/${e.id}`
|
className={`${
|
||||||
? "bg-sky-200"
|
active === `/tags/${e.id}`
|
||||||
: "hover:bg-slate-200 bg-gray-100"
|
? "bg-sky-200"
|
||||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
|
: "hover:bg-slate-200 bg-gray-100"
|
||||||
>
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
|
||||||
<FontAwesomeIcon
|
>
|
||||||
icon={faHashtag}
|
<FontAwesomeIcon
|
||||||
className="w-4 h-4 text-sky-500 mt-1"
|
icon={faHashtag}
|
||||||
/>
|
className="w-4 h-4 text-sky-500 mt-1"
|
||||||
|
/>
|
||||||
|
|
||||||
<p className="text-sky-600 truncate w-4/6">{e.name}</p>
|
<p className="text-sky-600 truncate w-full pr-7">
|
||||||
</div>
|
{e.name}
|
||||||
</Link>
|
</p>
|
||||||
);
|
</div>
|
||||||
})}
|
</Link>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={`duration-100 py-1 px-2 flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
||||||
|
>
|
||||||
|
<p className="text-gray-500 text-xs font-semibold truncate w-full pr-7">
|
||||||
|
You Have No Tags...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default function useInitialData() {
|
||||||
setCollections();
|
setCollections();
|
||||||
setTags();
|
setTags();
|
||||||
// setLinks();
|
// setLinks();
|
||||||
setAccount(data.user.username as string);
|
setAccount(data.user.id);
|
||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,26 @@ export default function AuthRedirect({ children }: Props) {
|
||||||
const { status, data } = useSession();
|
const { status, data } = useSession();
|
||||||
const [redirect, setRedirect] = useState(true);
|
const [redirect, setRedirect] = useState(true);
|
||||||
|
|
||||||
|
const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
|
||||||
|
|
||||||
useInitialData();
|
useInitialData();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!router.pathname.startsWith("/public")) {
|
if (!router.pathname.startsWith("/public")) {
|
||||||
if (status === "authenticated" && data.user.isSubscriber === false) {
|
if (
|
||||||
|
emailEnabled &&
|
||||||
|
status === "authenticated" &&
|
||||||
|
(data.user.isSubscriber === true ||
|
||||||
|
data.user.isSubscriber === undefined) &&
|
||||||
|
!data.user.username
|
||||||
|
) {
|
||||||
|
router.push("/choose-username").then(() => {
|
||||||
|
setRedirect(false);
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
status === "authenticated" &&
|
||||||
|
data.user.isSubscriber === false
|
||||||
|
) {
|
||||||
router.push("/subscribe").then(() => {
|
router.push("/subscribe").then(() => {
|
||||||
setRedirect(false);
|
setRedirect(false);
|
||||||
});
|
});
|
||||||
|
@ -28,6 +43,7 @@ export default function AuthRedirect({ children }: Props) {
|
||||||
router.pathname === "/register" ||
|
router.pathname === "/register" ||
|
||||||
router.pathname === "/confirmation" ||
|
router.pathname === "/confirmation" ||
|
||||||
router.pathname === "/subscribe" ||
|
router.pathname === "/subscribe" ||
|
||||||
|
router.pathname === "/choose-username" ||
|
||||||
router.pathname === "/forgot")
|
router.pathname === "/forgot")
|
||||||
) {
|
) {
|
||||||
router.push("/").then(() => {
|
router.push("/").then(() => {
|
||||||
|
|
|
@ -19,9 +19,10 @@ export default async function checkSubscription(
|
||||||
const isSubscriber = listByEmail.data.some((customer, i) => {
|
const isSubscriber = listByEmail.data.some((customer, i) => {
|
||||||
const hasValidSubscription = customer.subscriptions?.data.some(
|
const hasValidSubscription = customer.subscriptions?.data.some(
|
||||||
(subscription) => {
|
(subscription) => {
|
||||||
const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS;
|
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
|
||||||
const secondsInTwoWeeks = TRIAL_PERIOD_DAYS
|
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS;
|
||||||
? Number(TRIAL_PERIOD_DAYS) * 86400
|
const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS
|
||||||
|
? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400
|
||||||
: 1209600;
|
: 1209600;
|
||||||
|
|
||||||
subscriptionCanceledAt = subscription.canceled_at;
|
subscriptionCanceledAt = subscription.canceled_at;
|
||||||
|
|
|
@ -42,7 +42,7 @@ export default async function postCollection(
|
||||||
color: collection.color,
|
color: collection.color,
|
||||||
members: {
|
members: {
|
||||||
create: collection.members.map((e) => ({
|
create: collection.members.map((e) => ({
|
||||||
user: { connect: { username: e.user.username.toLowerCase() } },
|
user: { connect: { id: e.user.id } },
|
||||||
canCreate: e.canCreate,
|
canCreate: e.canCreate,
|
||||||
canUpdate: e.canUpdate,
|
canUpdate: e.canUpdate,
|
||||||
canDelete: e.canDelete,
|
canDelete: e.canDelete,
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default async function updateCollection(
|
||||||
isPublic: collection.isPublic,
|
isPublic: collection.isPublic,
|
||||||
members: {
|
members: {
|
||||||
create: collection.members.map((e) => ({
|
create: collection.members.map((e) => ({
|
||||||
user: { connect: { username: e.user.username.toLowerCase() } },
|
user: { connect: { id: e.user.id } },
|
||||||
canCreate: e.canCreate,
|
canCreate: e.canCreate,
|
||||||
canUpdate: e.canUpdate,
|
canUpdate: e.canUpdate,
|
||||||
canDelete: e.canDelete,
|
canDelete: e.canDelete,
|
||||||
|
|
|
@ -29,16 +29,16 @@ export default async function getUser({
|
||||||
return { response: "This profile is private.", status: 401 };
|
return { response: "This profile is private.", status: 401 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { password, ...unsensitiveInfo } = user;
|
const { password, ...lessSensitiveInfo } = user;
|
||||||
|
|
||||||
const data = isSelf
|
const data = isSelf
|
||||||
? // If user is requesting its own data
|
? // If user is requesting its own data
|
||||||
unsensitiveInfo
|
lessSensitiveInfo
|
||||||
: {
|
: {
|
||||||
// If user is requesting someone elses data
|
// If user is requesting someone elses data
|
||||||
id: unsensitiveInfo.id,
|
id: lessSensitiveInfo.id,
|
||||||
name: unsensitiveInfo.name,
|
name: lessSensitiveInfo.name,
|
||||||
username: unsensitiveInfo.username,
|
username: lessSensitiveInfo.username,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { response: data || null, status: 200 };
|
return { response: data || null, status: 200 };
|
||||||
|
|
|
@ -20,6 +20,15 @@ export default async function updateUser(
|
||||||
status: 400,
|
status: 400,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
|
||||||
|
|
||||||
|
if (!checkUsername.test(user.username.toLowerCase()))
|
||||||
|
return {
|
||||||
|
response:
|
||||||
|
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
|
||||||
|
status: 400,
|
||||||
|
};
|
||||||
|
|
||||||
const userIsTaken = await prisma.user.findFirst({
|
const userIsTaken = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: { not: sessionUser.id },
|
id: { not: sessionUser.id },
|
||||||
|
|
|
@ -4,19 +4,12 @@ import checkSubscription from "./checkSubscription";
|
||||||
export default async function paymentCheckout(
|
export default async function paymentCheckout(
|
||||||
stripeSecretKey: string,
|
stripeSecretKey: string,
|
||||||
email: string,
|
email: string,
|
||||||
action: "register" | "login",
|
|
||||||
priceId: string
|
priceId: string
|
||||||
) {
|
) {
|
||||||
const stripe = new Stripe(stripeSecretKey, {
|
const stripe = new Stripe(stripeSecretKey, {
|
||||||
apiVersion: "2022-11-15",
|
apiVersion: "2022-11-15",
|
||||||
});
|
});
|
||||||
|
|
||||||
// const a = await stripe.prices.retrieve("price_1NTn3PDaRUw6CJPLkw4dcwlJ");
|
|
||||||
|
|
||||||
// const listBySub = await stripe.subscriptions.list({
|
|
||||||
// customer: "cus_OGUzJrRea8Qbxx",
|
|
||||||
// });
|
|
||||||
|
|
||||||
const listByEmail = await stripe.customers.list({
|
const listByEmail = await stripe.customers.list({
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
expand: ["data.subscriptions"],
|
expand: ["data.subscriptions"],
|
||||||
|
@ -24,32 +17,8 @@ export default async function paymentCheckout(
|
||||||
|
|
||||||
const isExistingCostomer = listByEmail?.data[0]?.id || undefined;
|
const isExistingCostomer = listByEmail?.data[0]?.id || undefined;
|
||||||
|
|
||||||
// const hasPreviouslySubscribed = listByEmail.data.find((customer, i) => {
|
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
|
||||||
// const hasValidSubscription = customer.subscriptions?.data.some(
|
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS;
|
||||||
// (subscription) => {
|
|
||||||
// return subscription?.items?.data?.some(
|
|
||||||
// (subscriptionItem) => subscriptionItem?.plan?.id === priceId
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// customer.email?.toLowerCase() === email.toLowerCase() &&
|
|
||||||
// hasValidSubscription
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const previousSubscriptionId =
|
|
||||||
// hasPreviouslySubscribed?.subscriptions?.data[0].id;
|
|
||||||
|
|
||||||
// if (previousSubscriptionId) {
|
|
||||||
// console.log(previousSubscriptionId);
|
|
||||||
// const subscription = await stripe.subscriptions.resume(
|
|
||||||
// previousSubscriptionId
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS;
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
customer: isExistingCostomer ? isExistingCostomer : undefined,
|
customer: isExistingCostomer ? isExistingCostomer : undefined,
|
||||||
line_items: [
|
line_items: [
|
||||||
|
@ -60,13 +29,15 @@ export default async function paymentCheckout(
|
||||||
],
|
],
|
||||||
mode: "subscription",
|
mode: "subscription",
|
||||||
customer_email: isExistingCostomer ? undefined : email.toLowerCase(),
|
customer_email: isExistingCostomer ? undefined : email.toLowerCase(),
|
||||||
success_url: "http://localhost:3000?session_id={CHECKOUT_SESSION_ID}",
|
success_url: `${process.env.BASE_URL}?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
cancel_url: "http://localhost:3000/login",
|
cancel_url: `${process.env.BASE_URL}/login`,
|
||||||
automatic_tax: {
|
automatic_tax: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
subscription_data: {
|
subscription_data: {
|
||||||
trial_period_days: TRIAL_PERIOD_DAYS ? Number(TRIAL_PERIOD_DAYS) : 14,
|
trial_period_days: NEXT_PUBLIC_TRIAL_PERIOD_DAYS
|
||||||
|
? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS)
|
||||||
|
: 14,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,10 @@ export default async function updateCustomerEmail(
|
||||||
const customer = listByEmail.data.find((customer, i) => {
|
const customer = listByEmail.data.find((customer, i) => {
|
||||||
const hasValidSubscription = customer.subscriptions?.data.some(
|
const hasValidSubscription = customer.subscriptions?.data.some(
|
||||||
(subscription) => {
|
(subscription) => {
|
||||||
const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS;
|
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
|
||||||
const secondsInTwoWeeks = TRIAL_PERIOD_DAYS
|
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS;
|
||||||
? Number(TRIAL_PERIOD_DAYS) * 86400
|
const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS
|
||||||
|
? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400
|
||||||
: 1209600;
|
: 1209600;
|
||||||
|
|
||||||
const isNotCanceledOrHasTime = !(
|
const isNotCanceledOrHasTime = !(
|
||||||
|
|
|
@ -9,7 +9,7 @@ const addMemberToCollection = async (
|
||||||
setMember: (newMember: Member) => null | undefined
|
setMember: (newMember: Member) => null | undefined
|
||||||
) => {
|
) => {
|
||||||
const checkIfMemberAlreadyExists = collection.members.find((e) => {
|
const checkIfMemberAlreadyExists = collection.members.find((e) => {
|
||||||
const username = e.user.username.toLowerCase();
|
const username = (e.user.username || "").toLowerCase();
|
||||||
return username === memberUsername.toLowerCase();
|
return username === memberUsername.toLowerCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/prisma-adapter": "^1.0.0",
|
"@auth/prisma-adapter": "^1.0.1",
|
||||||
"@aws-sdk/client-s3": "^3.363.0",
|
"@aws-sdk/client-s3": "^3.363.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||||
|
@ -52,8 +52,8 @@
|
||||||
"@playwright/test": "^1.35.1",
|
"@playwright/test": "^1.35.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"postcss": "^8.4.24",
|
"postcss": "^8.4.26",
|
||||||
"prisma": "^4.16.2",
|
"prisma": "^4.16.2",
|
||||||
"tailwindcss": "^3.3.2"
|
"tailwindcss": "^3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
else if (session?.user?.isSubscriber === false)
|
else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"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(
|
const collectionIsAccessible = await getPermission(
|
||||||
|
|
|
@ -93,9 +93,10 @@ export const authOptions: AuthOptions = {
|
||||||
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
|
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
|
||||||
const PRICE_ID = process.env.PRICE_ID;
|
const PRICE_ID = process.env.PRICE_ID;
|
||||||
|
|
||||||
const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS;
|
const NEXT_PUBLIC_TRIAL_PERIOD_DAYS =
|
||||||
const secondsInTwoWeeks = TRIAL_PERIOD_DAYS
|
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS;
|
||||||
? Number(TRIAL_PERIOD_DAYS) * 86400
|
const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS
|
||||||
|
? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400
|
||||||
: 1209600;
|
: 1209600;
|
||||||
const subscriptionIsTimesUp =
|
const subscriptionIsTimesUp =
|
||||||
token.subscriptionCanceledAt &&
|
token.subscriptionCanceledAt &&
|
||||||
|
@ -110,15 +111,12 @@ export const authOptions: AuthOptions = {
|
||||||
PRICE_ID &&
|
PRICE_ID &&
|
||||||
(trigger || subscriptionIsTimesUp || !token.isSubscriber)
|
(trigger || subscriptionIsTimesUp || !token.isSubscriber)
|
||||||
) {
|
) {
|
||||||
console.log("EXECUTED!!!");
|
|
||||||
const subscription = await checkSubscription(
|
const subscription = await checkSubscription(
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
token.email as string,
|
token.email as string,
|
||||||
PRICE_ID
|
PRICE_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
subscription.isSubscriber;
|
|
||||||
|
|
||||||
if (subscription.subscriptionCanceledAt) {
|
if (subscription.subscriptionCanceledAt) {
|
||||||
token.subscriptionCanceledAt = subscription.subscriptionCanceledAt;
|
token.subscriptionCanceledAt = subscription.subscriptionCanceledAt;
|
||||||
} else token.subscriptionCanceledAt = undefined;
|
} else token.subscriptionCanceledAt = undefined;
|
||||||
|
@ -129,11 +127,20 @@ export const authOptions: AuthOptions = {
|
||||||
if (trigger === "signIn") {
|
if (trigger === "signIn") {
|
||||||
token.id = user.id;
|
token.id = user.id;
|
||||||
token.username = (user as any).username;
|
token.username = (user as any).username;
|
||||||
} else if (trigger === "update" && session?.name && session?.username) {
|
} else if (trigger === "update" && token.id) {
|
||||||
// Note, that `session` can be any arbitrary object, remember to validate it!
|
console.log(token);
|
||||||
token.name = session.name;
|
|
||||||
token.username = session.username.toLowerCase();
|
const user = await prisma.user.findUnique({
|
||||||
token.email = session.email.toLowerCase();
|
where: {
|
||||||
|
id: token.id as number,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
token.name = user.name;
|
||||||
|
token.username = user.username?.toLowerCase();
|
||||||
|
token.email = user.email?.toLowerCase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface Data {
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
name: string;
|
name: string;
|
||||||
username: string;
|
username?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ export default async function Index(
|
||||||
const body: User = req.body;
|
const body: User = req.body;
|
||||||
|
|
||||||
const checkHasEmptyFields = emailEnabled
|
const checkHasEmptyFields = emailEnabled
|
||||||
? !body.username || !body.password || !body.name || !body.email
|
? !body.password || !body.name || !body.email
|
||||||
: !body.username || !body.password || !body.name;
|
: !body.username || !body.password || !body.name;
|
||||||
|
|
||||||
if (checkHasEmptyFields)
|
if (checkHasEmptyFields)
|
||||||
|
@ -31,42 +31,22 @@ export default async function Index(
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ response: "Please fill out all the fields." });
|
.json({ response: "Please fill out all the fields." });
|
||||||
|
|
||||||
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
|
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
|
||||||
|
|
||||||
// Remove user's who aren't verified for more than 10 minutes
|
if (!emailEnabled && !checkUsername.test(body.username?.toLowerCase() || ""))
|
||||||
if (emailEnabled)
|
return res.status(400).json({
|
||||||
await prisma.user.deleteMany({
|
response:
|
||||||
where: {
|
"Username has to be between 3-30 characters, no spaces and special characters are allowed.",
|
||||||
OR: [
|
|
||||||
{
|
|
||||||
email: body.email,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: body.username,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
createdAt: {
|
|
||||||
lt: tenMinutesAgo,
|
|
||||||
},
|
|
||||||
emailVerified: null,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkIfUserExists = await prisma.user.findFirst({
|
const checkIfUserExists = await prisma.user.findFirst({
|
||||||
where: emailEnabled
|
where: emailEnabled
|
||||||
? {
|
? {
|
||||||
OR: [
|
email: body.email?.toLowerCase(),
|
||||||
{
|
|
||||||
username: body.username.toLowerCase(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: body.email?.toLowerCase(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
emailVerified: { not: null },
|
emailVerified: { not: null },
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
username: body.username.toLowerCase(),
|
username: (body.username as string).toLowerCase(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -78,14 +58,18 @@ export default async function Index(
|
||||||
await prisma.user.create({
|
await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
username: body.username.toLowerCase(),
|
username: emailEnabled
|
||||||
email: body.email?.toLowerCase(),
|
? undefined
|
||||||
|
: (body.username as string).toLowerCase(),
|
||||||
|
email: emailEnabled ? body.email?.toLowerCase() : undefined,
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json({ response: "User successfully created." });
|
return res.status(201).json({ response: "User successfully created." });
|
||||||
} else if (checkIfUserExists) {
|
} else if (checkIfUserExists) {
|
||||||
res.status(400).json({ response: "Username and/or Email already exists." });
|
return res
|
||||||
|
.status(400)
|
||||||
|
.json({ response: "Username and/or Email already exists." });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
const userId = session?.user.id;
|
const userId = session?.user.id;
|
||||||
const userName = session?.user.username?.toLowerCase();
|
const username = session?.user.username?.toLowerCase();
|
||||||
const queryId = Number(req.query.id);
|
const queryId = Number(req.query.id);
|
||||||
|
|
||||||
if (!userId || !userName)
|
if (!userId || !username)
|
||||||
return res
|
return res
|
||||||
.setHeader("Content-Type", "text/plain")
|
.setHeader("Content-Type", "text/plain")
|
||||||
.status(401)
|
.status(401)
|
||||||
|
@ -19,7 +19,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
else if (session?.user?.isSubscriber === false)
|
else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"You are not a subscriber, feel free to reach out to us at support@linkwarden.app in case of any issues.",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!queryId)
|
if (!queryId)
|
||||||
|
@ -37,7 +37,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
targetUser?.isPrivate &&
|
targetUser?.isPrivate &&
|
||||||
!targetUser.whitelistedUsers.includes(userName)
|
!targetUser.whitelistedUsers.includes(username)
|
||||||
) {
|
) {
|
||||||
return res
|
return res
|
||||||
.setHeader("Content-Type", "text/plain")
|
.setHeader("Content-Type", "text/plain")
|
||||||
|
|
|
@ -8,7 +8,7 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const PRICE_ID = process.env.PRICE_ID;
|
const PRICE_ID = process.env.PRICE_ID;
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
if (!session?.user?.username)
|
if (!session?.user?.id)
|
||||||
return res.status(401).json({ response: "You must be logged in." });
|
return res.status(401).json({ response: "You must be logged in." });
|
||||||
else if (!STRIPE_SECRET_KEY || !PRICE_ID) {
|
else if (!STRIPE_SECRET_KEY || !PRICE_ID) {
|
||||||
return res.status(400).json({ response: "Payment is disabled." });
|
return res.status(400).json({ response: "Payment is disabled." });
|
||||||
|
@ -18,7 +18,6 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const users = await paymentCheckout(
|
const users = await paymentCheckout(
|
||||||
STRIPE_SECRET_KEY,
|
STRIPE_SECRET_KEY,
|
||||||
session?.user.email,
|
session?.user.email,
|
||||||
"register",
|
|
||||||
PRICE_ID
|
PRICE_ID
|
||||||
);
|
);
|
||||||
return res.status(users.status).json({ response: users.response });
|
return res.status(users.status).json({ response: users.response });
|
||||||
|
|
|
@ -12,12 +12,12 @@ export default async function collections(
|
||||||
) {
|
) {
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
if (!session?.user?.username) {
|
if (!session?.user?.id) {
|
||||||
return res.status(401).json({ response: "You must be logged in." });
|
return res.status(401).json({ response: "You must be logged in." });
|
||||||
} else if (session?.user?.isSubscriber === false)
|
} else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"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") {
|
if (req.method === "GET") {
|
||||||
|
|
|
@ -9,12 +9,12 @@ import updateLink from "@/lib/api/controllers/links/updateLink";
|
||||||
export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
export default async function links(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
if (!session?.user?.username) {
|
if (!session?.user?.id) {
|
||||||
return res.status(401).json({ response: "You must be logged in." });
|
return res.status(401).json({ response: "You must be logged in." });
|
||||||
} else if (session?.user?.isSubscriber === false)
|
} else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"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") {
|
if (req.method === "GET") {
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) {
|
||||||
} else if (session?.user?.isSubscriber === false)
|
} else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"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") {
|
if (req.method === "GET") {
|
||||||
|
|
|
@ -7,17 +7,20 @@ import updateUser from "@/lib/api/controllers/users/updateUser";
|
||||||
export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getServerSession(req, res, authOptions);
|
const session = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
if (!session?.user.username) {
|
if (!session?.user.id) {
|
||||||
return res.status(401).json({ response: "You must be logged in." });
|
return res.status(401).json({ response: "You must be logged in." });
|
||||||
} else if (session?.user?.isSubscriber === false)
|
} else if (session?.user?.isSubscriber === false)
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
response:
|
response:
|
||||||
"You are not a subscriber, feel free to reach out to us at hello@linkwarden.app in case of any issues.",
|
"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 lookupUsername = (req.query.username as string) || undefined;
|
||||||
const lookupId = Number(req.query.id) || undefined;
|
const lookupId = Number(req.query.id) || undefined;
|
||||||
const isSelf = session.user.username === lookupUsername ? true : false;
|
const isSelf =
|
||||||
|
session.user.username === lookupUsername || session.user.id === lookupId
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
const users = await getUsers({
|
const users = await getUsers({
|
||||||
|
@ -29,15 +32,8 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) {
|
||||||
username: session.user.username,
|
username: session.user.username,
|
||||||
});
|
});
|
||||||
return res.status(users.status).json({ response: users.response });
|
return res.status(users.status).json({ response: users.response });
|
||||||
} else if (req.method === "PUT" && !req.body.password) {
|
} else if (req.method === "PUT") {
|
||||||
const updated = await updateUser(req.body, session.user);
|
const updated = await updateUser(req.body, session.user);
|
||||||
return res.status(updated.status).json({ response: updated.response });
|
return res.status(updated.status).json({ response: updated.response });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
|
||||||
// lookupUsername,
|
|
||||||
// lookupId,
|
|
||||||
// },
|
|
||||||
// isSelf,
|
|
||||||
// session.user.username
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import SubmitButton from "@/components/SubmitButton";
|
||||||
|
import { signOut } from "next-auth/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import useAccountStore from "@/store/account";
|
||||||
|
|
||||||
|
export default function Subscribe() {
|
||||||
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
|
const [inputedUsername, setInputedUsername] = useState("");
|
||||||
|
|
||||||
|
const { data, status, update } = useSession();
|
||||||
|
|
||||||
|
const { updateAccount, account } = useAccountStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(data?.user);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
async function submitUsername() {
|
||||||
|
setSubmitLoader(true);
|
||||||
|
|
||||||
|
const redirectionToast = toast.loading("Applying...");
|
||||||
|
|
||||||
|
const response = await updateAccount({
|
||||||
|
...account,
|
||||||
|
username: inputedUsername,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success("Username Applied!");
|
||||||
|
|
||||||
|
update({
|
||||||
|
id: data?.user.id,
|
||||||
|
});
|
||||||
|
} else toast.error(response.data as string);
|
||||||
|
toast.dismiss(redirectionToast);
|
||||||
|
setSubmitLoader(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
src="/linkwarden.png"
|
||||||
|
width={1694}
|
||||||
|
height={483}
|
||||||
|
alt="Linkwarden"
|
||||||
|
className="h-12 w-fit mx-auto mt-10"
|
||||||
|
/>
|
||||||
|
<div className="p-2 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[30rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
||||||
|
<p className="text-xl text-sky-500 w-fit font-bold">
|
||||||
|
Choose a Username (Last step)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
||||||
|
Username
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="john"
|
||||||
|
value={inputedUsername}
|
||||||
|
onChange={(e) => setInputedUsername(e.target.value)}
|
||||||
|
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-md text-gray-500 mt-1">
|
||||||
|
Feel free to reach out to us at{" "}
|
||||||
|
<a className="font-semibold" href="mailto:support@linkwarden.app">
|
||||||
|
support@linkwarden.app
|
||||||
|
</a>{" "}
|
||||||
|
in case of any issues.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SubmitButton
|
||||||
|
onClick={submitUsername}
|
||||||
|
label="Complete Registration"
|
||||||
|
className="mt-2 w-full text-center"
|
||||||
|
loading={submitLoader}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
onClick={() => signOut()}
|
||||||
|
className="w-fit mx-auto cursor-pointer text-gray-500 font-semibold "
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-xs text-gray-500 my-10">
|
||||||
|
© {new Date().getFullYear()} Linkwarden. All rights reserved.{" "}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import SortDropdown from "@/components/SortDropdown";
|
||||||
import useModalStore from "@/store/modals";
|
import useModalStore from "@/store/modals";
|
||||||
import useLinks from "@/hooks/useLinks";
|
import useLinks from "@/hooks/useLinks";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
|
import NoLinksFound from "@/components/NoLinksFound";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { setModal } = useModalStore();
|
const { setModal } = useModalStore();
|
||||||
|
@ -59,7 +60,7 @@ export default function Index() {
|
||||||
style={{ color: activeCollection?.color }}
|
style={{ color: activeCollection?.color }}
|
||||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-3 drop-shadow"
|
className="sm:w-8 sm:h-8 w-6 h-6 mt-3 drop-shadow"
|
||||||
/>
|
/>
|
||||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold py-1">
|
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold w-full py-1 break-words hyphens-auto">
|
||||||
{activeCollection?.name}
|
{activeCollection?.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -234,13 +235,17 @@ export default function Index() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid 2xl:grid-cols-3 xl:grid-cols-2 gap-5">
|
{links[0] ? (
|
||||||
{links
|
<div className="grid 2xl:grid-cols-3 xl:grid-cols-2 gap-5">
|
||||||
.filter((e) => e.collectionId === Number(router.query.id))
|
{links
|
||||||
.map((e, i) => {
|
.filter((e) => e.collectionId === Number(router.query.id))
|
||||||
return <LinkCard key={i} link={e} count={i} />;
|
.map((e, i) => {
|
||||||
})}
|
return <LinkCard key={i} link={e} count={i} />;
|
||||||
</div>
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<NoLinksFound />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -167,7 +167,7 @@ export default function Dashboard() {
|
||||||
</div>
|
</div>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
) : (
|
) : (
|
||||||
<div className="border border-solid border-sky-100 w-full mx-auto md:w-2/3 p-10 rounded-md">
|
<div className="border border-solid border-sky-100 w-full mx-auto md:w-2/3 p-10 rounded-2xl">
|
||||||
<p className="text-center text-2xl text-sky-500">
|
<p className="text-center text-2xl text-sky-500">
|
||||||
No Pinned Links
|
No Pinned Links
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -39,20 +39,21 @@ export default function Forgot() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-2 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
<Image
|
||||||
<div className="flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
|
src="/linkwarden.png"
|
||||||
<Image
|
width={1694}
|
||||||
src="/linkwarden.png"
|
height={483}
|
||||||
width={1694}
|
alt="Linkwarden"
|
||||||
height={483}
|
className="h-12 w-fit mx-auto mt-10"
|
||||||
alt="Linkwarden"
|
/>
|
||||||
className="h-12 w-fit"
|
<div className="p-2 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[30rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
||||||
/>
|
<p className="text-xl text-sky-500 w-fit font-bold">Fogot Password?</p>
|
||||||
<div className="text-center sm:text-right">
|
<p className="text-md text-gray-500 mt-1">
|
||||||
<p className="text-3xl text-sky-500">Password Reset</p>
|
Enter your Email so we can send you a link to recover your account.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
<p className="text-md text-gray-500 mt-1">
|
||||||
|
Make sure to change your password in the profile settings afterwards.
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">Email</p>
|
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">Email</p>
|
||||||
|
|
||||||
|
@ -63,10 +64,6 @@ export default function Forgot() {
|
||||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||||
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||||
/>
|
/>
|
||||||
<p className="text-md text-gray-500 mt-1">
|
|
||||||
Make sure to change your password in the profile settings
|
|
||||||
afterwards.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
|
@ -81,6 +78,9 @@ export default function Forgot() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-center text-xs text-gray-500 my-10">
|
||||||
|
© {new Date().getFullYear()} Linkwarden. All rights reserved.{" "}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import LinkCard from "@/components/LinkCard";
|
import LinkCard from "@/components/LinkCard";
|
||||||
|
import NoLinksFound from "@/components/NoLinksFound";
|
||||||
import SortDropdown from "@/components/SortDropdown";
|
import SortDropdown from "@/components/SortDropdown";
|
||||||
import useLinks from "@/hooks/useLinks";
|
import useLinks from "@/hooks/useLinks";
|
||||||
import MainLayout from "@/layouts/MainLayout";
|
import MainLayout from "@/layouts/MainLayout";
|
||||||
|
@ -52,11 +53,15 @@ export default function Links() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid 2xl:grid-cols-3 xl:grid-cols-2 gap-5">
|
{links[0] ? (
|
||||||
{links.map((e, i) => {
|
<div className="grid 2xl:grid-cols-3 xl:grid-cols-2 gap-5">
|
||||||
return <LinkCard key={i} link={e} count={i} />;
|
{links.map((e, i) => {
|
||||||
})}
|
return <LinkCard key={i} link={e} count={i} />;
|
||||||
</div>
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<NoLinksFound />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
|
|
|
@ -46,23 +46,20 @@ export default function Login() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-2 my-10 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
<Image
|
||||||
<div className="text-right flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
|
src="/linkwarden.png"
|
||||||
<Image
|
width={1694}
|
||||||
src="/linkwarden.png"
|
height={483}
|
||||||
width={1694}
|
alt="Linkwarden"
|
||||||
height={483}
|
className="h-12 w-fit mx-auto mt-10"
|
||||||
alt="Linkwarden"
|
/>
|
||||||
className="h-12 w-fit"
|
<p className="text-xl font-semibold text-sky-500 px-2 text-center">
|
||||||
/>
|
Sign in to your account
|
||||||
<div className="text-center sm:text-right">
|
</p>
|
||||||
<p className="text-3xl text-sky-500">Welcome back</p>
|
<div className="p-2 my-10 mx-auto flex flex-col gap-3 justify-between sm:w-[30rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
||||||
<p className="text-md font-semibold text-sky-400">
|
<p className="text-xl text-sky-500 w-fit font-bold">
|
||||||
Sign in to your account
|
Enter your credentials
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
||||||
Username
|
Username
|
||||||
|
@ -112,6 +109,9 @@ export default function Login() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-center text-xs text-gray-500 mb-10">
|
||||||
|
© {new Date().getFullYear()} Linkwarden. All rights reserved.{" "}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
name: string;
|
name: string;
|
||||||
username: string;
|
username?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
password: string;
|
password: string;
|
||||||
passwordConfirmation: string;
|
passwordConfirmation: string;
|
||||||
|
@ -20,7 +20,7 @@ export default function Register() {
|
||||||
|
|
||||||
const [form, setForm] = useState<FormData>({
|
const [form, setForm] = useState<FormData>({
|
||||||
name: "",
|
name: "",
|
||||||
username: "",
|
username: emailEnabled ? undefined : "",
|
||||||
email: emailEnabled ? "" : undefined,
|
email: emailEnabled ? "" : undefined,
|
||||||
password: "",
|
password: "",
|
||||||
passwordConfirmation: "",
|
passwordConfirmation: "",
|
||||||
|
@ -31,7 +31,6 @@ export default function Register() {
|
||||||
if (emailEnabled) {
|
if (emailEnabled) {
|
||||||
return (
|
return (
|
||||||
form.name !== "" &&
|
form.name !== "" &&
|
||||||
form.username !== "" &&
|
|
||||||
form.email !== "" &&
|
form.email !== "" &&
|
||||||
form.password !== "" &&
|
form.password !== "" &&
|
||||||
form.passwordConfirmation !== ""
|
form.passwordConfirmation !== ""
|
||||||
|
@ -54,35 +53,35 @@ export default function Register() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (checkHasEmptyFields()) {
|
if (checkHasEmptyFields()) {
|
||||||
if (form.password === form.passwordConfirmation) {
|
if (form.password !== form.passwordConfirmation)
|
||||||
const { passwordConfirmation, ...request } = form;
|
return toast.error("Passwords do not match.");
|
||||||
|
else if (form.password.length < 8)
|
||||||
|
return toast.error("Passwords must be at least 8 characters.");
|
||||||
|
const { passwordConfirmation, ...request } = form;
|
||||||
|
|
||||||
setSubmitLoader(true);
|
setSubmitLoader(true);
|
||||||
|
|
||||||
const load = toast.loading("Creating Account...");
|
const load = toast.loading("Creating Account...");
|
||||||
|
|
||||||
const response = await fetch("/api/auth/register", {
|
const response = await fetch("/api/auth/register", {
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
toast.dismiss(load);
|
toast.dismiss(load);
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (form.email) await sendConfirmation();
|
if (form.email) await sendConfirmation();
|
||||||
|
|
||||||
toast.success("User Created!");
|
toast.success("User Created!");
|
||||||
} else {
|
|
||||||
toast.error(data.response);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
toast.error("Passwords do not match.");
|
toast.error(data.response);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error("Please fill out all the fields.");
|
toast.error("Please fill out all the fields.");
|
||||||
|
@ -91,23 +90,24 @@ export default function Register() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-2 mx-auto my-10 flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
<Image
|
||||||
<div className="flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
|
src="/linkwarden.png"
|
||||||
<Image
|
width={1694}
|
||||||
src="/linkwarden.png"
|
height={483}
|
||||||
width={1694}
|
alt="Linkwarden"
|
||||||
height={483}
|
className="h-12 w-fit mx-auto mt-10"
|
||||||
alt="Linkwarden"
|
/>
|
||||||
className="h-12 w-fit"
|
<p className="text-center px-2 text-xl font-semibold text-sky-500">
|
||||||
/>
|
{process.env.NEXT_PUBLIC_STRIPE_IS_ACTIVE
|
||||||
<div className="text-center sm:text-right">
|
? `Start using our premium services with a ${
|
||||||
<p className="text-3xl text-sky-500">Get started</p>
|
process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS || 14
|
||||||
<p className="text-md font-semibold text-sky-400">
|
}-day free trial!`
|
||||||
Create a new account
|
: "Create a new account"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<div className="p-2 mx-auto my-10 flex flex-col gap-3 justify-between sm:w-[30rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
||||||
</div>
|
<p className="text-xl text-sky-500 w-fit font-bold">
|
||||||
|
Enter your details
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
||||||
Display Name
|
Display Name
|
||||||
|
@ -122,19 +122,21 @@ export default function Register() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
{emailEnabled ? undefined : (
|
||||||
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
<div>
|
||||||
Username
|
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
|
||||||
</p>
|
Username
|
||||||
|
</p>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="john"
|
placeholder="john"
|
||||||
value={form.username}
|
value={form.username}
|
||||||
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
onChange={(e) => setForm({ ...form, username: e.target.value })}
|
||||||
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{emailEnabled ? (
|
{emailEnabled ? (
|
||||||
<div>
|
<div>
|
||||||
|
@ -196,6 +198,9 @@ export default function Register() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-center text-xs text-gray-500 mb-10">
|
||||||
|
© {new Date().getFullYear()} Linkwarden. All rights reserved.{" "}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,6 @@ export default function Subscribe() {
|
||||||
const { data, status } = useSession();
|
const { data, status } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(data?.user);
|
|
||||||
}, [status]);
|
|
||||||
|
|
||||||
async function loginUser() {
|
async function loginUser() {
|
||||||
setSubmitLoader(true);
|
setSubmitLoader(true);
|
||||||
|
|
||||||
|
@ -30,31 +26,26 @@ export default function Subscribe() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="p-2 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
<Image
|
||||||
<div className="flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
|
src="/linkwarden.png"
|
||||||
<Image
|
width={1694}
|
||||||
src="/linkwarden.png"
|
height={483}
|
||||||
width={1694}
|
alt="Linkwarden"
|
||||||
height={483}
|
className="h-12 w-fit mx-auto mt-10"
|
||||||
alt="Linkwarden"
|
/>
|
||||||
className="h-12 w-fit"
|
<p className="text-xl font-semibold text-sky-500 text-center px-2">
|
||||||
/>
|
{process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS || 14} days free trial, then
|
||||||
<div className="text-center sm:text-right">
|
${process.env.NEXT_PUBLIC_PRICING}/month afterwards
|
||||||
<p className="text-3xl text-sky-500">14 days free trial</p>
|
</p>
|
||||||
<p className="text-md font-semibold text-sky-400">
|
<div className="p-2 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[30rem] w-80 bg-slate-50 rounded-md border border-sky-100">
|
||||||
Then $5/month afterwards
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-md text-gray-500 mt-1">
|
<p className="text-md text-gray-500 mt-1">
|
||||||
You will be redirected to Stripe.
|
You will be redirected to Stripe.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-md text-gray-500 mt-1">
|
<p className="text-md text-gray-500 mt-1">
|
||||||
feel free to reach out to us at{" "}
|
Feel free to reach out to us at{" "}
|
||||||
<a className="font-semibold" href="mailto:hello@linkwarden.app">
|
<a className="font-semibold" href="mailto:support@linkwarden.app">
|
||||||
hello@linkwarden.app
|
support@linkwarden.app
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
in case of any issues.
|
in case of any issues.
|
||||||
</p>
|
</p>
|
||||||
|
@ -74,6 +65,9 @@ export default function Subscribe() {
|
||||||
Sign Out
|
Sign Out
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-center text-xs text-gray-500 my-10">
|
||||||
|
© {new Date().getFullYear()} Linkwarden. All rights reserved.{" "}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ CREATE TABLE "Session" (
|
||||||
CREATE TABLE "User" (
|
CREATE TABLE "User" (
|
||||||
"id" SERIAL NOT NULL,
|
"id" SERIAL NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"username" TEXT NOT NULL,
|
"username" TEXT,
|
||||||
"email" TEXT,
|
"email" TEXT,
|
||||||
"emailVerified" TIMESTAMP(3),
|
"emailVerified" TIMESTAMP(3),
|
||||||
"image" TEXT,
|
"image" TEXT,
|
|
@ -38,7 +38,7 @@ model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
|
|
||||||
username String @unique
|
username String? @unique
|
||||||
|
|
||||||
email String? @unique
|
email String? @unique
|
||||||
emailVerified DateTime?
|
emailVerified DateTime?
|
||||||
|
|
|
@ -3,19 +3,19 @@ import { AccountSettings } from "@/types/global";
|
||||||
|
|
||||||
type ResponseObject = {
|
type ResponseObject = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
data: object | string;
|
data: Omit<AccountSettings, "password"> | object | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type AccountStore = {
|
type AccountStore = {
|
||||||
account: AccountSettings;
|
account: AccountSettings;
|
||||||
setAccount: (username: string) => void;
|
setAccount: (id: number) => void;
|
||||||
updateAccount: (user: AccountSettings) => Promise<ResponseObject>;
|
updateAccount: (user: AccountSettings) => Promise<ResponseObject>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useAccountStore = create<AccountStore>()((set) => ({
|
const useAccountStore = create<AccountStore>()((set) => ({
|
||||||
account: {} as AccountSettings,
|
account: {} as AccountSettings,
|
||||||
setAccount: async (username) => {
|
setAccount: async (id) => {
|
||||||
const response = await fetch(`/api/routes/users?username=${username}`);
|
const response = await fetch(`/api/routes/users?id=${id}`);
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,13 @@ declare global {
|
||||||
EMAIL_FROM?: string;
|
EMAIL_FROM?: string;
|
||||||
EMAIL_SERVER?: string;
|
EMAIL_SERVER?: string;
|
||||||
|
|
||||||
|
NEXT_PUBLIC_STRIPE_IS_ACTIVE?: string;
|
||||||
STRIPE_SECRET_KEY?: string;
|
STRIPE_SECRET_KEY?: string;
|
||||||
PRICE_ID?: string;
|
PRICE_ID?: string;
|
||||||
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL?: string;
|
NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL?: string;
|
||||||
TRIAL_PERIOD_DAYS?: string;
|
NEXT_PUBLIC_TRIAL_PERIOD_DAYS?: string;
|
||||||
|
BASE_URL?: string;
|
||||||
|
NEXT_PUBLIC_PRICING?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
60
yarn.lock
60
yarn.lock
|
@ -12,10 +12,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
|
||||||
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
|
||||||
|
|
||||||
"@auth/core@0.8.1":
|
"@auth/core@0.9.0":
|
||||||
version "0.8.1"
|
version "0.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.8.1.tgz#8fbfb7b11ed3e4b346857b033e454efb7c16df26"
|
resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.9.0.tgz#7a5d66eea0bc059cef072734698547ae2a0c86a6"
|
||||||
integrity sha512-WudBmZudZ/cvykxHV5hIwrYsd7AlETQ535O7w3sSiiumT28+U9GvBb8oSRtfzxpW9rym3lAdfeTJqGA8U4FecQ==
|
integrity sha512-W2WO0WCBg1T3P8+yjQPzurTQhPv6ecBYfJ2oE3uvXPAX5ZLWAMSjKFAIa9oLZy5pwrB+YehJZPnlIxVilhrVcg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@panva/hkdf" "^1.0.4"
|
"@panva/hkdf" "^1.0.4"
|
||||||
cookie "0.5.0"
|
cookie "0.5.0"
|
||||||
|
@ -24,12 +24,12 @@
|
||||||
preact "10.11.3"
|
preact "10.11.3"
|
||||||
preact-render-to-string "5.2.3"
|
preact-render-to-string "5.2.3"
|
||||||
|
|
||||||
"@auth/prisma-adapter@^1.0.0":
|
"@auth/prisma-adapter@^1.0.1":
|
||||||
version "1.0.0"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-1.0.0.tgz#9107498921997b6174e0189553f29d0eb49ba2a0"
|
resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-1.0.1.tgz#eba93843e77018fa7ca0d68726aa959d6b60512c"
|
||||||
integrity sha512-+x+s5dgpNmqrcQC2ZRAXZIM6yhkWP/EXjIUgqUyMepLiX1OHi2AXIUAAbXsW4oG9OpYr/rvPIzPBpuGt6sPFwQ==
|
integrity sha512-sBp9l/jVr7l9y7rp2Pv6eoP7i8X2CgRNE3jDWJ0B/u+HnKRofXflD1cldPqRSAkJhqH3UxhVtMTEijT9FoofmQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@auth/core" "0.8.1"
|
"@auth/core" "0.9.0"
|
||||||
|
|
||||||
"@aws-crypto/crc32@3.0.0":
|
"@aws-crypto/crc32@3.0.0":
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -3391,12 +3391,7 @@ levn@^0.4.1:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
type-check "~0.4.0"
|
type-check "~0.4.0"
|
||||||
|
|
||||||
lilconfig@^2.0.5:
|
lilconfig@^2.0.5, lilconfig@^2.1.0:
|
||||||
version "2.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
|
|
||||||
integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==
|
|
||||||
|
|
||||||
lilconfig@^2.1.0:
|
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||||
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
|
||||||
|
@ -3540,12 +3535,7 @@ mz@^2.7.0:
|
||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nanoid@^3.3.4:
|
nanoid@^3.3.4, nanoid@^3.3.6:
|
||||||
version "3.3.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
|
||||||
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
|
|
||||||
|
|
||||||
nanoid@^3.3.6:
|
|
||||||
version "3.3.6"
|
version "3.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||||
|
@ -3981,10 +3971,10 @@ postcss@8.4.14:
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
postcss@^8.4.23, postcss@^8.4.24:
|
postcss@^8.4.23, postcss@^8.4.26:
|
||||||
version "8.4.24"
|
version "8.4.26"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
|
||||||
integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
|
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid "^3.3.6"
|
nanoid "^3.3.6"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
|
@ -4237,16 +4227,7 @@ resolve-from@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||||
|
|
||||||
resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.1:
|
resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.1, resolve@^1.22.2:
|
||||||
version "1.22.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
|
||||||
integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
|
|
||||||
dependencies:
|
|
||||||
is-core-module "^2.9.0"
|
|
||||||
path-parse "^1.0.7"
|
|
||||||
supports-preserve-symlinks-flag "^1.0.0"
|
|
||||||
|
|
||||||
resolve@^1.22.2:
|
|
||||||
version "1.22.2"
|
version "1.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
|
||||||
integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
|
integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
|
||||||
|
@ -4564,10 +4545,10 @@ synckit@^0.8.4:
|
||||||
"@pkgr/utils" "^2.3.1"
|
"@pkgr/utils" "^2.3.1"
|
||||||
tslib "^2.5.0"
|
tslib "^2.5.0"
|
||||||
|
|
||||||
tailwindcss@^3.3.2:
|
tailwindcss@^3.3.3:
|
||||||
version "3.3.2"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
||||||
integrity sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==
|
integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@alloc/quick-lru" "^5.2.0"
|
"@alloc/quick-lru" "^5.2.0"
|
||||||
arg "^5.0.2"
|
arg "^5.0.2"
|
||||||
|
@ -4589,7 +4570,6 @@ tailwindcss@^3.3.2:
|
||||||
postcss-load-config "^4.0.1"
|
postcss-load-config "^4.0.1"
|
||||||
postcss-nested "^6.0.1"
|
postcss-nested "^6.0.1"
|
||||||
postcss-selector-parser "^6.0.11"
|
postcss-selector-parser "^6.0.11"
|
||||||
postcss-value-parser "^4.2.0"
|
|
||||||
resolve "^1.22.2"
|
resolve "^1.22.2"
|
||||||
sucrase "^3.32.0"
|
sucrase "^3.32.0"
|
||||||
|
|
||||||
|
|
Ŝarĝante…
Reference in New Issue