many improvements

This commit is contained in:
Daniel 2023-07-19 16:39:59 -04:00
parent 8f6dfdd868
commit 7912815c9e
14 changed files with 176 additions and 109 deletions

View File

@ -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 (
@ -57,9 +57,7 @@ export default function ChangePassword({
user.name !== account.name user.name !== account.name
) { ) {
update({ update({
username: user.username, id: data?.user.id,
email: user.email,
name: user.name,
}); });
signOut(); signOut();

View File

@ -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);
@ -66,9 +66,7 @@ export default function PrivacySettings({
user.name !== account.name user.name !== account.name
) { ) {
update({ update({
username: user.username, id: data?.user.id,
email: user.email,
name: user.name,
}); });
signOut(); signOut();

View File

@ -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);
@ -84,9 +84,7 @@ export default function ProfileSettings({
user.name !== account.name user.name !== account.name
) { ) {
update({ update({
username: user.username, id: data?.user.id,
email: user.email,
name: user.name,
}); });
signOut(); signOut();
@ -158,7 +156,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"
/> />

View File

@ -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(() => {

View File

@ -10,12 +10,6 @@ export default async function paymentCheckout(
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"],
@ -23,31 +17,6 @@ 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 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 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,

View File

@ -126,11 +126,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;
}, },

View File

@ -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,30 +31,9 @@ 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);
// 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,
},
});
const checkUsername = RegExp("^[a-z0-9_-]{3,31}$"); const checkUsername = RegExp("^[a-z0-9_-]{3,31}$");
if (!checkUsername.test(body.username)) if (!emailEnabled && !checkUsername.test(body.username || ""))
return res.status(400).json({ return res.status(400).json({
response: response:
"Username has to be between 3-30 characters, no spaces and special characters are allowed.", "Username has to be between 3-30 characters, no spaces and special characters are allowed.",
@ -63,18 +42,11 @@ export default async function Index(
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(),
}, },
}); });
@ -86,8 +58,10 @@ 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,
}, },
}); });

View File

@ -32,7 +32,7 @@ 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 });
} }

108
pages/choose-username.tsx Normal file
View File

@ -0,0 +1,108 @@
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,
});
signOut();
} else toast.error(response.data as string);
toast.dismiss(redirectionToast);
setSubmitLoader(false);
}
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">
<div className="flex flex-col gap-2 justify-between items-center mb-5">
<Image
src="/linkwarden.png"
width={1694}
height={483}
alt="Linkwarden"
className="h-12 w-fit mx-auto"
/>
<div className="text-center">
<p className="text-3xl text-sky-500">One Last Step...</p>
<p className="font-semibold text-sky-400">
Please choose a username to start using your account.
</p>
</div>
</div>
<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>
<p className="text-gray-500 text-center">
Note that you will have to log back in to complete the process.
</p>
<div>
<p className="text-md text-gray-500 mt-1">
Feel free to reach out to us at{" "}
<a className="font-semibold" href="mailto:hello@linkwarden.app">
hello@linkwarden.app
</a>{" "}
in case of any issues.
</p>
</div>
<SubmitButton
onClick={submitUsername}
label="Choose Username"
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>
</>
);
}

View File

@ -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 !== ""
@ -122,19 +121,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>

View File

@ -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);
@ -52,7 +48,7 @@ export default function Subscribe() {
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:hello@linkwarden.app">
hello@linkwarden.app hello@linkwarden.app
</a>{" "} </a>{" "}

View File

@ -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,

View File

@ -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?

View File

@ -3,7 +3,7 @@ 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 = {