chore: restore dangling changes from rebase

This commit is contained in:
Mythie
2023-11-06 14:47:46 +11:00
parent ee3614f10e
commit b0e3abffd6
37 changed files with 79 additions and 792 deletions

View File

@ -1,8 +0,0 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@ -1,9 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}

View File

@ -14,18 +14,18 @@
"customizations": {
"vscode": {
"extensions": [
"GitHub.vscode-pull-request-github",
"GitHub.copilot-labs",
"GitHub.copilot-chat",
"GitHub.copilot",
"aaron-bond.better-comments",
"mikestead.dotenv",
"VisualStudioExptTeam.vscodeintellicode",
"Prisma.prisma",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mikestead.dotenv",
"unifiedjs.vscode-mdx",
"esbenp.prettier-vscode"
"GitHub.copilot-chat",
"GitHub.copilot-labs",
"GitHub.copilot",
"GitHub.vscode-pull-request-github",
"Prisma.prisma",
"VisualStudioExptTeam.vscodeintellicode",
]
}
}

View File

@ -1,29 +0,0 @@
DATABASE_URL="postgres://documenso:password@127.0.0.1:54320/documenso"
NEXT_PUBLIC_WEBAPP_URL=""
# AUTH
NEXTAUTH_SECRET='lorem ipsum sit dolor random string for encryption this could literally be anything'
NEXTAUTH_URL=""
# SIGNING
CERT_FILE_PATH=""
CERT_PASSPHRASE=""
CERT_FILE_ENCODING=""
# EMAIL
SMTP_MAIL_HOST='127.0.0.1'
SMTP_MAIL_PORT='2500'
SMTP_MAIL_USER='documenso'
SMTP_MAIL_PASSWORD='documenso'
MAIL_FROM='documenso@gitpod.io'
# STRIPE
STRIPE_API_KEY=""
STRIPE_WEBHOOK_SECRET=""
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID=""
NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID=""
#FEATURE FLAGS
# Allow users to register via the /signup page. Otherwise they will be redirect to the home page.
NEXT_PUBLIC_ALLOW_SIGNUP=true
NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS=false

View File

@ -52,11 +52,3 @@ You can build the project with:
```bash
npm run build
```
### Making a Pull Request
When making a pull request, be sure to add a changeset when something has changed with Documenso.
```shell
npm run changeset
```

View File

@ -69,6 +69,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
</PlausibleProvider>
</ThemeProvider>
</FeatureFlagProvider>
<Toaster />
</body>
</html>
);

View File

@ -40,7 +40,7 @@ export const Callout = ({ starCount }: CalloutProps) => {
className="rounded-full bg-transparent backdrop-blur-sm"
onClick={onSignUpClick}
>
Get the Community Plan
Get the Early Adopters Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
$30/mo. forever!
</span>

View File

@ -24,7 +24,13 @@ export const Header = ({ className, ...props }: HeaderProps) => {
<header className={cn('flex items-center justify-between', className)} {...props}>
<div className="flex items-center space-x-4">
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
<Image src="/logo.png" alt="Documenso Logo" width={170} height={25} />
<Image
src="/logo.png"
alt="Documenso Logo"
className="dark:invert"
width={170}
height={25}
/>
</Link>
{isSinglePlayerModeMarketingEnabled && (

View File

@ -114,7 +114,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
className="rounded-full bg-transparent backdrop-blur-sm"
onClick={onSignUpClick}
>
Get the Community Plan
Get the Early Adopters Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
$30/mo. forever!
</span>

View File

@ -109,7 +109,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
data-plan="community"
className="border-primary bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border-2 px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380]"
>
<p className="text-foreground text-4xl font-medium">Community</p>
<p className="text-foreground text-4xl font-medium">Early Adopters</p>
<div className="text-primary mt-2.5 text-xl font-medium">
<AnimatePresence mode="wait">
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
@ -168,7 +168,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
</Link>
<div className="mt-8 flex w-full flex-col divide-y">
<p className="text-foreground py-4 font-medium">Everything in Community, plus:</p>
<p className="text-foreground py-4 font-medium">Everything in Early Adopters, plus:</p>
<p className="text-foreground py-4">Custom Subdomain</p>
<p className="text-foreground py-4">Compliance Check</p>
<p className="text-foreground py-4">Guaranteed Uptime</p>

View File

@ -189,7 +189,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5"
onSubmit={handleSubmit(onFormSubmit)}
>
<h3 className="text-2xl font-semibold">Sign up for the community plan</h3>
<h3 className="text-2xl font-semibold">Sign up for the early adopters plan</h3>
<p className="text-muted-foreground mt-2 text-xs">
with Timur Ercan & Lucas Smith from Documenso
</p>

View File

@ -1,115 +0,0 @@
import { useState } from "react";
import Link from "next/link";
import { Button } from "@documenso/ui";
import Logo from "./logo";
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { FormProvider, useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
interface ForgotPasswordForm {
email: string;
}
export default function ForgotPassword() {
const { register, formState, resetField, handleSubmit } = useForm<ForgotPasswordForm>();
const [resetSuccessful, setResetSuccessful] = useState(false);
const onSubmit = async (values: ForgotPasswordForm) => {
const response = await toast.promise(
fetch(`/api/auth/forgot-password`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}),
{
loading: "Sending...",
success: "Reset link sent.",
error: "Could not send reset link :/",
}
);
if (!response.ok) {
toast.dismiss();
if (response.status == 404) {
toast.error("Email address not found.");
}
if (response.status == 400) {
toast.error("Password reset requested.");
}
if (response.status == 500) {
toast.error("Something went wrong.");
}
return;
}
if (response.ok) {
setResetSuccessful(true);
}
resetField("email");
};
return (
<>
<div className="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<div>
<Logo className="mx-auto h-20 w-auto"></Logo>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
{resetSuccessful ? "Reset Password" : "Forgot Password?"}
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
{resetSuccessful
? "Please check your email for reset instructions."
: "No worries, we'll send you reset instructions."}
</p>
</div>
{!resetSuccessful && (
<form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="-space-y-px rounded-md shadow-sm">
<div>
<label htmlFor="email-address" className="sr-only">
Email
</label>
<input
{...register("email")}
id="email-address"
name="email"
type="email"
autoComplete="email"
required
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
placeholder="Email"
/>
</div>
</div>
<div>
<Button
type="submit"
disabled={formState.isSubmitting}
className="group relative flex w-full">
Reset password
</Button>
</div>
</form>
)}
<div>
<Link href="/login">
<div className="relative mt-10 flex items-center justify-center gap-2 text-sm text-gray-500 hover:cursor-pointer hover:text-gray-900">
<ArrowLeftIcon className="h-4 w-4" />
Back to log in
</div>
</Link>
</div>
</div>
</div>
</>
);
}

View File

@ -1,143 +0,0 @@
import { useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { Button } from "@documenso/ui";
import Logo from "./logo";
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { toast } from "react-hot-toast";
import * as z from "zod";
const ZResetPasswordFormSchema = z
.object({
password: z.string().min(8, { message: "Password must be at least 8 characters" }),
confirmPassword: z.string().min(8, { message: "Password must be at least 8 characters" }),
})
.refine((data) => data.password === data.confirmPassword, {
path: ["confirmPassword"],
message: "Password don't match",
});
type TResetPasswordFormSchema = z.infer<typeof ZResetPasswordFormSchema>;
export default function ResetPassword() {
const router = useRouter();
const { token } = router.query;
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
} = useForm<TResetPasswordFormSchema>({
resolver: zodResolver(ZResetPasswordFormSchema),
});
const [resetSuccessful, setResetSuccessful] = useState(false);
const onSubmit = async ({ password }: TResetPasswordFormSchema) => {
const response = await toast.promise(
fetch(`/api/auth/reset-password`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ password, token }),
}),
{
loading: "Resetting...",
success: `Reset password successful`,
error: "Could not reset password :/",
}
);
if (!response.ok) {
toast.dismiss();
const error = await response.json();
toast.error(error.message);
}
if (response.ok) {
setResetSuccessful(true);
setTimeout(() => {
router.push("/login");
}, 3000);
}
};
return (
<>
<div className="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<div>
<Logo className="mx-auto h-20 w-auto"></Logo>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
Reset Password
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
{resetSuccessful ? "Your password has been reset." : "Please chose your new password"}
</p>
</div>
{!resetSuccessful && (
<form className="mt-8 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div className="-space-y-px rounded-md shadow-sm">
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
{...register("password", { required: "Password is required" })}
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
placeholder="New password"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="sr-only">
Password
</label>
<input
{...register("confirmPassword")}
id="confirmPassword"
name="confirmPassword"
type="password"
required
className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm"
placeholder="Confirm new password"
/>
</div>
</div>
{errors && (
<span className="text-xs text-red-500">{errors.confirmPassword?.message}</span>
)}
<div>
<Button
type="submit"
disabled={isSubmitting}
className="group relative flex w-full">
Reset password
</Button>
</div>
</form>
)}
<div>
<Link href="/login">
<div className="relative mt-10 flex items-center justify-center gap-2 text-sm text-gray-500 hover:cursor-pointer hover:text-gray-900">
<ArrowLeftIcon className="h-4 w-4" />
Back to log in
</div>
</Link>
</div>
</div>
</div>
</>
);
}

View File

@ -22,7 +22,7 @@
"@tanstack/react-query": "^4.29.5",
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
"lucide-react": "^0.277.0",
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"next": "14.0.0",

View File

@ -1,63 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { sendResetPassword } from "@documenso/lib/mail";
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
import prisma from "@documenso/prisma";
import crypto from "crypto";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { email } = req.body;
const cleanEmail = email.toLowerCase();
if (!cleanEmail || !/.+@.+/.test(cleanEmail)) {
res.status(400).json({ message: "Invalid email" });
return;
}
const user = await prisma.user.findFirst({
where: {
email: cleanEmail,
},
});
if (!user) {
return res.status(200).json({ message: "A password reset email has been sent." });
}
const existingToken = await prisma.passwordResetToken.findFirst({
where: {
userId: user.id,
createdAt: {
gte: new Date(Date.now() - 1000 * 60 * 60),
},
},
});
if (existingToken) {
return res.status(200).json({ message: "A password reset email has been sent." });
}
const token = crypto.randomBytes(64).toString("hex");
const expiry = new Date();
expiry.setHours(expiry.getHours() + 24); // Set expiry to one hour from now
let passwordResetToken;
try {
passwordResetToken = await prisma.passwordResetToken.create({
data: {
token,
expiry,
userId: user.id,
},
});
} catch (error) {
return res.status(500).json({ message: "Something went wrong" });
}
await sendResetPassword(user, passwordResetToken.token);
return res.status(200).json({ message: "A password reset email has been sent." });
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -1,69 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { hashPassword, verifyPassword } from "@documenso/lib/auth";
import { sendResetPasswordSuccessMail } from "@documenso/lib/mail";
import { defaultHandler, defaultResponder } from "@documenso/lib/server";
import prisma from "@documenso/prisma";
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const { token, password } = req.body;
if (!token) {
res.status(400).json({ message: "Invalid token" });
return;
}
const foundToken = await prisma.passwordResetToken.findUnique({
where: {
token,
},
include: {
User: true,
},
});
if (!foundToken) {
return res.status(404).json({ message: "Invalid token." });
}
const now = new Date();
if (now > foundToken.expiry) {
return res.status(400).json({ message: "Token has expired" });
}
const isSamePassword = await verifyPassword(password, foundToken.User.password!);
if (isSamePassword) {
return res.status(400).json({ message: "New password must be different" });
}
const hashedPassword = await hashPassword(password);
const transaction = await prisma.$transaction([
prisma.user.update({
where: {
id: foundToken.userId,
},
data: {
password: hashedPassword,
},
}),
prisma.passwordResetToken.deleteMany({
where: {
userId: foundToken.userId,
},
}),
]);
if (!transaction) {
return res.status(500).json({ message: "Error resetting password." });
}
await sendResetPasswordSuccessMail(foundToken.User);
res.status(200).json({ message: "Password reset successful." });
}
export default defaultHandler({
POST: Promise.resolve({ default: defaultResponder(postHandler) }),
});

View File

@ -1,30 +0,0 @@
import Head from "next/head";
import { getUserFromToken } from "@documenso/lib/server";
import ResetPassword from "../../../components/reset-password";
export default function ResetPasswordPage() {
return (
<>
<Head>
<title>Reset Password | Documenso</title>
</Head>
<ResetPassword />
</>
);
}
export async function getServerSideProps(context: any) {
const user = await getUserFromToken(context.req, context.res);
if (user)
return {
redirect: {
source: "/login",
destination: "/dashboard",
permanent: false,
},
};
return {
props: {},
};
}

View File

@ -1,20 +0,0 @@
import React from "react";
import Logo from "../../../components/logo";
export default function ResetPage() {
return (
<div className="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-md space-y-8">
<div>
<Logo className="mx-auto h-20 w-auto"></Logo>
<h2 className="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
Reset Password
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
The token you provided is invalid. Please try again.
</p>
</div>
</div>
</div>
);
}

View File

@ -1,32 +0,0 @@
import { GetServerSideProps, GetServerSidePropsContext } from "next";
import Head from "next/head";
import { getUserFromToken } from "@documenso/lib/server";
import ForgotPassword from "../components/forgot-password";
export default function ForgotPasswordPage() {
return (
<>
<Head>
<title>Forgot Password | Documenso</title>
</Head>
<ForgotPassword />
</>
);
}
export async function getServerSideProps({ req }: GetServerSidePropsContext) {
const user = await getUserFromToken(req);
if (user)
return {
redirect: {
source: "/login",
destination: "/dashboard",
permanent: false,
},
};
return {
props: {},
};
}

View File

@ -1,4 +1,4 @@
import { notFound } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import { match } from 'ts-pattern';
@ -40,7 +40,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
viewedDocument({ token }).catch(() => null),
]);
if (!document || !recipient) {
if (!document || !document.documentData || !recipient) {
return notFound();
}
@ -55,14 +55,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
redirect(`/sign/${token}/complete`);
}
const user = await getServerComponentSession();
const documentDataUrl = await getFile(documentData)
.then((buffer) => Buffer.from(buffer).toString('base64'))
.then((data) => `data:application/pdf;base64,${data}`);
const user = await getServerComponentSession();
return (
<SigningProvider
email={recipient.email}

View File

@ -39,6 +39,7 @@ export type SignInFormProps = {
export const SignInForm = ({ className }: SignInFormProps) => {
const { toast } = useToast();
const [showPassword, setShowPassword] = useState(false);
const {
register,

View File

@ -1,14 +1,7 @@
ARG NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
ARG NEXT_PUBLIC_ALLOW_SIGNUP=true
ARG NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS=false
ARG NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID
ARG NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS production_deps
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
@ -21,13 +14,6 @@ RUN npm ci --production
# Install dependencies only when needed
FROM base AS builder
ARG NEXT_PUBLIC_WEBAPP_URL
ARG NEXT_PUBLIC_ALLOW_SIGNUP
ARG NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS
ARG NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID
ARG NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID
WORKDIR /app
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
@ -45,7 +31,6 @@ RUN npm run build --workspaces
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production

View File

@ -1,3 +1,4 @@
name: documenso
services:
database:
image: postgres:15

65
package-lock.json generated
View File

@ -88,7 +88,7 @@
"@tanstack/react-query": "^4.29.5",
"formidable": "^2.1.1",
"framer-motion": "^10.12.8",
"lucide-react": "^0.277.0",
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"next": "14.0.0",
@ -7178,15 +7178,6 @@
"node": ">=8"
}
},
"node_modules/breakword": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/breakword/-/breakword-1.0.6.tgz",
"integrity": "sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==",
"dev": true,
"dependencies": {
"wcwidth": "^1.0.1"
}
},
"node_modules/browserslist": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
@ -8571,18 +8562,6 @@
"node": ">= 0.4"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"dev": true,
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@ -10095,16 +10074,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/find-yarn-workspace-root2": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz",
"integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==",
"dev": true,
"dependencies": {
"micromatch": "^4.0.2",
"pkg-dir": "^4.2.0"
}
},
"node_modules/flat-cache": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
@ -11393,18 +11362,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-ci": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
"dev": true,
"dependencies": {
"ci-info": "^3.2.0"
},
"bin": {
"is-ci": "bin.js"
}
},
"node_modules/is-core-module": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
@ -11662,18 +11619,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-subdir": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz",
"integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==",
"dev": true,
"dependencies": {
"better-path-resolve": "1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/is-symbol": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
@ -12569,9 +12514,9 @@
}
},
"node_modules/lucide-react": {
"version": "0.277.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.277.0.tgz",
"integrity": "sha512-9epmznme+vW14V9d2rsMeLr3fMnf59lYDUOVUg6s7oVN22Zq8h4B30+3CIdFFV9UXCjPG5ZNKHfO/hf96cl46A==",
"version": "0.279.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.279.0.tgz",
"integrity": "sha512-LJ8g66+Bxc3t3x9vKTeK3wn3xucrOQGfJ9ou9GsBwCt2offsrT2BB90XrTrIzE1noYYDe2O8jZaRHi6sAHXNxw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
@ -19956,7 +19901,7 @@
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"framer-motion": "^10.12.8",
"lucide-react": "^0.277.0",
"lucide-react": "^0.279.0",
"luxon": "^3.4.2",
"next": "14.0.0",
"pdfjs-dist": "3.6.172",

View File

@ -44,5 +44,8 @@
"workspaces": [
"apps/*",
"packages/*"
]
],
"dependencies": {
"recharts": "^2.7.2"
}
}

View File

@ -1,51 +0,0 @@
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
import { User } from "@prisma/client";
export const resetPasswordSuccessTemplate = (user: User) => {
return `
<div style="background-color: #eaeaea; padding: 2%;">
<div
style="text-align:left; margin: auto; font-size: 14px; color: #353434; max-width: 500px; border-radius: 0.375rem; background: white; padding: 50px">
<img src="${NEXT_PUBLIC_WEBAPP_URL}/logo_h.png" alt="Documenso Logo"
style="width: 180px; display: block; margin-bottom: 14px;" />
<h2 style="text-align: left; margin-top: 20px; font-size: 24px; font-weight: bold">Password updated!</h2>
<p style="margin-top: 15px">
Hi ${user.name ? user.name : user.email},
</p>
<p style="margin-top: 15px">
We've changed your password as you asked. You can now sign in with your new password.
</p>
<p style="margin-top: 15px">
Didn't request a password change? We are here to help you secure your account, just <a href="https://documenso.com">contact us</a>.
</p>
<p style="margin-top: 15px">
<p style="font-weight: bold">
The Documenso Team
</p>
</p>
<p style="text-align:left; margin-top: 30px">
<small>Want to send you own signing links?
<a href="https://documenso.com">Hosted Documenso is here!</a>.</small>
</p>
</div>
</div>
<div style="text-align: left; line-height: 18px; color: #666666; margin: 24px">
<div style="margin-top: 12px">
<b>Need help?</b>
<br>
Contact us at <a href="mailto:hi@documenso.com">hi@documenso.com</a>
</div>
<hr size="1" style="height: 1px; border: none; color: #D8D8D8; background-color: #D8D8D8">
<div style="text-align: center">
<small>Easy and beautiful document signing by Documenso.</small>
</div>
</div>
`;
};
export default resetPasswordSuccessTemplate;

View File

@ -1,46 +0,0 @@
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
export const resetPasswordTemplate = (ctaLink: string, ctaLabel: string) => {
const customContent = `
<h2 style="margin-top: 36px; font-size: 24px; font-weight: bold;">Forgot your password?</h2>
<p style="margin-top: 8px;">
That's okay, it happens! Click the button below to reset your password.
</p>
<p style="margin: 30px 0px; text-align: center">
<a href="${ctaLink}" style="background-color: #37f095; white-space: nowrap; color: white; border-color: transparent; border-width: 1px; border-radius: 0.375rem; font-size: 18px; padding-left: 16px; padding-right: 16px; padding-top: 10px; padding-bottom: 10px; text-decoration: none; margin-top: 4px; margin-bottom: 4px;">
${ctaLabel}
</a>
</p>
<p style="margin-top: 20px;">
<small>Want to send you own signing links? <a href="https://documenso.com">Hosted Documenso is here!</a>.</small>
</p>`;
const html = `
<div style="background-color: #eaeaea; padding: 2%;">
<div
style="text-align:center; margin: auto; font-size: 14px; color: #353434; max-width: 500px; border-radius: 0.375rem; background: white; padding: 50px">
<img src="${NEXT_PUBLIC_WEBAPP_URL}/logo_h.png" alt="Documenso Logo"
style="width: 180px; display: block; margin: auto; margin-bottom: 14px;" />
${customContent}
</div>
</div>
`;
const footer = `
<div style="text-align: left; line-height: 18px; color: #666666; margin: 24px">
<div style="margin-top: 12px">
<b>Need help?</b>
<br>
Contact us at <a href="mailto:hi@documenso.com">hi@documenso.com</a>
</div>
<hr size="1" style="height: 1px; border: none; color: #D8D8D8; background-color: #D8D8D8">
<div style="text-align: center">
<small>Easy and beautiful document signing by Documenso.</small>
</div>
</div>`;
return html + footer;
};
export default resetPasswordTemplate;

View File

@ -1,14 +0,0 @@
import { resetPasswordTemplate } from "@documenso/lib/mail";
import { NEXT_PUBLIC_WEBAPP_URL } from "../constants";
import { sendMail } from "./sendMail";
import { User } from "@prisma/client";
export const sendResetPassword = async (user: User, token: string) => {
await sendMail(
user.email,
"Forgot password?",
resetPasswordTemplate(`${NEXT_PUBLIC_WEBAPP_URL}/auth/reset/${token}`, "Reset Your Password")
).catch((err) => {
throw err;
});
};

View File

@ -1,11 +0,0 @@
import resetPasswordSuccessTemplate from "./resetPasswordSuccessTemplate";
import { sendMail } from "./sendMail";
import { User } from "@prisma/client";
export const sendResetPasswordSuccessMail = async (user: User) => {
await sendMail(user.email, "Password Reset Success!", resetPasswordSuccessTemplate(user)).catch(
(err) => {
throw err;
}
);
};

View File

@ -4,6 +4,7 @@ import { mailer } from '@documenso/email/mailer';
import { render } from '@documenso/email/render';
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
import { renderCustomEmailTemplate } from '@documenso/lib/utils/render-custom-email-template';
import { prisma } from '@documenso/prisma';
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';

View File

@ -14,6 +14,7 @@ export const appRouter = router({
profile: profileRouter,
document: documentRouter,
field: fieldRouter,
admin: adminRouter,
shareLink: shareLinkRouter,
});

View File

@ -1,15 +1,15 @@
import type { LucideIcon, LucideProps } from 'lucide-react/dist/lucide-react';
import { forwardRef } from 'react';
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
export const SignatureIcon = (({
size = 24,
color = 'currentColor',
strokeWidth = 1.33,
absoluteStrokeWidth,
...props
}: LucideProps) => {
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
export const SignatureIcon: LucideIcon = forwardRef(
(
{ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props },
ref,
) => {
return (
<svg
ref={ref}
width={size}
height={size}
viewBox="0 0 16 16"
@ -20,10 +20,15 @@ export const SignatureIcon = (({
<path
d="M1.5 11H14.5M1.5 14C1.5 14 8.72 2 4.86938 2H4.875C2.01 2 1.97437 14.0694 8 6.51188V6.5C8 6.5 9 11.3631 11.5 7.52375V7.5C11.5 7.5 11.5 9 14.5 9"
stroke={color}
strokeWidth={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
strokeWidth={
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth
}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}) as LucideIcon;
},
);
SignatureIcon.displayName = 'SignatureIcon';

View File

@ -58,7 +58,7 @@
"clsx": "^1.2.1",
"cmdk": "^0.2.0",
"framer-motion": "^10.12.8",
"lucide-react": "^0.277.0",
"lucide-react": "^0.279.0",
"luxon": "^3.4.2",
"next": "14.0.0",
"pdfjs-dist": "3.6.172",

View File

@ -2,13 +2,8 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
".next/**",
"!.next/cache/**"
]
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"clean": {
@ -30,9 +25,7 @@
"dependsOn": ["^build"]
}
},
"globalDependencies": [
"**/.env.*local"
],
"globalDependencies": ["**/.env.*local"],
"globalEnv": [
"APP_VERSION",
"NEXTAUTH_URL",