diff --git a/apps/web/src/app/(unauthenticated)/check-email/page.tsx b/apps/web/src/app/(unauthenticated)/check-email/page.tsx new file mode 100644 index 000000000..fffbc44c1 --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/check-email/page.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; + +import { Button } from '@documenso/ui/primitives/button'; + +export default function ForgotPasswordPage() { + return ( +
+

Email sent!

+ +

+ A password reset email has been sent, if you have an account you should see it in your inbox + shortly. +

+ + +
+ ); +} diff --git a/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx b/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx new file mode 100644 index 000000000..4f0617f7c --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/forgot-password/page.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link'; + +import { ForgotPasswordForm } from '~/components/forms/forgot-password'; + +export default function ForgotPasswordPage() { + return ( +
+

Forgotten your password?

+ +

+ No worries, it happens! Enter your email and we'll email you a special link to reset your + password. +

+ + + +

+ Remembered your password?{' '} + + Sign In + +

+
+ ); +} diff --git a/apps/web/src/app/(unauthenticated)/layout.tsx b/apps/web/src/app/(unauthenticated)/layout.tsx new file mode 100644 index 000000000..c88b9fb2e --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/layout.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import Image from 'next/image'; + +import backgroundPattern from '~/assets/background-pattern.png'; + +type UnauthenticatedLayoutProps = { + children: React.ReactNode; +}; + +export default function UnauthenticatedLayout({ children }: UnauthenticatedLayoutProps) { + return ( +
+
+
+ background pattern +
+ +
{children}
+
+
+ ); +} diff --git a/apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx b/apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx new file mode 100644 index 000000000..04afd2c4d --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx @@ -0,0 +1,37 @@ +import Link from 'next/link'; +import { redirect } from 'next/navigation'; + +import { getResetTokenValidity } from '@documenso/lib/server-only/user/get-reset-token-validity'; + +import { ResetPasswordForm } from '~/components/forms/reset-password'; + +type ResetPasswordPageProps = { + params: { + token: string; + }; +}; + +export default async function ResetPasswordPage({ params: { token } }: ResetPasswordPageProps) { + const isValid = await getResetTokenValidity({ token }); + + if (!isValid) { + redirect('/reset-password'); + } + + return ( +
+

Reset Password

+ +

Please choose your new password

+ + + +

+ Don't have an account?{' '} + + Sign up + +

+
+ ); +} diff --git a/apps/web/src/app/(unauthenticated)/reset-password/page.tsx b/apps/web/src/app/(unauthenticated)/reset-password/page.tsx new file mode 100644 index 000000000..c4f521363 --- /dev/null +++ b/apps/web/src/app/(unauthenticated)/reset-password/page.tsx @@ -0,0 +1,20 @@ +import Link from 'next/link'; + +import { Button } from '@documenso/ui/primitives/button'; + +export default function ResetPasswordPage() { + return ( +
+

Unable to reset password

+ +

+ The token you have used to reset your password is either expired or it never existed. If you + have still forgotten your password, please request a new reset link. +

+ + +
+ ); +} diff --git a/apps/web/src/app/(unauthenticated)/signin/page.tsx b/apps/web/src/app/(unauthenticated)/signin/page.tsx index 800ff9b9a..868b0471d 100644 --- a/apps/web/src/app/(unauthenticated)/signin/page.tsx +++ b/apps/web/src/app/(unauthenticated)/signin/page.tsx @@ -1,43 +1,33 @@ -import Image from 'next/image'; import Link from 'next/link'; -import backgroundPattern from '~/assets/background-pattern.png'; -import connections from '~/assets/card-sharing-figure.png'; import { SignInForm } from '~/components/forms/signin'; export default function SignInPage() { return ( -
-
-
- background pattern -
+
+

Sign in to your account

-
-

Sign in to your account

+

+ Welcome back, we are lucky to have you. +

-

- Welcome back, we are lucky to have you. -

+ - +

+ Don't have an account?{' '} + + Sign up + +

-

- Don't have an account?{' '} - - Sign up - -

-
- -
- documenso connections -
-
-
+

+ + Forgotten your password? + +

+ ); } diff --git a/apps/web/src/app/(unauthenticated)/signup/page.tsx b/apps/web/src/app/(unauthenticated)/signup/page.tsx index b398e6990..0d82e5c4f 100644 --- a/apps/web/src/app/(unauthenticated)/signup/page.tsx +++ b/apps/web/src/app/(unauthenticated)/signup/page.tsx @@ -1,44 +1,25 @@ -import Image from 'next/image'; import Link from 'next/link'; -import backgroundPattern from '~/assets/background-pattern.png'; -import connections from '~/assets/connections.png'; import { SignUpForm } from '~/components/forms/signup'; export default function SignUpPage() { return ( -
-
-
- background pattern -
+
+

Create a new account

-
-

Create a shiny, new Documenso Account ✨

+

+ Create your account and start using state-of-the-art document signing. Open and beautiful + signing is within your grasp. +

-

- Create your account and start using state-of-the-art document signing. Open and - beautiful signing is within your grasp. -

+ - - -

- Already have an account?{' '} - - Sign in instead - -

-
- -
- documenso connections -
-
-
+

+ Already have an account?{' '} + + Sign in instead + +

+ ); } diff --git a/apps/web/src/components/forms/forgot-password.tsx b/apps/web/src/components/forms/forgot-password.tsx new file mode 100644 index 000000000..449d346e4 --- /dev/null +++ b/apps/web/src/components/forms/forgot-password.tsx @@ -0,0 +1,80 @@ +'use client'; + +import { useRouter } from 'next/navigation'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +import { trpc } from '@documenso/trpc/react'; +import { cn } from '@documenso/ui/lib/utils'; +import { Button } from '@documenso/ui/primitives/button'; +import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; +import { Input } from '@documenso/ui/primitives/input'; +import { Label } from '@documenso/ui/primitives/label'; +import { useToast } from '@documenso/ui/primitives/use-toast'; + +export const ZForgotPasswordFormSchema = z.object({ + email: z.string().email().min(1), +}); + +export type TForgotPasswordFormSchema = z.infer; + +export type ForgotPasswordFormProps = { + className?: string; +}; + +export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => { + const router = useRouter(); + const { toast } = useToast(); + + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm({ + values: { + email: '', + }, + resolver: zodResolver(ZForgotPasswordFormSchema), + }); + + const { mutateAsync: forgotPassword } = trpc.profile.forgotPassword.useMutation(); + + const onFormSubmit = async ({ email }: TForgotPasswordFormSchema) => { + await forgotPassword({ email }).catch(() => null); + + toast({ + title: 'Reset email sent', + description: + 'A password reset email has been sent, if you have an account you should see it in your inbox shortly.', + duration: 5000, + }); + + reset(); + + router.push('/check-email'); + }; + + return ( +
+
+ + + + + +
+ + +
+ ); +}; diff --git a/apps/web/src/components/forms/password.tsx b/apps/web/src/components/forms/password.tsx index e9431dd4a..c190803cd 100644 --- a/apps/web/src/components/forms/password.tsx +++ b/apps/web/src/components/forms/password.tsx @@ -92,7 +92,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => { onSubmit={handleSubmit(onFormSubmit)} >
-
-