mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-08 23:59:38 -06:00
Feature/add signup (#5)
* update prisma model to support user * add signup page * add email verification * remove seed user * update README and Dockerfile to support signup and the removal of seed data
This commit is contained in:
@@ -2,8 +2,12 @@
|
||||
SECRET=RANDOM_STRING
|
||||
DATABASE_URL='postgresql://user@localhost:5432/snoopforms?schema=public'
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
ADMIN_EMAIL=user@example.com
|
||||
ADMIN_PASSWORD='admin123'
|
||||
|
||||
MAIL_FROM=noreply@example.com
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=smtpUser
|
||||
SMTP_PASSWORD=smtpPassword
|
||||
|
||||
# For Docker Setup use this Database URL:
|
||||
# DATABASE_URL='postgresql://postgres:postgres@postgres:5432/snoopforms?schema=public'
|
||||
|
||||
@@ -12,7 +12,6 @@ WORKDIR /app
|
||||
COPY . .
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
RUN yarn prisma generate
|
||||
RUN yarn tsc prisma/seed.ts
|
||||
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
|
||||
@@ -69,7 +69,7 @@ yarn install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
4. Use the code editor of your choice to edit the .env file.
|
||||
4. Use the code editor of your choice to edit the .env file. You need to change all fields according to your setup. The SMTP-credentials are essential for verification emails to work during user signup.
|
||||
|
||||
5. Make sure your PostgreSQL Database Server is running. Then let prisma set up the database for you:
|
||||
|
||||
@@ -101,7 +101,7 @@ git clone https://github.com/snoopForms/snoopforms.git && cd snoopforms
|
||||
|
||||
```
|
||||
|
||||
Create a `.env` file based on `.env.example` and change it according to your setup.
|
||||
Create a `.env` file based on `.env.example` and change all fields according to your setup. The SMTP-credentials are essential for verification emails to work during user signup.
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -9,12 +9,7 @@ services:
|
||||
|
||||
snoopforms:
|
||||
build: .
|
||||
command:
|
||||
[
|
||||
sh,
|
||||
-c,
|
||||
"yarn prisma migrate deploy && yarn prisma db seed && yarn start",
|
||||
]
|
||||
command: [sh, -c, "yarn prisma migrate deploy && yarn start"]
|
||||
depends_on:
|
||||
- postgres
|
||||
ports:
|
||||
|
||||
48
lib/email.ts
Normal file
48
lib/email.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
interface sendEmailData {
|
||||
to: string;
|
||||
subject: string;
|
||||
text?: string;
|
||||
html: string;
|
||||
}
|
||||
|
||||
export const sendEmail = async (emailData: sendEmailData) => {
|
||||
let transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT,
|
||||
secure: process.env.SMTP_SECURE_ENABLED || false, // true for 465, false for other ports
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
},
|
||||
});
|
||||
const emailDefaults = {
|
||||
from: process.env.MAIL_FROM || "noreply@snoopforms.com",
|
||||
};
|
||||
await transporter.sendMail({ ...emailDefaults, ...emailData });
|
||||
};
|
||||
|
||||
export const sendVerificationEmail = async (user) => {
|
||||
const token = jwt.sign({ id: user.id }, process.env.SECRET + user.email, {
|
||||
expiresIn: "1d",
|
||||
});
|
||||
const verifyLink = `${
|
||||
process.env.NEXTAUTH_URL
|
||||
}/auth/verify?token=${encodeURIComponent(token)}`;
|
||||
const verificationRequestLink = `${
|
||||
process.env.NEXTAUTH_URL
|
||||
}/auth/verification-requested?email=${encodeURIComponent(user.email)}`;
|
||||
await sendEmail({
|
||||
to: user.email,
|
||||
subject: "Welcome to snoopForms",
|
||||
html: `Welcome to snoopForms!<br/><br/>To verify your email address and start using snoopForms please click this link:<br/>
|
||||
<a href="${verifyLink}">${verifyLink}</a><br/>
|
||||
<br/>
|
||||
The link is valid for one day. If it has expired please request a new token here:<br/>
|
||||
<a href="${verificationRequestLink}">${verificationRequestLink}</a><br/>
|
||||
<br/>
|
||||
Your snoopForms Team`,
|
||||
});
|
||||
};
|
||||
43
lib/users.ts
Normal file
43
lib/users.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { hashPassword } from "./auth";
|
||||
|
||||
export const createUser = async (firstname, lastname, email, password) => {
|
||||
const hashedPassword = await hashPassword(password);
|
||||
try {
|
||||
const res = await fetch(`/api/public/users`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
firstname,
|
||||
lastname,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
}),
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
const json = await res.json();
|
||||
throw Error(json.error);
|
||||
}
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
throw Error(`${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const resendVerificationEmail = async (email) => {
|
||||
try {
|
||||
const res = await fetch(`/api/public/users/verfication-email`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
}),
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
const json = await res.json();
|
||||
throw Error(json.error);
|
||||
}
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
throw Error(`${error.message}`);
|
||||
}
|
||||
};
|
||||
13
package.json
13
package.json
@@ -6,8 +6,7 @@
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"seed:prod": "node prisma/seed.js"
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@editorjs/editorjs": "^2.24.3",
|
||||
@@ -15,7 +14,7 @@
|
||||
"@editorjs/paragraph": "^2.8.0",
|
||||
"@headlessui/react": "^1.6.1",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@prisma/client": "^3.15.1",
|
||||
"@prisma/client": "^4.0.0",
|
||||
"@snoopforms/react": "^0.0.3",
|
||||
"babel-plugin-superjson-next": "^0.4.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -23,10 +22,11 @@
|
||||
"editorjs-drag-drop": "^1.1.2",
|
||||
"editorjs-undo": "^2.0.3",
|
||||
"json2csv": "^5.0.7",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"next": "12.1.6",
|
||||
"next-auth": "^4.3.4",
|
||||
"nextjs-cors": "^2.1.1",
|
||||
"nodemailer": "^6.7.5",
|
||||
"nodemailer": "^6.7.7",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-icons": "^4.4.0",
|
||||
@@ -45,12 +45,9 @@
|
||||
"eslint": "8.15.0",
|
||||
"eslint-config-next": "12.1.6",
|
||||
"postcss": "^8.4.13",
|
||||
"prisma": "^3.15.1",
|
||||
"prisma": "^4.0.0",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed.ts"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import jwt from "jsonwebtoken";
|
||||
import NextAuth from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "../../../lib/prisma";
|
||||
@@ -8,6 +9,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
return await NextAuth(req, res, {
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
id: "credentials",
|
||||
// The name to display on the sign in form (e.g. "Sign in with...")
|
||||
name: "Credentials",
|
||||
// The credentials is used to generate a suitable form on the sign in page.
|
||||
@@ -65,7 +67,86 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
};
|
||||
},
|
||||
}),
|
||||
CredentialsProvider({
|
||||
id: "token",
|
||||
// The name to display on the sign in form (e.g. "Sign in with...")
|
||||
name: "Token",
|
||||
// The credentials is used to generate a suitable form on the sign in page.
|
||||
// You can specify whatever fields you are expecting to be submitted.
|
||||
// e.g. domain, username, password, 2FA token, etc.
|
||||
// You can pass any HTML attribute to the <input> tag through the object.
|
||||
credentials: {
|
||||
token: {
|
||||
label: "Verification Token",
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
async authorize(credentials, _req) {
|
||||
let user;
|
||||
try {
|
||||
const { id } = await jwt.decode(credentials?.token);
|
||||
user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw Error("Internal server error. Please try again later");
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
if (user.emailVerified) {
|
||||
throw new Error("Email already verified");
|
||||
}
|
||||
|
||||
const isValid = await new Promise((resolve) => {
|
||||
jwt.verify(
|
||||
credentials?.token,
|
||||
process.env.SECRET + user.email,
|
||||
(err) => {
|
||||
if (err) resolve(false);
|
||||
if (!err) resolve(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
throw new Error("Token is not valid or expired");
|
||||
}
|
||||
|
||||
user = await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: { emailVerified: new Date().toISOString() },
|
||||
});
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
firstname: user.firstname,
|
||||
lastname: user.firstname,
|
||||
emailVerified: user.emailVerified,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async signIn({ user }) {
|
||||
if (user.emailVerified) {
|
||||
return true;
|
||||
} else {
|
||||
// Return false to display a default error message or you can return a URL to redirect to
|
||||
return `/auth/verification-requested?email=${encodeURIComponent(
|
||||
user.email
|
||||
)}`;
|
||||
}
|
||||
},
|
||||
},
|
||||
secret: process.env.SECRET,
|
||||
pages: {
|
||||
signIn: "/auth/signin",
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function handle(
|
||||
},
|
||||
include: {
|
||||
owner: {
|
||||
select: { name: true },
|
||||
select: { firstname: true },
|
||||
},
|
||||
_count: {
|
||||
select: { submissionSessions: true },
|
||||
|
||||
45
pages/api/public/users/index.tsx
Normal file
45
pages/api/public/users/index.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { prisma } from "../../../../lib/prisma";
|
||||
import { sendVerificationEmail } from "../../../../lib/email";
|
||||
|
||||
export default async function handle(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
// POST /api/public/users
|
||||
// Creates a new user
|
||||
// Required fields in body: email, password (hashed)
|
||||
// Optional fields in body: firstname, lastname
|
||||
if (req.method === "POST") {
|
||||
const user = req.body;
|
||||
// create user in database
|
||||
try {
|
||||
const userData = await prisma.user.create({
|
||||
data: {
|
||||
...user,
|
||||
},
|
||||
});
|
||||
await sendVerificationEmail(userData);
|
||||
res.json(userData);
|
||||
} catch (e) {
|
||||
if (e.code === "P2002") {
|
||||
return res.status(409).json({
|
||||
error: "user with this email address already exists",
|
||||
errorCode: e.code,
|
||||
});
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
error: e.message,
|
||||
errorCode: e.code,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(
|
||||
`The HTTP ${req.method} method is not supported by this route.`
|
||||
);
|
||||
}
|
||||
}
|
||||
44
pages/api/public/users/verfication-email.tsx
Normal file
44
pages/api/public/users/verfication-email.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { prisma } from "../../../../lib/prisma";
|
||||
import { sendVerificationEmail } from "../../../../lib/email";
|
||||
|
||||
export default async function handle(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
// POST /api/public/users
|
||||
// Sends a new verification email to a user with a specific email address
|
||||
// Required fields in body: email
|
||||
if (req.method === "POST") {
|
||||
const { email } = req.body;
|
||||
// create user in database
|
||||
try {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
error: "No user with this email address found",
|
||||
});
|
||||
}
|
||||
if (user.emailVerified) {
|
||||
return res.status(400).json({
|
||||
error: "Email address has already been verified",
|
||||
});
|
||||
}
|
||||
await sendVerificationEmail(user);
|
||||
res.json(user);
|
||||
} catch (e) {
|
||||
return res.status(500).json({
|
||||
error: e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown HTTP Method
|
||||
else {
|
||||
throw new Error(
|
||||
`The HTTP ${req.method} method is not supported by this route.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { XCircleIcon } from "@heroicons/react/solid";
|
||||
import { GetServerSideProps } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
interface props {
|
||||
csrfToken: string;
|
||||
@@ -101,10 +102,15 @@ export default function SignIn({ csrfToken }: props) {
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<div className="text-center">
|
||||
<a href="" className="text-xs text-red hover:text-red-600">
|
||||
Create an account
|
||||
</a>
|
||||
<div className="mt-3 text-center">
|
||||
<Link href="/auth/signup">
|
||||
<a
|
||||
href=""
|
||||
className="text-xs text-red hover:text-red-600"
|
||||
>
|
||||
Create an account
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
183
pages/auth/signup.tsx
Normal file
183
pages/auth/signup.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { getCsrfToken } from "next-auth/react";
|
||||
import { XCircleIcon } from "@heroicons/react/solid";
|
||||
import { GetServerSideProps } from "next";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { createUser } from "../../lib/users";
|
||||
import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface props {
|
||||
csrfToken: string;
|
||||
}
|
||||
|
||||
export default function SignIn({ csrfToken }: props) {
|
||||
const router = useRouter();
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await createUser(
|
||||
e.target.elements.firstname.value,
|
||||
e.target.elements.lastname.value,
|
||||
e.target.elements.email.value,
|
||||
e.target.elements.password.value
|
||||
);
|
||||
router.push(
|
||||
`/auth/verification-requested?email=${encodeURIComponent(
|
||||
e.target.elements.email.value
|
||||
)}`
|
||||
);
|
||||
} catch (e) {
|
||||
setError(e.message);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex min-h-screen bg-ui-gray-light">
|
||||
<div className="flex flex-col justify-center flex-1 px-4 py-12 mx-auto sm:px-6 lg:flex-none lg:px-20 xl:px-24">
|
||||
{error && (
|
||||
<div className="absolute p-4 rounded-md top-10 bg-red-50">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon
|
||||
className="w-5 h-5 text-red-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">
|
||||
An error occurred when logging you in
|
||||
</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full max-w-sm p-8 mx-auto bg-white rounded-xl shadow-cont lg:w-96">
|
||||
<div>
|
||||
<Image
|
||||
src="/img/snoopforms-logo.svg"
|
||||
alt="snoopForms logo"
|
||||
width={500}
|
||||
height={89}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
<p className="text-sm text-center text-gray-600">
|
||||
Create your own forms and collect submissions by creating a
|
||||
snoopForms account.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<input
|
||||
name="csrfToken"
|
||||
type="hidden"
|
||||
defaultValue={csrfToken}
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="firstname"
|
||||
className="block text-sm font-medium text-ui-gray-dark"
|
||||
>
|
||||
First name
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="firstname"
|
||||
name="firstname"
|
||||
type="text"
|
||||
autoComplete="given-name"
|
||||
required
|
||||
className="block w-full px-3 py-2 border rounded-md shadow-sm appearance-none placeholder-ui-gray-medium border-ui-gray-medium focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="lastname"
|
||||
className="block text-sm font-medium text-ui-gray-dark"
|
||||
>
|
||||
Last name
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="lastname"
|
||||
name="lastname"
|
||||
type="text"
|
||||
autoComplete="family-name"
|
||||
required
|
||||
className="block w-full px-3 py-2 border rounded-md shadow-sm appearance-none placeholder-ui-gray-medium border-ui-gray-medium focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-ui-gray-dark"
|
||||
>
|
||||
Email address
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="block w-full px-3 py-2 border rounded-md shadow-sm appearance-none placeholder-ui-gray-medium border-ui-gray-medium focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="email"
|
||||
className="block text-sm font-medium text-ui-gray-dark"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
className="block w-full px-3 py-2 border rounded-md shadow-sm appearance-none placeholder-ui-gray-medium border-ui-gray-medium focus:outline-none focus:ring-red-500 focus:border-red-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex justify-center w-full px-4 py-2 text-sm font-medium text-white border border-transparent rounded-md shadow-sm bg-red hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
Sign up
|
||||
</button>
|
||||
<div className="mt-3 text-xs text-center text-gray-600">
|
||||
Already have an account?{" "}
|
||||
<Link href="/auth/signin">
|
||||
<a className="text-red hover:text-red-600">Log in.</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const csrfToken = await getCsrfToken(context);
|
||||
return {
|
||||
props: { csrfToken },
|
||||
};
|
||||
};
|
||||
68
pages/auth/verification-requested.tsx
Normal file
68
pages/auth/verification-requested.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { toast } from "react-toastify";
|
||||
import { resendVerificationEmail } from "../../lib/users";
|
||||
|
||||
interface props {
|
||||
csrfToken: string;
|
||||
}
|
||||
|
||||
export default function SignIn({}: props) {
|
||||
const router = useRouter();
|
||||
const email = router.query.email;
|
||||
|
||||
const requestVerificationEmail = async () => {
|
||||
try {
|
||||
await resendVerificationEmail(email);
|
||||
toast("Verification email successfully sent. Please check your inbox.");
|
||||
} catch (e) {
|
||||
toast.error(`Error: ${e.message}`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex min-h-screen bg-ui-gray-light">
|
||||
<div className="flex flex-col justify-center flex-1 px-4 py-12 mx-auto sm:px-6 lg:flex-none lg:px-20 xl:px-24">
|
||||
<div className="w-full max-w-sm p-8 mx-auto bg-white rounded-xl shadow-cont lg:w-96">
|
||||
<div>
|
||||
<Image
|
||||
src="/img/snoopforms-logo.svg"
|
||||
alt="snoopForms logo"
|
||||
width={500}
|
||||
height={89}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-8">
|
||||
{email ? (
|
||||
<>
|
||||
<h1 className="mb-4 font-bold text-center leading-2">
|
||||
Please verify your email address
|
||||
</h1>
|
||||
<p className="text-center">
|
||||
We have sent you an email to the address{" "}
|
||||
<span className="italic">{router.query.email}</span>. Please
|
||||
click the link in the email to activate your account.
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<p className="text-xs text-center">
|
||||
You didn't receive an email or your link expired?
|
||||
<br />
|
||||
Click the button below to request a new email.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => requestVerificationEmail()}
|
||||
className="flex justify-center w-full px-4 py-2 mt-5 text-sm font-medium text-gray-600 bg-white border border-gray-400 rounded-md shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
Request a new verification mail
|
||||
</button>{" "}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-center">No E-Mail Address provided</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
pages/auth/verify.tsx
Normal file
27
pages/auth/verify.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Verify() {
|
||||
const router = useRouter();
|
||||
const token = router.query.token?.toString();
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
signIn("token", {
|
||||
token,
|
||||
callbackUrl: `/forms`,
|
||||
});
|
||||
}
|
||||
}, [token]);
|
||||
return (
|
||||
<div className="flex min-h-screen bg-ui-gray-light">
|
||||
<div className="flex flex-col justify-center flex-1 px-4 py-12 mx-auto sm:px-6 lg:flex-none lg:px-20 xl:px-24">
|
||||
<div className="w-full max-w-sm p-8 mx-auto bg-white rounded-xl shadow-cont lg:w-96">
|
||||
<p className="text-center">
|
||||
{!token ? "No Token provided" : "Verifying..."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
prisma/migrations/20220713132158_add_signup/migration.sql
Normal file
24
prisma/migrations/20220713132158_add_signup/migration.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `name` on the `users` table. All the data in the column will be lost.
|
||||
- You are about to drop the `verification_requests` table. If the table is not empty, all the data it contains will be lost.
|
||||
- Made the column `email` on table `users` required. This step will fail if there are existing NULL values in that column.
|
||||
- Made the column `password` on table `users` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "Form" DROP CONSTRAINT "Form_ownerId_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" DROP COLUMN "name",
|
||||
ADD COLUMN "firstname" TEXT,
|
||||
ADD COLUMN "lastname" TEXT,
|
||||
ALTER COLUMN "email" SET NOT NULL,
|
||||
ALTER COLUMN "password" SET NOT NULL;
|
||||
|
||||
-- DropTable
|
||||
DROP TABLE "verification_requests";
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Form" ADD CONSTRAINT "Form_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -20,7 +20,7 @@ model Form {
|
||||
id String @id
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
ownerId Int
|
||||
formType FormType @default(NOCODE)
|
||||
name String @default("")
|
||||
@@ -73,24 +73,14 @@ model SessionEvent {
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
name String?
|
||||
email String? @unique
|
||||
firstname String?
|
||||
lastname String?
|
||||
email String @unique
|
||||
emailVerified DateTime? @map(name: "email_verified")
|
||||
password String?
|
||||
password String
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
forms Form[]
|
||||
|
||||
@@map(name: "users")
|
||||
}
|
||||
|
||||
model VerificationRequest {
|
||||
id Int @id @default(autoincrement())
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @default(now()) @map(name: "updated_at")
|
||||
|
||||
@@map(name: "verification_requests")
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { PrismaClient, Prisma } from "@prisma/client";
|
||||
import { hash } from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log(`Start seeding ...`);
|
||||
if (process.env.ADMIN_PASSWORD) {
|
||||
const passwordHash = await hash(process.env.ADMIN_PASSWORD, 12);
|
||||
|
||||
if (typeof passwordHash === "string") {
|
||||
const users: Prisma.UserCreateInput[] = [
|
||||
{
|
||||
name: "Admin",
|
||||
email: process.env.ADMIN_EMAIL,
|
||||
password: passwordHash,
|
||||
},
|
||||
];
|
||||
|
||||
for (const user of users) {
|
||||
const userRes = await prisma.user.upsert({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
update: {},
|
||||
create: user,
|
||||
});
|
||||
console.log(`Created user with id: ${userRes.id}`);
|
||||
}
|
||||
console.log(`Seeding finished.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
129
yarn.lock
129
yarn.lock
@@ -230,22 +230,22 @@
|
||||
resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.0.2.tgz"
|
||||
integrity sha512-MSAs9t3Go7GUkMhpKC44T58DJ5KGk2vBo+h1cqQeqlMfdGkxaVB78ZWpv9gYi/g2fa4sopag9gJsNvS8XGgWJA==
|
||||
|
||||
"@prisma/client@^3.15.1":
|
||||
version "3.15.1"
|
||||
resolved "https://registry.npmjs.org/@prisma/client/-/client-3.15.1.tgz"
|
||||
integrity sha512-Lsk7oupvO9g99mrIs07iE6BIMouHs46Yq/YY8itTsUQNKfecsPuZvVYvcKci0pqRQ0neOpvIvoA/ouZmIMBCrQ==
|
||||
"@prisma/client@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.0.0.tgz#ed2f46930a1da0d8ae88d7965485973576b04270"
|
||||
integrity sha512-g1h2OGoRo7anBVQ9Cw3gsbjwPtvf7i0pkGxKeZICtwkvE5CZXW+xZF4FZdmrViYkKaAShbISL0teNpu9ecpf4g==
|
||||
dependencies:
|
||||
"@prisma/engines-version" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e"
|
||||
"@prisma/engines-version" "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
|
||||
|
||||
"@prisma/engines-version@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e":
|
||||
version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e"
|
||||
resolved "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz"
|
||||
integrity sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w==
|
||||
"@prisma/engines-version@3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11":
|
||||
version "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11.tgz#4b5efe5eee2feef12910e4627a572cd96ed83236"
|
||||
integrity sha512-PiZhdD624SrYEjyLboI0X7OugNbxUzDJx9v/6ldTKuqNDVUCmRH/Z00XwDi/dgM4FlqOSO+YiUsSiSKjxxG8cw==
|
||||
|
||||
"@prisma/engines@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e":
|
||||
version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e"
|
||||
resolved "https://registry.npmjs.org/@prisma/engines/-/engines-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz"
|
||||
integrity sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg==
|
||||
"@prisma/engines@3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11":
|
||||
version "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11.tgz#82f0018153cffa05d61422f9c0c7b0479b180f75"
|
||||
integrity sha512-u/rG4lDHALolWBLr3yebZ+N2qImp3SDMcu7bHNJuRDaYvYEXy/MqfNRNEgd9GoPsXL3gofYf0VzJf2AmCG3YVw==
|
||||
|
||||
"@rushstack/eslint-patch@^1.1.3":
|
||||
version "1.1.3"
|
||||
@@ -574,6 +574,11 @@ browserslist@^4.20.3:
|
||||
node-releases "^2.0.5"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz"
|
||||
@@ -800,6 +805,13 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
editorjs-drag-drop@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/editorjs-drag-drop/-/editorjs-drag-drop-1.1.2.tgz"
|
||||
@@ -1508,6 +1520,22 @@ jsonparse@^1.3.1:
|
||||
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz"
|
||||
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
|
||||
|
||||
jsonwebtoken@^8.5.1:
|
||||
version "8.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
|
||||
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz"
|
||||
@@ -1516,6 +1544,23 @@ jsonparse@^1.3.1:
|
||||
array-includes "^3.1.4"
|
||||
object.assign "^4.1.2"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
language-subtag-registry@~0.3.2:
|
||||
version "0.3.21"
|
||||
resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz"
|
||||
@@ -1559,16 +1604,46 @@ lodash.get@^4.4.2:
|
||||
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz"
|
||||
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
@@ -1693,10 +1768,10 @@ node-releases@^2.0.5:
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz"
|
||||
integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==
|
||||
|
||||
nodemailer@^6.7.5:
|
||||
version "6.7.5"
|
||||
resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.5.tgz"
|
||||
integrity sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==
|
||||
nodemailer@^6.7.7:
|
||||
version "6.7.7"
|
||||
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.7.tgz#e522fbd7507b81c51446d3f79c4603bf00083ddd"
|
||||
integrity sha512-pOLC/s+2I1EXuSqO5Wa34i3kXZG3gugDssH+ZNCevHad65tc8vQlCQpOLaUjopvkRQKm2Cki2aME7fEOPRy3bA==
|
||||
|
||||
normalize-path@^3.0.0, normalize-path@~3.0.0:
|
||||
version "3.0.0"
|
||||
@@ -1967,12 +2042,12 @@ pretty-format@^3.8.0:
|
||||
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
|
||||
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
|
||||
|
||||
prisma@^3.15.1:
|
||||
version "3.15.1"
|
||||
resolved "https://registry.npmjs.org/prisma/-/prisma-3.15.1.tgz"
|
||||
integrity sha512-MLO3JUGJpe5+EVisA/i47+zlyF8Ug0ivvGYG4B9oSXQcPiUHB1ccmnpxqR7o0Up5SQgmxkBiEU//HgR6UuIKOw==
|
||||
prisma@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.0.0.tgz#4ddb8fcd4f64d33aff8c198a6986dcce66dc8152"
|
||||
integrity sha512-Dtsar03XpCBkcEb2ooGWO/WcgblDTLzGhPcustbehwlFXuTMliMDRzXsfygsgYwQoZnAUKRd1rhpvBNEUziOVw==
|
||||
dependencies:
|
||||
"@prisma/engines" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e"
|
||||
"@prisma/engines" "3.16.0-49.da41d2bb3406da22087b849f0e911199ba4fbf11"
|
||||
|
||||
prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
@@ -2111,6 +2186,11 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
safe-buffer@^5.0.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
scheduler@^0.20.2:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz"
|
||||
@@ -2119,6 +2199,11 @@ scheduler@^0.20.2:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
semver@^5.6.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
semver@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user