This commit is contained in:
David Nguyen
2025-02-11 02:04:00 +11:00
parent d24f67d922
commit 548d92c2fc
22 changed files with 1260 additions and 1152 deletions

View File

@ -3,11 +3,12 @@ import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { match } from 'ts-pattern';
import { z } from 'zod';
import { authClient } from '@documenso/auth/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@ -55,15 +56,15 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
const isSubmitting = form.formState.isSubmitting;
const { mutateAsync: resetPassword } = trpc.profile.resetPassword.useMutation();
const onFormSubmit = async ({ password }: Omit<TResetPasswordFormSchema, 'repeatedPassword'>) => {
try {
await resetPassword({
await authClient.emailPassword.resetPassword({
password,
token,
});
await navigate('/signin');
form.reset();
toast({
@ -71,8 +72,6 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
description: _(msg`Your password has been updated successfully.`),
duration: 5000,
});
navigate('/signin');
} catch (err) {
const error = AppError.parseError(err);

View File

@ -5,7 +5,7 @@ import { Trans } from '@lingui/react/macro';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { trpc } from '@documenso/trpc/react';
import { authClient } from '@documenso/auth/client';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
@ -42,11 +42,9 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
const isSubmitting = form.formState.isSubmitting;
const { mutateAsync: sendConfirmationEmail } = trpc.profile.sendConfirmationEmail.useMutation();
const onFormSubmit = async ({ email }: TSendConfirmationEmailFormSchema) => {
try {
await sendConfirmationEmail({ email });
await authClient.emailPassword.resendVerifyEmail({ email });
toast({
title: _(msg`Confirmation email sent`),
@ -59,6 +57,7 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
form.reset();
} catch (err) {
toast({
variant: 'destructive',
title: _(msg`An error occurred while sending your confirmation email`),
description: _(msg`Please try again and make sure you enter the correct email address.`),
});

View File

@ -17,6 +17,7 @@ import { getOptionalLoaderSession } from 'server/utils/get-loader-session';
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
import { createPublicEnv } from '@documenso/lib/utils/env';
import { extractLocaleData } from '@documenso/lib/utils/i18n';
import { TrpcProvider } from '@documenso/trpc/react';
import { Toaster } from '@documenso/ui/primitives/toaster';
@ -99,9 +100,7 @@ export async function loader({ request }: Route.LoaderArgs) {
lang,
theme: getTheme(),
session,
__ENV__: Object.fromEntries(
Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_')), // Todo: I'm pretty sure this will leak?
),
publicEnv: createPublicEnv(),
},
{
headers: {
@ -112,7 +111,7 @@ export async function loader({ request }: Route.LoaderArgs) {
}
export function Layout({ children }: { children: React.ReactNode }) {
const { __ENV__, theme, lang } = useLoaderData<typeof loader>() || {};
const { publicEnv, theme, lang } = useLoaderData<typeof loader>() || {};
// const [theme] = useTheme();
@ -145,7 +144,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
<script
dangerouslySetInnerHTML={{
__html: `window.__ENV__ = ${JSON.stringify(__ENV__)}`,
__html: `window.__ENV__ = ${JSON.stringify(publicEnv)}`,
}}
/>
</body>

View File

@ -11,6 +11,8 @@ import { env } from '@documenso/lib/utils/env';
import { SignInForm } from '~/components/forms/signin';
import type { Route } from './+types/signin';
export function meta() {
return [{ title: 'Sign In' }];
}
@ -18,13 +20,24 @@ export function meta() {
export function loader() {
const session = getOptionalLoaderSession();
// SSR env variables.
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
const oidcProviderLabel = OIDC_PROVIDER_LABEL;
if (session) {
throw redirect('/documents');
}
return {
isGoogleSSOEnabled,
isOIDCSSOEnabled,
oidcProviderLabel,
};
}
export default function SignIn() {
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
export default function SignIn({ loaderData }: Route.ComponentProps) {
const { isGoogleSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel } = loaderData;
return (
<div className="w-screen max-w-lg px-4">
@ -39,12 +52,12 @@ export default function SignIn() {
<hr className="-mx-6 my-4" />
<SignInForm
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
oidcProviderLabel={OIDC_PROVIDER_LABEL}
isGoogleSSOEnabled={isGoogleSSOEnabled}
isOIDCSSOEnabled={isOIDCSSOEnabled}
oidcProviderLabel={oidcProviderLabel}
/>
{NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
{env('NEXT_PUBLIC_DISABLE_SIGNUP') !== 'true' && (
<p className="text-muted-foreground mt-6 text-center text-sm">
<Trans>
Don't have an account?{' '}

View File

@ -5,6 +5,8 @@ import { env } from '@documenso/lib/utils/env';
import { SignUpForm } from '~/components/forms/signup';
import type { Route } from './+types/signup';
export function meta() {
return [{ title: 'Sign Up' }];
}
@ -12,17 +14,28 @@ export function meta() {
export function loader() {
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
// SSR env variables.
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
throw redirect('/signin');
}
return {
isGoogleSSOEnabled,
isOIDCSSOEnabled,
};
}
export default function SignUp() {
export default function SignUp({ loaderData }: Route.ComponentProps) {
const { isGoogleSSOEnabled, isOIDCSSOEnabled } = loaderData;
return (
<SignUpForm
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
isGoogleSSOEnabled={isGoogleSSOEnabled}
isOIDCSSOEnabled={isOIDCSSOEnabled}
/>
);
}

View File

@ -3,6 +3,22 @@ import { Outlet, isRouteErrorResponse, useRouteError } from 'react-router';
import { EmbedAuthenticationRequired } from '~/components/embed/embed-authentication-required';
import { EmbedPaywall } from '~/components/embed/embed-paywall';
import type { Route } from './+types/_layout';
// Todo: Test
export function headers({ loaderHeaders }: Route.HeadersArgs) {
const origin = loaderHeaders.get('Origin') ?? '*';
// Allow third parties to iframe the document.
return {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Origin': origin,
'Content-Security-Policy': `frame-ancestors ${origin}`,
'Referrer-Policy': 'strict-origin-when-cross-origin',
'X-Content-Type-Options': 'nosniff',
};
}
export default function Layout() {
return <Outlet />;
}

View File

@ -6,11 +6,12 @@
"build": "sh .bin/build.sh",
"build:app": "cross-env NODE_ENV=production react-router build",
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
"dev": "react-router dev",
"start": "cross-env NODE_ENV=production node build/server/main.js",
"dev": "npm run with:env -- react-router dev",
"start": "npm run with:env -- cross-env NODE_ENV=production node build/server/main.js",
"clean": "rimraf .react-router && rimraf node_modules",
"typecheck": "react-router typegen && tsc",
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs"
"copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs",
"with:env": "dotenv -e ../../.env -e ../../.env.local --"
},
"dependencies": {
"@documenso/api": "*",

View File

@ -2,21 +2,18 @@ import { lingui } from '@lingui/vite-plugin';
import { reactRouter } from '@react-router/dev/vite';
import autoprefixer from 'autoprefixer';
import serverAdapter from 'hono-react-router-adapter/vite';
import path from 'path';
import tailwindcss from 'tailwindcss';
import { defineConfig, loadEnv } from 'vite';
import { defineConfig } from 'vite';
import macrosPlugin from 'vite-plugin-babel-macros';
import tsconfigPaths from 'vite-tsconfig-paths';
/**
* Note: We load the env variables externally so we can have runtime enviroment variables
* for docker.
*
* Do not configure any envs here.
*/
export default defineConfig({
envDir: path.join(__dirname, '../../'),
envPrefix: '__DO_NOT_USE_OR_YOU_WILL_BE_FIRED__',
define: {
'process.env': {
...process.env,
...loadEnv('development', path.join(__dirname, '../../'), ''),
},
},
css: {
postcss: {
plugins: [tailwindcss, autoprefixer],
@ -37,18 +34,12 @@ export default defineConfig({
],
ssr: {
noExternal: ['react-dropzone', 'plausible-tracker', 'pdfjs-dist'],
external: ['@node-rs/bcrypt', '@node-rs/bcrypt-wasm32-wasi', '@prisma/client'],
external: ['@node-rs/bcrypt', '@prisma/client', '@documenso/tailwind-config'],
},
optimizeDeps: {
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
include: ['prop-types', 'file-selector', 'attr-accept'],
exclude: [
'node_modules',
'@node-rs/bcrypt',
'@node-rs/bcrypt-wasm32-wasi',
'@documenso/pdf-sign',
'sharp',
],
exclude: ['node_modules', '@node-rs/bcrypt', '@documenso/pdf-sign', 'sharp'],
},
resolve: {
alias: {