diff --git a/.env.sample b/.env.sample index 3413f7c..8e0d4a5 100644 --- a/.env.sample +++ b/.env.sample @@ -18,7 +18,10 @@ EMAIL_FROM= EMAIL_SERVER= # Stripe settings (You don't need these, it's for the cloud instance payments) +NEXT_PUBLIC_STRIPE_IS_ACTIVE= STRIPE_SECRET_KEY= PRICE_ID= -TRIAL_PERIOD_DAYS= -NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL= \ No newline at end of file +NEXT_PUBLIC_TRIAL_PERIOD_DAYS= +NEXT_PUBLIC_STRIPE_BILLING_PORTAL_URL= +BASE_URL=http://localhost:3000 +NEXT_PUBLIC_PRICING= \ No newline at end of file diff --git a/.github/SECURITY.md b/.github/SECURITY.md index cfe046c..4e1c81f 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -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: -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). diff --git a/components/InputSelect/CollectionSelection.tsx b/components/InputSelect/CollectionSelection.tsx index d7307a0..f140bbc 100644 --- a/components/InputSelect/CollectionSelection.tsx +++ b/components/InputSelect/CollectionSelection.tsx @@ -45,7 +45,7 @@ export default function CollectionSelection({ onChange, defaultValue }: Props) { return ( { setMember({ ...member, @@ -174,7 +174,7 @@ export default function TeamManagement({ e.key === "Enter" && addMemberToCollection( session.data?.user.username as string, - member.user.username, + member.user.username || "", collection, setMemberState ) @@ -188,7 +188,7 @@ export default function TeamManagement({ onClick={() => addMemberToCollection( session.data?.user.username as string, - member.user.username, + member.user.username || "", collection, setMemberState ) diff --git a/components/Modal/Link/LinkDetails.tsx b/components/Modal/Link/LinkDetails.tsx index 3d18233..1847c92 100644 --- a/components/Modal/Link/LinkDetails.tsx +++ b/components/Modal/Link/LinkDetails.tsx @@ -151,7 +151,7 @@ export default function LinkDetails({ link }: Props) { /> )}
-

+

{link.name}

If you still need help or encountered any issues, feel free to reach out to us at:{" "} - - hello@linkwarden.app + + support@linkwarden.app

diff --git a/components/Modal/User/ChangePassword.tsx b/components/Modal/User/ChangePassword.tsx index 6237291..4c95d8e 100644 --- a/components/Modal/User/ChangePassword.tsx +++ b/components/Modal/User/ChangePassword.tsx @@ -1,7 +1,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { AccountSettings } from "@/types/global"; 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 SubmitButton from "@/components/SubmitButton"; import { toast } from "react-hot-toast"; @@ -23,7 +23,7 @@ export default function ChangePassword({ const [submitLoader, setSubmitLoader] = useState(false); const { account, updateAccount } = useAccountStore(); - const { update } = useSession(); + const { update, data } = useSession(); useEffect(() => { if ( @@ -37,38 +37,45 @@ export default function ChangePassword({ const submit = async () => { if (newPassword == "" || newPassword2 == "") { 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 ( diff --git a/components/Modal/User/PrivacySettings.tsx b/components/Modal/User/PrivacySettings.tsx index 8957d5f..ef6cd58 100644 --- a/components/Modal/User/PrivacySettings.tsx +++ b/components/Modal/User/PrivacySettings.tsx @@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; import Checkbox from "../../Checkbox"; import useAccountStore from "@/store/account"; 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 SubmitButton from "../../SubmitButton"; import { toast } from "react-hot-toast"; @@ -18,7 +18,7 @@ export default function PrivacySettings({ setUser, user, }: Props) { - const { update } = useSession(); + const { update, data } = useSession(); const { account, updateAccount } = useAccountStore(); const [submitLoader, setSubmitLoader] = useState(false); @@ -59,18 +59,25 @@ export default function PrivacySettings({ if (response.ok) { 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) - update({ username: user.username, name: user.name }); + signOut(); + } else if ( + user.username !== account.username || + user.name !== account.name + ) + update({ + id: data?.user.id, + }); - if (response.ok) { setUser({ ...user, newPassword: undefined }); toggleSettingsModal(); - } + } else toast.error(response.data as string); + setSubmitLoader(false); }; return ( diff --git a/components/Modal/User/ProfileSettings.tsx b/components/Modal/User/ProfileSettings.tsx index 57e81c4..5716b68 100644 --- a/components/Modal/User/ProfileSettings.tsx +++ b/components/Modal/User/ProfileSettings.tsx @@ -23,7 +23,7 @@ export default function ProfileSettings({ setUser, user, }: Props) { - const { update } = useSession(); + const { update, data } = useSession(); const { account, updateAccount } = useAccountStore(); const [profileStatus, setProfileStatus] = useState(true); @@ -77,21 +77,20 @@ export default function ProfileSettings({ if (response.ok) { toast.success("Settings Applied!"); - toggleSettingsModal(); - if ( - user.username !== account.username || - user.name !== account.name || - user.email !== account.email - ) { + if (user.email !== account.email) { update({ - username: user.username, - email: user.username, - name: user.name, + id: data?.user.id, }); signOut(); - } + } else if ( + user.username !== account.username || + user.name !== account.name + ) + update({ + id: data?.user.id, + }); setUser({ ...user, newPassword: undefined }); toggleSettingsModal(); @@ -124,7 +123,7 @@ export default function ProfileSettings({ )} -
+
) : undefined} + + {user.email !== account.email ? ( +

+ You will need to log back in after you apply this Email. +

+ ) : undefined}
diff --git a/components/NoLinksFound.tsx b/components/NoLinksFound.tsx new file mode 100644 index 0000000..e1c07e6 --- /dev/null +++ b/components/NoLinksFound.tsx @@ -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 ( +
+

+ You haven't created any Links Here +

+
+
+

Start by creating a

{" "} +
{ + 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" + > + + + New Link + +
+
+
+ ); +} diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index d7dc970..9866653 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -127,29 +127,41 @@ export default function Sidebar({ className }: { className?: string }) { leaveTo="transform opacity-0 -translate-y-3" > - {collections - .sort((a, b) => a.name.localeCompare(b.name)) - .map((e, i) => { - return ( - -
- + {collections[0] ? ( + collections + .sort((a, b) => a.name.localeCompare(b.name)) + .map((e, i) => { + return ( + +
+ -

{e.name}

-
- - ); - })} +

+ {e.name} +

+
+ + ); + }) + ) : ( +
+

+ You Have No Collections... +

+
+ )}
@@ -175,28 +187,40 @@ export default function Sidebar({ className }: { className?: string }) { leaveTo="transform opacity-0 -translate-y-3" > - {tags - .sort((a, b) => a.name.localeCompare(b.name)) - .map((e, i) => { - return ( - -
- + {tags[0] ? ( + tags + .sort((a, b) => a.name.localeCompare(b.name)) + .map((e, i) => { + return ( + +
+ -

{e.name}

-
- - ); - })} +

+ {e.name} +

+
+ + ); + }) + ) : ( +
+

+ You Have No Tags... +

+
+ )}
diff --git a/hooks/useInitialData.tsx b/hooks/useInitialData.tsx index 5655de3..a25fadd 100644 --- a/hooks/useInitialData.tsx +++ b/hooks/useInitialData.tsx @@ -17,7 +17,7 @@ export default function useInitialData() { setCollections(); setTags(); // setLinks(); - setAccount(data.user.username as string); + setAccount(data.user.id); } }, [status]); } diff --git a/layouts/AuthRedirect.tsx b/layouts/AuthRedirect.tsx index 35c2ceb..a5fd1ad 100644 --- a/layouts/AuthRedirect.tsx +++ b/layouts/AuthRedirect.tsx @@ -14,11 +14,26 @@ export default function AuthRedirect({ children }: Props) { const { status, data } = useSession(); const [redirect, setRedirect] = useState(true); + const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; + useInitialData(); useEffect(() => { 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(() => { setRedirect(false); }); @@ -28,6 +43,7 @@ export default function AuthRedirect({ children }: Props) { router.pathname === "/register" || router.pathname === "/confirmation" || router.pathname === "/subscribe" || + router.pathname === "/choose-username" || router.pathname === "/forgot") ) { router.push("/").then(() => { diff --git a/lib/api/checkSubscription.ts b/lib/api/checkSubscription.ts index 67f001c..6537c22 100644 --- a/lib/api/checkSubscription.ts +++ b/lib/api/checkSubscription.ts @@ -19,9 +19,10 @@ export default async function checkSubscription( const isSubscriber = listByEmail.data.some((customer, i) => { const hasValidSubscription = customer.subscriptions?.data.some( (subscription) => { - const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS; - const secondsInTwoWeeks = TRIAL_PERIOD_DAYS - ? Number(TRIAL_PERIOD_DAYS) * 86400 + const NEXT_PUBLIC_TRIAL_PERIOD_DAYS = + process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS; + const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS + ? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400 : 1209600; subscriptionCanceledAt = subscription.canceled_at; diff --git a/lib/api/controllers/collections/postCollection.ts b/lib/api/controllers/collections/postCollection.ts index 213266a..8e4cc99 100644 --- a/lib/api/controllers/collections/postCollection.ts +++ b/lib/api/controllers/collections/postCollection.ts @@ -42,7 +42,7 @@ export default async function postCollection( color: collection.color, members: { create: collection.members.map((e) => ({ - user: { connect: { username: e.user.username.toLowerCase() } }, + user: { connect: { id: e.user.id } }, canCreate: e.canCreate, canUpdate: e.canUpdate, canDelete: e.canDelete, diff --git a/lib/api/controllers/collections/updateCollection.ts b/lib/api/controllers/collections/updateCollection.ts index ac3dc65..7d2c65e 100644 --- a/lib/api/controllers/collections/updateCollection.ts +++ b/lib/api/controllers/collections/updateCollection.ts @@ -43,7 +43,7 @@ export default async function updateCollection( isPublic: collection.isPublic, members: { create: collection.members.map((e) => ({ - user: { connect: { username: e.user.username.toLowerCase() } }, + user: { connect: { id: e.user.id } }, canCreate: e.canCreate, canUpdate: e.canUpdate, canDelete: e.canDelete, diff --git a/lib/api/controllers/users/getUsers.ts b/lib/api/controllers/users/getUsers.ts index b75ea7f..381a713 100644 --- a/lib/api/controllers/users/getUsers.ts +++ b/lib/api/controllers/users/getUsers.ts @@ -29,16 +29,16 @@ export default async function getUser({ return { response: "This profile is private.", status: 401 }; } - const { password, ...unsensitiveInfo } = user; + const { password, ...lessSensitiveInfo } = user; const data = isSelf ? // If user is requesting its own data - unsensitiveInfo + lessSensitiveInfo : { // If user is requesting someone elses data - id: unsensitiveInfo.id, - name: unsensitiveInfo.name, - username: unsensitiveInfo.username, + id: lessSensitiveInfo.id, + name: lessSensitiveInfo.name, + username: lessSensitiveInfo.username, }; return { response: data || null, status: 200 }; diff --git a/lib/api/controllers/users/updateUser.ts b/lib/api/controllers/users/updateUser.ts index 064dd84..8f4eb0f 100644 --- a/lib/api/controllers/users/updateUser.ts +++ b/lib/api/controllers/users/updateUser.ts @@ -20,6 +20,15 @@ export default async function updateUser( 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({ where: { id: { not: sessionUser.id }, diff --git a/lib/api/paymentCheckout.ts b/lib/api/paymentCheckout.ts index 63dac04..06dd7e3 100644 --- a/lib/api/paymentCheckout.ts +++ b/lib/api/paymentCheckout.ts @@ -4,19 +4,12 @@ import checkSubscription from "./checkSubscription"; export default async function paymentCheckout( stripeSecretKey: string, email: string, - action: "register" | "login", priceId: string ) { const stripe = new Stripe(stripeSecretKey, { 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({ email: email.toLowerCase(), expand: ["data.subscriptions"], @@ -24,32 +17,8 @@ export default async function paymentCheckout( const isExistingCostomer = listByEmail?.data[0]?.id || undefined; - // const hasPreviouslySubscribed = listByEmail.data.find((customer, i) => { - // const hasValidSubscription = customer.subscriptions?.data.some( - // (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 NEXT_PUBLIC_TRIAL_PERIOD_DAYS = + process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS; const session = await stripe.checkout.sessions.create({ customer: isExistingCostomer ? isExistingCostomer : undefined, line_items: [ @@ -60,13 +29,15 @@ export default async function paymentCheckout( ], mode: "subscription", customer_email: isExistingCostomer ? undefined : email.toLowerCase(), - success_url: "http://localhost:3000?session_id={CHECKOUT_SESSION_ID}", - cancel_url: "http://localhost:3000/login", + success_url: `${process.env.BASE_URL}?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${process.env.BASE_URL}/login`, automatic_tax: { enabled: true, }, 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, }, }); diff --git a/lib/api/updateCustomerEmail.ts b/lib/api/updateCustomerEmail.ts index 07af401..95ebc60 100644 --- a/lib/api/updateCustomerEmail.ts +++ b/lib/api/updateCustomerEmail.ts @@ -18,9 +18,10 @@ export default async function updateCustomerEmail( const customer = listByEmail.data.find((customer, i) => { const hasValidSubscription = customer.subscriptions?.data.some( (subscription) => { - const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS; - const secondsInTwoWeeks = TRIAL_PERIOD_DAYS - ? Number(TRIAL_PERIOD_DAYS) * 86400 + const NEXT_PUBLIC_TRIAL_PERIOD_DAYS = + process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS; + const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS + ? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400 : 1209600; const isNotCanceledOrHasTime = !( diff --git a/lib/client/addMemberToCollection.ts b/lib/client/addMemberToCollection.ts index f1ec14f..3e705ec 100644 --- a/lib/client/addMemberToCollection.ts +++ b/lib/client/addMemberToCollection.ts @@ -9,7 +9,7 @@ const addMemberToCollection = async ( setMember: (newMember: Member) => null | undefined ) => { const checkIfMemberAlreadyExists = collection.members.find((e) => { - const username = e.user.username.toLowerCase(); + const username = (e.user.username || "").toLowerCase(); return username === memberUsername.toLowerCase(); }); diff --git a/package.json b/package.json index cc005e9..4f58de5 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "lint": "next lint" }, "dependencies": { - "@auth/prisma-adapter": "^1.0.0", + "@auth/prisma-adapter": "^1.0.1", "@aws-sdk/client-s3": "^3.363.0", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", @@ -52,8 +52,8 @@ "@playwright/test": "^1.35.1", "@types/bcrypt": "^5.0.0", "autoprefixer": "^10.4.14", - "postcss": "^8.4.24", + "postcss": "^8.4.26", "prisma": "^4.16.2", - "tailwindcss": "^3.3.2" + "tailwindcss": "^3.3.3" } } diff --git a/pages/api/archives/[...params].ts b/pages/api/archives/[...params].ts index bfbc4a5..757f635 100644 --- a/pages/api/archives/[...params].ts +++ b/pages/api/archives/[...params].ts @@ -18,7 +18,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { else if (session?.user?.isSubscriber === false) res.status(401).json({ 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( diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index 6777e1d..bdf85c5 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -93,9 +93,10 @@ export const authOptions: AuthOptions = { const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY; const PRICE_ID = process.env.PRICE_ID; - const TRIAL_PERIOD_DAYS = process.env.TRIAL_PERIOD_DAYS; - const secondsInTwoWeeks = TRIAL_PERIOD_DAYS - ? Number(TRIAL_PERIOD_DAYS) * 86400 + const NEXT_PUBLIC_TRIAL_PERIOD_DAYS = + process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS; + const secondsInTwoWeeks = NEXT_PUBLIC_TRIAL_PERIOD_DAYS + ? Number(NEXT_PUBLIC_TRIAL_PERIOD_DAYS) * 86400 : 1209600; const subscriptionIsTimesUp = token.subscriptionCanceledAt && @@ -110,15 +111,12 @@ export const authOptions: AuthOptions = { PRICE_ID && (trigger || subscriptionIsTimesUp || !token.isSubscriber) ) { - console.log("EXECUTED!!!"); const subscription = await checkSubscription( STRIPE_SECRET_KEY, token.email as string, PRICE_ID ); - subscription.isSubscriber; - if (subscription.subscriptionCanceledAt) { token.subscriptionCanceledAt = subscription.subscriptionCanceledAt; } else token.subscriptionCanceledAt = undefined; @@ -129,11 +127,20 @@ export const authOptions: AuthOptions = { if (trigger === "signIn") { token.id = user.id; token.username = (user as any).username; - } else if (trigger === "update" && session?.name && session?.username) { - // Note, that `session` can be any arbitrary object, remember to validate it! - token.name = session.name; - token.username = session.username.toLowerCase(); - token.email = session.email.toLowerCase(); + } else if (trigger === "update" && token.id) { + console.log(token); + + const user = await prisma.user.findUnique({ + where: { + id: token.id as number, + }, + }); + + if (user) { + token.name = user.name; + token.username = user.username?.toLowerCase(); + token.email = user.email?.toLowerCase(); + } } return token; }, diff --git a/pages/api/auth/register.ts b/pages/api/auth/register.ts index 9e91bee..c7e5136 100644 --- a/pages/api/auth/register.ts +++ b/pages/api/auth/register.ts @@ -11,7 +11,7 @@ interface Data { interface User { name: string; - username: string; + username?: string; email?: string; password: string; } @@ -23,7 +23,7 @@ export default async function Index( const body: User = req.body; const checkHasEmptyFields = emailEnabled - ? !body.username || !body.password || !body.name || !body.email + ? !body.password || !body.name || !body.email : !body.username || !body.password || !body.name; if (checkHasEmptyFields) @@ -31,42 +31,22 @@ export default async function Index( .status(400) .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) - await prisma.user.deleteMany({ - where: { - OR: [ - { - email: body.email, - }, - { - username: body.username, - }, - ], - createdAt: { - lt: tenMinutesAgo, - }, - emailVerified: null, - }, + if (!emailEnabled && !checkUsername.test(body.username?.toLowerCase() || "")) + return res.status(400).json({ + response: + "Username has to be between 3-30 characters, no spaces and special characters are allowed.", }); const checkIfUserExists = await prisma.user.findFirst({ where: emailEnabled ? { - OR: [ - { - username: body.username.toLowerCase(), - }, - { - email: body.email?.toLowerCase(), - }, - ], + email: body.email?.toLowerCase(), 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({ data: { name: body.name, - username: body.username.toLowerCase(), - email: body.email?.toLowerCase(), + username: emailEnabled + ? undefined + : (body.username as string).toLowerCase(), + email: emailEnabled ? body.email?.toLowerCase() : undefined, password: hashedPassword, }, }); - res.status(201).json({ response: "User successfully created." }); + return res.status(201).json({ response: "User successfully created." }); } 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." }); } } diff --git a/pages/api/avatar/[id].ts b/pages/api/avatar/[id].ts index 0287229..73b155b 100644 --- a/pages/api/avatar/[id].ts +++ b/pages/api/avatar/[id].ts @@ -8,10 +8,10 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { const session = await getServerSession(req, res, authOptions); const userId = session?.user.id; - const userName = session?.user.username?.toLowerCase(); + const username = session?.user.username?.toLowerCase(); const queryId = Number(req.query.id); - if (!userId || !userName) + if (!userId || !username) return res .setHeader("Content-Type", "text/plain") .status(401) @@ -19,7 +19,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { else if (session?.user?.isSubscriber === false) res.status(401).json({ 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) @@ -37,7 +37,7 @@ export default async function Index(req: NextApiRequest, res: NextApiResponse) { if ( targetUser?.isPrivate && - !targetUser.whitelistedUsers.includes(userName) + !targetUser.whitelistedUsers.includes(username) ) { return res .setHeader("Content-Type", "text/plain") diff --git a/pages/api/payment/index.ts b/pages/api/payment/index.ts index 66566b7..cd37caf 100644 --- a/pages/api/payment/index.ts +++ b/pages/api/payment/index.ts @@ -8,7 +8,7 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { const PRICE_ID = process.env.PRICE_ID; 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." }); else if (!STRIPE_SECRET_KEY || !PRICE_ID) { 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( STRIPE_SECRET_KEY, session?.user.email, - "register", PRICE_ID ); return res.status(users.status).json({ response: users.response }); diff --git a/pages/api/routes/collections/index.ts b/pages/api/routes/collections/index.ts index 25fec73..2132add 100644 --- a/pages/api/routes/collections/index.ts +++ b/pages/api/routes/collections/index.ts @@ -12,12 +12,12 @@ export default async function collections( ) { 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." }); } else if (session?.user?.isSubscriber === false) res.status(401).json({ 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") { diff --git a/pages/api/routes/links/index.ts b/pages/api/routes/links/index.ts index 4a226d4..eb5be9d 100644 --- a/pages/api/routes/links/index.ts +++ b/pages/api/routes/links/index.ts @@ -9,12 +9,12 @@ import updateLink from "@/lib/api/controllers/links/updateLink"; export default async function links(req: NextApiRequest, res: NextApiResponse) { 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." }); } else if (session?.user?.isSubscriber === false) res.status(401).json({ 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") { diff --git a/pages/api/routes/tags/index.ts b/pages/api/routes/tags/index.ts index 0df83a8..5841ace 100644 --- a/pages/api/routes/tags/index.ts +++ b/pages/api/routes/tags/index.ts @@ -11,7 +11,7 @@ export default async function tags(req: NextApiRequest, res: NextApiResponse) { } else if (session?.user?.isSubscriber === false) res.status(401).json({ 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") { diff --git a/pages/api/routes/users/index.ts b/pages/api/routes/users/index.ts index 6757bab..cd310bf 100644 --- a/pages/api/routes/users/index.ts +++ b/pages/api/routes/users/index.ts @@ -7,17 +7,20 @@ import updateUser from "@/lib/api/controllers/users/updateUser"; export default async function users(req: NextApiRequest, res: NextApiResponse) { const session = await getServerSession(req, res, authOptions); - if (!session?.user.username) { + if (!session?.user.id) { return res.status(401).json({ response: "You must be logged in." }); } else if (session?.user?.isSubscriber === false) res.status(401).json({ response: - "You are not a subscriber, feel free to reach out to us at 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 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") { const users = await getUsers({ @@ -29,15 +32,8 @@ export default async function users(req: NextApiRequest, res: NextApiResponse) { username: session.user.username, }); 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); return res.status(updated.status).json({ response: updated.response }); } } - -// { -// lookupUsername, -// lookupId, -// }, -// isSelf, -// session.user.username diff --git a/pages/choose-username.tsx b/pages/choose-username.tsx new file mode 100644 index 0000000..8a5bcaf --- /dev/null +++ b/pages/choose-username.tsx @@ -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 ( + <> + Linkwarden +
+

+ Choose a Username (Last step) +

+ +
+

+ Username +

+ + 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" + /> +
+
+

+ Feel free to reach out to us at{" "} + + support@linkwarden.app + {" "} + in case of any issues. +

+
+ + + +
signOut()} + className="w-fit mx-auto cursor-pointer text-gray-500 font-semibold " + > + Sign Out +
+
+

+ © {new Date().getFullYear()} Linkwarden. All rights reserved.{" "} +

+ + ); +} diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index 7c6d688..93a4631 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -18,6 +18,7 @@ import SortDropdown from "@/components/SortDropdown"; import useModalStore from "@/store/modals"; import useLinks from "@/hooks/useLinks"; import usePermissions from "@/hooks/usePermissions"; +import NoLinksFound from "@/components/NoLinksFound"; export default function Index() { const { setModal } = useModalStore(); @@ -59,7 +60,7 @@ export default function Index() { style={{ color: activeCollection?.color }} className="sm:w-8 sm:h-8 w-6 h-6 mt-3 drop-shadow" /> -

+

{activeCollection?.name}

@@ -234,13 +235,17 @@ export default function Index() { -
- {links - .filter((e) => e.collectionId === Number(router.query.id)) - .map((e, i) => { - return ; - })} -
+ {links[0] ? ( +
+ {links + .filter((e) => e.collectionId === Number(router.query.id)) + .map((e, i) => { + return ; + })} +
+ ) : ( + + )} ); diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index c12ea0f..b7eac2e 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -167,7 +167,7 @@ export default function Dashboard() { ) : ( -
+

No Pinned Links

diff --git a/pages/forgot.tsx b/pages/forgot.tsx index 2b1f23d..c2c5764 100644 --- a/pages/forgot.tsx +++ b/pages/forgot.tsx @@ -39,20 +39,21 @@ export default function Forgot() { return ( <> -
-
- Linkwarden -
-

Password Reset

-
-
- + Linkwarden +
+

Fogot Password?

+

+ Enter your Email so we can send you a link to recover your account. +

+

+ Make sure to change your password in the profile settings afterwards. +

Email

@@ -63,10 +64,6 @@ export default function Forgot() { 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" /> -

- Make sure to change your password in the profile settings - afterwards. -

+

+ © {new Date().getFullYear()} Linkwarden. All rights reserved.{" "} +

); } diff --git a/pages/links.tsx b/pages/links.tsx index c8cae0e..349d10e 100644 --- a/pages/links.tsx +++ b/pages/links.tsx @@ -1,4 +1,5 @@ import LinkCard from "@/components/LinkCard"; +import NoLinksFound from "@/components/NoLinksFound"; import SortDropdown from "@/components/SortDropdown"; import useLinks from "@/hooks/useLinks"; import MainLayout from "@/layouts/MainLayout"; @@ -52,11 +53,15 @@ export default function Links() { ) : null}
-
- {links.map((e, i) => { - return ; - })} -
+ {links[0] ? ( +
+ {links.map((e, i) => { + return ; + })} +
+ ) : ( + + )} ); diff --git a/pages/login.tsx b/pages/login.tsx index f17798c..6bdfd46 100644 --- a/pages/login.tsx +++ b/pages/login.tsx @@ -46,23 +46,20 @@ export default function Login() { return ( <> -
-
- Linkwarden -
-

Welcome back

-

- Sign in to your account -

-
-
- + Linkwarden +

+ Sign in to your account +

+
+

+ Enter your credentials +

Username @@ -112,6 +109,9 @@ export default function Login() {

+

+ © {new Date().getFullYear()} Linkwarden. All rights reserved.{" "} +

); } diff --git a/pages/register.tsx b/pages/register.tsx index 2edada5..355fe84 100644 --- a/pages/register.tsx +++ b/pages/register.tsx @@ -9,7 +9,7 @@ const emailEnabled = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; type FormData = { name: string; - username: string; + username?: string; email?: string; password: string; passwordConfirmation: string; @@ -20,7 +20,7 @@ export default function Register() { const [form, setForm] = useState({ name: "", - username: "", + username: emailEnabled ? undefined : "", email: emailEnabled ? "" : undefined, password: "", passwordConfirmation: "", @@ -31,7 +31,6 @@ export default function Register() { if (emailEnabled) { return ( form.name !== "" && - form.username !== "" && form.email !== "" && form.password !== "" && form.passwordConfirmation !== "" @@ -54,35 +53,35 @@ export default function Register() { }; if (checkHasEmptyFields()) { - if (form.password === form.passwordConfirmation) { - const { passwordConfirmation, ...request } = form; + if (form.password !== form.passwordConfirmation) + 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", { - body: JSON.stringify(request), - headers: { - "Content-Type": "application/json", - }, - method: "POST", - }); + const response = await fetch("/api/auth/register", { + body: JSON.stringify(request), + headers: { + "Content-Type": "application/json", + }, + method: "POST", + }); - const data = await response.json(); + const data = await response.json(); - toast.dismiss(load); - setSubmitLoader(false); + toast.dismiss(load); + setSubmitLoader(false); - if (response.ok) { - if (form.email) await sendConfirmation(); + if (response.ok) { + if (form.email) await sendConfirmation(); - toast.success("User Created!"); - } else { - toast.error(data.response); - } + toast.success("User Created!"); } else { - toast.error("Passwords do not match."); + toast.error(data.response); } } else { toast.error("Please fill out all the fields."); @@ -91,23 +90,24 @@ export default function Register() { return ( <> -
-
- Linkwarden -
-

Get started

-

- Create a new account -

-
-
- + Linkwarden +

+ {process.env.NEXT_PUBLIC_STRIPE_IS_ACTIVE + ? `Start using our premium services with a ${ + process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS || 14 + }-day free trial!` + : "Create a new account"} +

+
+

+ Enter your details +

Display Name @@ -122,19 +122,21 @@ export default function Register() { />

-
-

- Username -

+ {emailEnabled ? undefined : ( +
+

+ Username +

- 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" - /> -
+ 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" + /> +
+ )} {emailEnabled ? (
@@ -196,6 +198,9 @@ export default function Register() {
+

+ © {new Date().getFullYear()} Linkwarden. All rights reserved.{" "} +

); } diff --git a/pages/subscribe.tsx b/pages/subscribe.tsx index 4d4697b..0dbbba7 100644 --- a/pages/subscribe.tsx +++ b/pages/subscribe.tsx @@ -12,10 +12,6 @@ export default function Subscribe() { const { data, status } = useSession(); const router = useRouter(); - useEffect(() => { - console.log(data?.user); - }, [status]); - async function loginUser() { setSubmitLoader(true); @@ -30,31 +26,26 @@ export default function Subscribe() { return ( <> -
-
- Linkwarden -
-

14 days free trial

-

- Then $5/month afterwards -

-
-
- + Linkwarden +

+ {process.env.NEXT_PUBLIC_TRIAL_PERIOD_DAYS || 14} days free trial, then + ${process.env.NEXT_PUBLIC_PRICING}/month afterwards +

+

You will be redirected to Stripe.

- feel free to reach out to us at{" "} - - hello@linkwarden.app + Feel free to reach out to us at{" "} + + support@linkwarden.app {" "} in case of any issues.

@@ -74,6 +65,9 @@ export default function Subscribe() { Sign Out
+

+ © {new Date().getFullYear()} Linkwarden. All rights reserved.{" "} +

); } diff --git a/prisma/migrations/20230715102805_init/migration.sql b/prisma/migrations/20230719181459_init/migration.sql similarity index 99% rename from prisma/migrations/20230715102805_init/migration.sql rename to prisma/migrations/20230719181459_init/migration.sql index 408f99f..1b7dfcf 100644 --- a/prisma/migrations/20230715102805_init/migration.sql +++ b/prisma/migrations/20230719181459_init/migration.sql @@ -30,7 +30,7 @@ CREATE TABLE "Session" ( CREATE TABLE "User" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, - "username" TEXT NOT NULL, + "username" TEXT, "email" TEXT, "emailVerified" TIMESTAMP(3), "image" TEXT, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a3882aa..43d8b90 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -38,7 +38,7 @@ model User { id Int @id @default(autoincrement()) name String - username String @unique + username String? @unique email String? @unique emailVerified DateTime? diff --git a/store/account.ts b/store/account.ts index 7df7578..638848c 100644 --- a/store/account.ts +++ b/store/account.ts @@ -3,19 +3,19 @@ import { AccountSettings } from "@/types/global"; type ResponseObject = { ok: boolean; - data: object | string; + data: Omit | object | string; }; type AccountStore = { account: AccountSettings; - setAccount: (username: string) => void; + setAccount: (id: number) => void; updateAccount: (user: AccountSettings) => Promise; }; const useAccountStore = create()((set) => ({ account: {} as AccountSettings, - setAccount: async (username) => { - const response = await fetch(`/api/routes/users?username=${username}`); + setAccount: async (id) => { + const response = await fetch(`/api/routes/users?id=${id}`); const data = await response.json(); diff --git a/types/enviornment.d.ts b/types/enviornment.d.ts index 0f32eb7..a093c11 100644 --- a/types/enviornment.d.ts +++ b/types/enviornment.d.ts @@ -17,10 +17,13 @@ declare global { EMAIL_FROM?: string; EMAIL_SERVER?: string; + NEXT_PUBLIC_STRIPE_IS_ACTIVE?: string; STRIPE_SECRET_KEY?: string; PRICE_ID?: 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; } } } diff --git a/yarn.lock b/yarn.lock index 532967f..ffd631c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@auth/core@0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.8.1.tgz#8fbfb7b11ed3e4b346857b033e454efb7c16df26" - integrity sha512-WudBmZudZ/cvykxHV5hIwrYsd7AlETQ535O7w3sSiiumT28+U9GvBb8oSRtfzxpW9rym3lAdfeTJqGA8U4FecQ== +"@auth/core@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@auth/core/-/core-0.9.0.tgz#7a5d66eea0bc059cef072734698547ae2a0c86a6" + integrity sha512-W2WO0WCBg1T3P8+yjQPzurTQhPv6ecBYfJ2oE3uvXPAX5ZLWAMSjKFAIa9oLZy5pwrB+YehJZPnlIxVilhrVcg== dependencies: "@panva/hkdf" "^1.0.4" cookie "0.5.0" @@ -24,12 +24,12 @@ preact "10.11.3" preact-render-to-string "5.2.3" -"@auth/prisma-adapter@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-1.0.0.tgz#9107498921997b6174e0189553f29d0eb49ba2a0" - integrity sha512-+x+s5dgpNmqrcQC2ZRAXZIM6yhkWP/EXjIUgqUyMepLiX1OHi2AXIUAAbXsW4oG9OpYr/rvPIzPBpuGt6sPFwQ== +"@auth/prisma-adapter@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@auth/prisma-adapter/-/prisma-adapter-1.0.1.tgz#eba93843e77018fa7ca0d68726aa959d6b60512c" + integrity sha512-sBp9l/jVr7l9y7rp2Pv6eoP7i8X2CgRNE3jDWJ0B/u+HnKRofXflD1cldPqRSAkJhqH3UxhVtMTEijT9FoofmQ== dependencies: - "@auth/core" "0.8.1" + "@auth/core" "0.9.0" "@aws-crypto/crc32@3.0.0": version "3.0.0" @@ -3391,12 +3391,7 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lilconfig@^2.0.5: - 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: +lilconfig@^2.0.5, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== @@ -3540,12 +3535,7 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.4: - 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: +nanoid@^3.3.4, nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== @@ -3981,10 +3971,10 @@ postcss@8.4.14: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.23, postcss@^8.4.24: - version "8.4.24" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" - integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== +postcss@^8.4.23, postcss@^8.4.26: + version "8.4.26" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94" + integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw== dependencies: nanoid "^3.3.6" 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" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.1: - 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: +resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.1, resolve@^1.22.2: version "1.22.2" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== @@ -4564,10 +4545,10 @@ synckit@^0.8.4: "@pkgr/utils" "^2.3.1" tslib "^2.5.0" -tailwindcss@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3" - integrity sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w== +tailwindcss@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" + integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" @@ -4589,7 +4570,6 @@ tailwindcss@^3.3.2: postcss-load-config "^4.0.1" postcss-nested "^6.0.1" postcss-selector-parser "^6.0.11" - postcss-value-parser "^4.2.0" resolve "^1.22.2" sucrase "^3.32.0"