mirror of
https://github.com/documenso/documenso.git
synced 2025-11-09 20:12:31 +10:00
chore: restore dangling changes from rebase
This commit is contained in:
@ -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)
|
|
||||||
@ -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"
|
|
||||||
}
|
|
||||||
@ -14,18 +14,18 @@
|
|||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {
|
"vscode": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"GitHub.vscode-pull-request-github",
|
|
||||||
"GitHub.copilot-labs",
|
|
||||||
"GitHub.copilot-chat",
|
|
||||||
"GitHub.copilot",
|
|
||||||
"aaron-bond.better-comments",
|
"aaron-bond.better-comments",
|
||||||
"mikestead.dotenv",
|
|
||||||
"VisualStudioExptTeam.vscodeintellicode",
|
|
||||||
"Prisma.prisma",
|
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"mikestead.dotenv",
|
||||||
"unifiedjs.vscode-mdx",
|
"unifiedjs.vscode-mdx",
|
||||||
"esbenp.prettier-vscode"
|
"GitHub.copilot-chat",
|
||||||
|
"GitHub.copilot-labs",
|
||||||
|
"GitHub.copilot",
|
||||||
|
"GitHub.vscode-pull-request-github",
|
||||||
|
"Prisma.prisma",
|
||||||
|
"VisualStudioExptTeam.vscodeintellicode",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
.env.gitpod
29
.env.gitpod
@ -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
|
|
||||||
@ -52,11 +52,3 @@ You can build the project with:
|
|||||||
```bash
|
```bash
|
||||||
npm run build
|
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
|
|
||||||
```
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<a href="https://github.com/documenso/documenso/issues">Issues</a>
|
<a href="https://github.com/documenso/documenso/issues">Issues</a>
|
||||||
·
|
·
|
||||||
<a href="https://github.com/documenso/documenso/milestones">Roadmap</a>
|
<a href="https://github.com/documenso/documenso/milestones">Roadmap</a>
|
||||||
·
|
·
|
||||||
<a href="https://documen.so/launches">Upcoming Launches</a>
|
<a href="https://documen.so/launches">Upcoming Launches</a>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -20,11 +20,11 @@ Today, I'm pleased to share with you a preview of the next Documenso.
|
|||||||
|
|
||||||
We redesigned the whole signing flow to make it more appealing and more convenient.
|
We redesigned the whole signing flow to make it more appealing and more convenient.
|
||||||
|
|
||||||
We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it.
|
We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it.
|
||||||
|
|
||||||
**We call it happy minimalism.**
|
**We call it happy minimalism.**
|
||||||
|
|
||||||
We paid particular attention to the moment of signing, which should be celebrated.
|
We paid particular attention to the moment of signing, which should be celebrated.
|
||||||
|
|
||||||
The image below is the final bloom of the completion celebration we added:
|
The image below is the final bloom of the completion celebration we added:
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,8 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
</PlausibleProvider>
|
</PlausibleProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</FeatureFlagProvider>
|
</FeatureFlagProvider>
|
||||||
|
|
||||||
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export const Callout = ({ starCount }: CalloutProps) => {
|
|||||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||||
onClick={onSignUpClick}
|
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">
|
<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!
|
$30/mo. forever!
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -24,7 +24,13 @@ export const Header = ({ className, ...props }: HeaderProps) => {
|
|||||||
<header className={cn('flex items-center justify-between', className)} {...props}>
|
<header className={cn('flex items-center justify-between', className)} {...props}>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
|
<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>
|
</Link>
|
||||||
|
|
||||||
{isSinglePlayerModeMarketingEnabled && (
|
{isSinglePlayerModeMarketingEnabled && (
|
||||||
|
|||||||
@ -114,7 +114,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
|||||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||||
onClick={onSignUpClick}
|
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">
|
<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!
|
$30/mo. forever!
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
data-plan="community"
|
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]"
|
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">
|
<div className="text-primary mt-2.5 text-xl font-medium">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
|
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
|
||||||
@ -168,7 +168,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-col divide-y">
|
<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">Custom Subdomain</p>
|
||||||
<p className="text-foreground py-4">Compliance Check</p>
|
<p className="text-foreground py-4">Compliance Check</p>
|
||||||
<p className="text-foreground py-4">Guaranteed Uptime</p>
|
<p className="text-foreground py-4">Guaranteed Uptime</p>
|
||||||
|
|||||||
@ -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"
|
className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5"
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
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">
|
<p className="text-muted-foreground mt-2 text-xs">
|
||||||
with Timur Ercan & Lucas Smith from Documenso
|
with Timur Ercan & Lucas Smith from Documenso
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"@tanstack/react-query": "^4.29.5",
|
"@tanstack/react-query": "^4.29.5",
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.277.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
@ -50,4 +50,4 @@
|
|||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.18",
|
||||||
"@types/react-dom": "18.2.7"
|
"@types/react-dom": "18.2.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) }),
|
|
||||||
});
|
|
||||||
@ -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) }),
|
|
||||||
});
|
|
||||||
@ -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: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
viewedDocument({ token }).catch(() => null),
|
viewedDocument({ token }).catch(() => null),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!document || !recipient) {
|
if (!document || !document.documentData || !recipient) {
|
||||||
return notFound();
|
return notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,14 +55,6 @@ export default async function SigningPage({ params: { token } }: SigningPageProp
|
|||||||
redirect(`/sign/${token}/complete`);
|
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 (
|
return (
|
||||||
<SigningProvider
|
<SigningProvider
|
||||||
email={recipient.email}
|
email={recipient.email}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export type SignInFormProps = {
|
|||||||
|
|
||||||
export const SignInForm = ({ className }: SignInFormProps) => {
|
export const SignInForm = ({ className }: SignInFormProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
|
|||||||
@ -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
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
# Install dependencies only when needed
|
# Install dependencies only when needed
|
||||||
FROM base AS production_deps
|
FROM base AS production_deps
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
# 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
|
# Install dependencies only when needed
|
||||||
FROM base AS builder
|
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
|
WORKDIR /app
|
||||||
|
|
||||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
# 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
|
# Production image, copy all the files and run next
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
name: documenso
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
|
|||||||
65
package-lock.json
generated
65
package-lock.json
generated
@ -88,7 +88,7 @@
|
|||||||
"@tanstack/react-query": "^4.29.5",
|
"@tanstack/react-query": "^4.29.5",
|
||||||
"formidable": "^2.1.1",
|
"formidable": "^2.1.1",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.277.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
@ -7178,15 +7178,6 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.22.1",
|
"version": "4.22.1",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
||||||
@ -8571,18 +8562,6 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/define-properties": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||||
@ -10095,16 +10074,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/flat-cache": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz",
|
||||||
@ -11393,18 +11362,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.13.0",
|
"version": "2.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
|
"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"
|
"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": {
|
"node_modules/is-symbol": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
|
||||||
@ -12569,9 +12514,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
"version": "0.277.0",
|
"version": "0.279.0",
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.277.0.tgz",
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.279.0.tgz",
|
||||||
"integrity": "sha512-9epmznme+vW14V9d2rsMeLr3fMnf59lYDUOVUg6s7oVN22Zq8h4B30+3CIdFFV9UXCjPG5ZNKHfO/hf96cl46A==",
|
"integrity": "sha512-LJ8g66+Bxc3t3x9vKTeK3wn3xucrOQGfJ9ou9GsBwCt2offsrT2BB90XrTrIzE1noYYDe2O8jZaRHi6sAHXNxw==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
@ -19956,7 +19901,7 @@
|
|||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"cmdk": "^0.2.0",
|
"cmdk": "^0.2.0",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.277.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.2",
|
"luxon": "^3.4.2",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.6.172",
|
||||||
|
|||||||
@ -44,5 +44,8 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"recharts": "^2.7.2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -4,6 +4,7 @@ import { mailer } from '@documenso/email/mailer';
|
|||||||
import { render } from '@documenso/email/render';
|
import { render } from '@documenso/email/render';
|
||||||
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
import { DocumentInviteEmailTemplate } from '@documenso/email/templates/document-invite';
|
||||||
import { FROM_ADDRESS, FROM_NAME } from '@documenso/lib/constants/email';
|
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 { prisma } from '@documenso/prisma';
|
||||||
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
import { DocumentStatus, SendStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export const appRouter = router({
|
|||||||
profile: profileRouter,
|
profile: profileRouter,
|
||||||
document: documentRouter,
|
document: documentRouter,
|
||||||
field: fieldRouter,
|
field: fieldRouter,
|
||||||
|
admin: adminRouter,
|
||||||
shareLink: shareLinkRouter,
|
shareLink: shareLinkRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,29 +1,34 @@
|
|||||||
import type { LucideIcon, LucideProps } from 'lucide-react/dist/lucide-react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||||
export const SignatureIcon = (({
|
|
||||||
size = 24,
|
export const SignatureIcon: LucideIcon = forwardRef(
|
||||||
color = 'currentColor',
|
(
|
||||||
strokeWidth = 1.33,
|
{ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props },
|
||||||
absoluteStrokeWidth,
|
ref,
|
||||||
...props
|
) => {
|
||||||
}: LucideProps) => {
|
return (
|
||||||
return (
|
<svg
|
||||||
<svg
|
ref={ref}
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
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}
|
stroke={color}
|
||||||
strokeWidth={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
|
strokeWidth={
|
||||||
strokeLinecap="round"
|
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth
|
||||||
strokeLinejoin="round"
|
}
|
||||||
/>
|
strokeLinecap="round"
|
||||||
</svg>
|
strokeLinejoin="round"
|
||||||
);
|
/>
|
||||||
}) as LucideIcon;
|
</svg>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SignatureIcon.displayName = 'SignatureIcon';
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"cmdk": "^0.2.0",
|
"cmdk": "^0.2.0",
|
||||||
"framer-motion": "^10.12.8",
|
"framer-motion": "^10.12.8",
|
||||||
"lucide-react": "^0.277.0",
|
"lucide-react": "^0.279.0",
|
||||||
"luxon": "^3.4.2",
|
"luxon": "^3.4.2",
|
||||||
"next": "14.0.0",
|
"next": "14.0.0",
|
||||||
"pdfjs-dist": "3.6.172",
|
"pdfjs-dist": "3.6.172",
|
||||||
|
|||||||
13
turbo.json
13
turbo.json
@ -2,13 +2,8 @@
|
|||||||
"$schema": "https://turbo.build/schema.json",
|
"$schema": "https://turbo.build/schema.json",
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"build": {
|
"build": {
|
||||||
"dependsOn": [
|
"dependsOn": ["^build"],
|
||||||
"^build"
|
"outputs": [".next/**", "!.next/cache/**"]
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
".next/**",
|
|
||||||
"!.next/cache/**"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"lint": {},
|
"lint": {},
|
||||||
"clean": {
|
"clean": {
|
||||||
@ -30,9 +25,7 @@
|
|||||||
"dependsOn": ["^build"]
|
"dependsOn": ["^build"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globalDependencies": [
|
"globalDependencies": ["**/.env.*local"],
|
||||||
"**/.env.*local"
|
|
||||||
],
|
|
||||||
"globalEnv": [
|
"globalEnv": [
|
||||||
"APP_VERSION",
|
"APP_VERSION",
|
||||||
"NEXTAUTH_URL",
|
"NEXTAUTH_URL",
|
||||||
|
|||||||
Reference in New Issue
Block a user