mirror of
https://github.com/documenso/documenso.git
synced 2025-11-20 03:32:14 +10:00
fix: wip
This commit is contained in:
@ -3,11 +3,12 @@ import { msg } from '@lingui/core/macro';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans } from '@lingui/react/macro';
|
import { Trans } from '@lingui/react/macro';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
import { match } from 'ts-pattern';
|
import { match } from 'ts-pattern';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { authClient } from '@documenso/auth/client';
|
||||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
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 { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
@ -55,15 +56,15 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
|
|||||||
|
|
||||||
const isSubmitting = form.formState.isSubmitting;
|
const isSubmitting = form.formState.isSubmitting;
|
||||||
|
|
||||||
const { mutateAsync: resetPassword } = trpc.profile.resetPassword.useMutation();
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ password }: Omit<TResetPasswordFormSchema, 'repeatedPassword'>) => {
|
const onFormSubmit = async ({ password }: Omit<TResetPasswordFormSchema, 'repeatedPassword'>) => {
|
||||||
try {
|
try {
|
||||||
await resetPassword({
|
await authClient.emailPassword.resetPassword({
|
||||||
password,
|
password,
|
||||||
token,
|
token,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await navigate('/signin');
|
||||||
|
|
||||||
form.reset();
|
form.reset();
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@ -71,8 +72,6 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
|
|||||||
description: _(msg`Your password has been updated successfully.`),
|
description: _(msg`Your password has been updated successfully.`),
|
||||||
duration: 5000,
|
duration: 5000,
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate('/signin');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = AppError.parseError(err);
|
const error = AppError.parseError(err);
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Trans } from '@lingui/react/macro';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
import { authClient } from '@documenso/auth/client';
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import {
|
import {
|
||||||
@ -42,11 +42,9 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
|
|||||||
|
|
||||||
const isSubmitting = form.formState.isSubmitting;
|
const isSubmitting = form.formState.isSubmitting;
|
||||||
|
|
||||||
const { mutateAsync: sendConfirmationEmail } = trpc.profile.sendConfirmationEmail.useMutation();
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ email }: TSendConfirmationEmailFormSchema) => {
|
const onFormSubmit = async ({ email }: TSendConfirmationEmailFormSchema) => {
|
||||||
try {
|
try {
|
||||||
await sendConfirmationEmail({ email });
|
await authClient.emailPassword.resendVerifyEmail({ email });
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Confirmation email sent`),
|
title: _(msg`Confirmation email sent`),
|
||||||
@ -59,6 +57,7 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
|
|||||||
form.reset();
|
form.reset();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast({
|
toast({
|
||||||
|
variant: 'destructive',
|
||||||
title: _(msg`An error occurred while sending your confirmation email`),
|
title: _(msg`An error occurred while sending your confirmation email`),
|
||||||
description: _(msg`Please try again and make sure you enter the correct email address.`),
|
description: _(msg`Please try again and make sure you enter the correct email address.`),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { getOptionalLoaderSession } from 'server/utils/get-loader-session';
|
|||||||
|
|
||||||
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
|
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
|
||||||
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
|
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 { extractLocaleData } from '@documenso/lib/utils/i18n';
|
||||||
import { TrpcProvider } from '@documenso/trpc/react';
|
import { TrpcProvider } from '@documenso/trpc/react';
|
||||||
import { Toaster } from '@documenso/ui/primitives/toaster';
|
import { Toaster } from '@documenso/ui/primitives/toaster';
|
||||||
@ -99,9 +100,7 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||||||
lang,
|
lang,
|
||||||
theme: getTheme(),
|
theme: getTheme(),
|
||||||
session,
|
session,
|
||||||
__ENV__: Object.fromEntries(
|
publicEnv: createPublicEnv(),
|
||||||
Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_')), // Todo: I'm pretty sure this will leak?
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@ -112,7 +111,7 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const { __ENV__, theme, lang } = useLoaderData<typeof loader>() || {};
|
const { publicEnv, theme, lang } = useLoaderData<typeof loader>() || {};
|
||||||
|
|
||||||
// const [theme] = useTheme();
|
// const [theme] = useTheme();
|
||||||
|
|
||||||
@ -145,7 +144,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `window.__ENV__ = ${JSON.stringify(__ENV__)}`,
|
__html: `window.__ENV__ = ${JSON.stringify(publicEnv)}`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { env } from '@documenso/lib/utils/env';
|
|||||||
|
|
||||||
import { SignInForm } from '~/components/forms/signin';
|
import { SignInForm } from '~/components/forms/signin';
|
||||||
|
|
||||||
|
import type { Route } from './+types/signin';
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return [{ title: 'Sign In' }];
|
return [{ title: 'Sign In' }];
|
||||||
}
|
}
|
||||||
@ -18,13 +20,24 @@ export function meta() {
|
|||||||
export function loader() {
|
export function loader() {
|
||||||
const session = getOptionalLoaderSession();
|
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) {
|
if (session) {
|
||||||
throw redirect('/documents');
|
throw redirect('/documents');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isGoogleSSOEnabled,
|
||||||
|
isOIDCSSOEnabled,
|
||||||
|
oidcProviderLabel,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SignIn() {
|
export default function SignIn({ loaderData }: Route.ComponentProps) {
|
||||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
const { isGoogleSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel } = loaderData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-screen max-w-lg px-4">
|
<div className="w-screen max-w-lg px-4">
|
||||||
@ -39,12 +52,12 @@ export default function SignIn() {
|
|||||||
<hr className="-mx-6 my-4" />
|
<hr className="-mx-6 my-4" />
|
||||||
|
|
||||||
<SignInForm
|
<SignInForm
|
||||||
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
isGoogleSSOEnabled={isGoogleSSOEnabled}
|
||||||
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
isOIDCSSOEnabled={isOIDCSSOEnabled}
|
||||||
oidcProviderLabel={OIDC_PROVIDER_LABEL}
|
oidcProviderLabel={oidcProviderLabel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
|
{env('NEXT_PUBLIC_DISABLE_SIGNUP') !== 'true' && (
|
||||||
<p className="text-muted-foreground mt-6 text-center text-sm">
|
<p className="text-muted-foreground mt-6 text-center text-sm">
|
||||||
<Trans>
|
<Trans>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { env } from '@documenso/lib/utils/env';
|
|||||||
|
|
||||||
import { SignUpForm } from '~/components/forms/signup';
|
import { SignUpForm } from '~/components/forms/signup';
|
||||||
|
|
||||||
|
import type { Route } from './+types/signup';
|
||||||
|
|
||||||
export function meta() {
|
export function meta() {
|
||||||
return [{ title: 'Sign Up' }];
|
return [{ title: 'Sign Up' }];
|
||||||
}
|
}
|
||||||
@ -12,17 +14,28 @@ export function meta() {
|
|||||||
export function loader() {
|
export function loader() {
|
||||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
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') {
|
if (NEXT_PUBLIC_DISABLE_SIGNUP === 'true') {
|
||||||
throw redirect('/signin');
|
throw redirect('/signin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isGoogleSSOEnabled,
|
||||||
|
isOIDCSSOEnabled,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SignUp() {
|
export default function SignUp({ loaderData }: Route.ComponentProps) {
|
||||||
|
const { isGoogleSSOEnabled, isOIDCSSOEnabled } = loaderData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SignUpForm
|
<SignUpForm
|
||||||
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
|
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
|
||||||
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
isGoogleSSOEnabled={isGoogleSSOEnabled}
|
||||||
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
isOIDCSSOEnabled={isOIDCSSOEnabled}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,22 @@ import { Outlet, isRouteErrorResponse, useRouteError } from 'react-router';
|
|||||||
import { EmbedAuthenticationRequired } from '~/components/embed/embed-authentication-required';
|
import { EmbedAuthenticationRequired } from '~/components/embed/embed-authentication-required';
|
||||||
import { EmbedPaywall } from '~/components/embed/embed-paywall';
|
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() {
|
export default function Layout() {
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,12 @@
|
|||||||
"build": "sh .bin/build.sh",
|
"build": "sh .bin/build.sh",
|
||||||
"build:app": "cross-env NODE_ENV=production react-router build",
|
"build:app": "cross-env NODE_ENV=production react-router build",
|
||||||
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
|
"build:server": "cross-env NODE_ENV=production rollup -c rollup.config.mjs",
|
||||||
"dev": "react-router dev",
|
"dev": "npm run with:env -- react-router dev",
|
||||||
"start": "cross-env NODE_ENV=production node build/server/main.js",
|
"start": "npm run with:env -- cross-env NODE_ENV=production node build/server/main.js",
|
||||||
"clean": "rimraf .react-router && rimraf node_modules",
|
"clean": "rimraf .react-router && rimraf node_modules",
|
||||||
"typecheck": "react-router typegen && tsc",
|
"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": {
|
"dependencies": {
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
|
|||||||
@ -2,21 +2,18 @@ import { lingui } from '@lingui/vite-plugin';
|
|||||||
import { reactRouter } from '@react-router/dev/vite';
|
import { reactRouter } from '@react-router/dev/vite';
|
||||||
import autoprefixer from 'autoprefixer';
|
import autoprefixer from 'autoprefixer';
|
||||||
import serverAdapter from 'hono-react-router-adapter/vite';
|
import serverAdapter from 'hono-react-router-adapter/vite';
|
||||||
import path from 'path';
|
|
||||||
import tailwindcss from 'tailwindcss';
|
import tailwindcss from 'tailwindcss';
|
||||||
import { defineConfig, loadEnv } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import macrosPlugin from 'vite-plugin-babel-macros';
|
import macrosPlugin from 'vite-plugin-babel-macros';
|
||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
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({
|
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: {
|
css: {
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: [tailwindcss, autoprefixer],
|
plugins: [tailwindcss, autoprefixer],
|
||||||
@ -37,18 +34,12 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
ssr: {
|
ssr: {
|
||||||
noExternal: ['react-dropzone', 'plausible-tracker', 'pdfjs-dist'],
|
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: {
|
optimizeDeps: {
|
||||||
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
|
entries: ['./app/**/*', '../../packages/ui/**/*', '../../packages/lib/**/*'],
|
||||||
include: ['prop-types', 'file-selector', 'attr-accept'],
|
include: ['prop-types', 'file-selector', 'attr-accept'],
|
||||||
exclude: [
|
exclude: ['node_modules', '@node-rs/bcrypt', '@documenso/pdf-sign', 'sharp'],
|
||||||
'node_modules',
|
|
||||||
'@node-rs/bcrypt',
|
|
||||||
'@node-rs/bcrypt-wasm32-wasi',
|
|
||||||
'@documenso/pdf-sign',
|
|
||||||
'sharp',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@ -9,10 +9,11 @@ const config: LinguiConfig = {
|
|||||||
catalogs: [
|
catalogs: [
|
||||||
{
|
{
|
||||||
path: '<rootDir>/packages/lib/translations/{locale}/web',
|
path: '<rootDir>/packages/lib/translations/{locale}/web',
|
||||||
include: ['apps/remix/src', 'packages/ui', 'packages/lib', 'packages/email'],
|
include: ['apps/remix/app', 'packages/ui', 'packages/lib', 'packages/email'],
|
||||||
exclude: ['**/node_modules/**'],
|
exclude: ['**/node_modules/**'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
compileNamespace: 'es',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
2185
package-lock.json
generated
2185
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@
|
|||||||
"@documenso/prisma": "*",
|
"@documenso/prisma": "*",
|
||||||
"@ts-rest/core": "^3.30.5",
|
"@ts-rest/core": "^3.30.5",
|
||||||
"@ts-rest/open-api": "^3.33.0",
|
"@ts-rest/open-api": "^3.33.0",
|
||||||
"@ts-rest/serverless": "^3.51.0",
|
"@ts-rest/serverless": "^3.30.5",
|
||||||
"@types/swagger-ui-react": "^4.18.3",
|
"@types/swagger-ui-react": "^4.18.3",
|
||||||
"luxon": "^3.4.0",
|
"luxon": "^3.4.0",
|
||||||
"superjson": "^1.13.1",
|
"superjson": "^1.13.1",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type { AuthAppType } from '../server';
|
|||||||
import { handleSignInRedirect } from '../server/lib/utils/redirect';
|
import { handleSignInRedirect } from '../server/lib/utils/redirect';
|
||||||
import type {
|
import type {
|
||||||
TForgotPasswordSchema,
|
TForgotPasswordSchema,
|
||||||
|
TResendVerifyEmailSchema,
|
||||||
TResetPasswordSchema,
|
TResetPasswordSchema,
|
||||||
TSignUpSchema,
|
TSignUpSchema,
|
||||||
TUpdatePasswordSchema,
|
TUpdatePasswordSchema,
|
||||||
@ -79,6 +80,13 @@ export class AuthClient {
|
|||||||
await this.handleError(response);
|
await this.handleError(response);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resendVerifyEmail: async (data: TResendVerifyEmailSchema) => {
|
||||||
|
const response = await this.client['email-password']['resend-verify-email'].$post({
|
||||||
|
json: data,
|
||||||
|
});
|
||||||
|
await this.handleError(response);
|
||||||
|
},
|
||||||
|
|
||||||
verifyEmail: async (data: TVerifyEmailSchema) => {
|
verifyEmail: async (data: TVerifyEmailSchema) => {
|
||||||
const response = await this.client['email-password']['verify-email'].$post({ json: data });
|
const response = await this.client['email-password']['verify-email'].$post({ json: data });
|
||||||
await this.handleError(response);
|
await this.handleError(response);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import { getRequiredSession, getSession } from '../lib/utils/get-session';
|
|||||||
import type { HonoAuthContext } from '../types/context';
|
import type { HonoAuthContext } from '../types/context';
|
||||||
import {
|
import {
|
||||||
ZForgotPasswordSchema,
|
ZForgotPasswordSchema,
|
||||||
|
ZResendVerifyEmailSchema,
|
||||||
ZResetPasswordSchema,
|
ZResetPasswordSchema,
|
||||||
ZSignInSchema,
|
ZSignInSchema,
|
||||||
ZSignUpSchema,
|
ZSignUpSchema,
|
||||||
@ -196,17 +197,17 @@ export const emailPasswordRoute = new Hono<HonoAuthContext>()
|
|||||||
/**
|
/**
|
||||||
* Resend verification email endpoint.
|
* Resend verification email endpoint.
|
||||||
*/
|
*/
|
||||||
.post('/resend-email', zValidator('json', ZVerifyEmailSchema), async (c) => {
|
.post('/resend-verify-email', zValidator('json', ZResendVerifyEmailSchema), async (c) => {
|
||||||
const { state, userId } = await verifyEmail({ token: c.req.valid('json').token });
|
const { email } = c.req.valid('json');
|
||||||
|
|
||||||
// If email is verified, automatically authenticate user.
|
await jobsClient.triggerJob({
|
||||||
if (state === EMAIL_VERIFICATION_STATE.VERIFIED && userId !== null) {
|
name: 'send.signup.confirmation.email',
|
||||||
await onAuthorize({ userId }, c);
|
payload: {
|
||||||
}
|
email,
|
||||||
|
},
|
||||||
return c.json({
|
|
||||||
state,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return c.text('OK', 201);
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Forgot password endpoint.
|
* Forgot password endpoint.
|
||||||
|
|||||||
@ -68,6 +68,12 @@ export const ZVerifyEmailSchema = z.object({
|
|||||||
|
|
||||||
export type TVerifyEmailSchema = z.infer<typeof ZVerifyEmailSchema>;
|
export type TVerifyEmailSchema = z.infer<typeof ZVerifyEmailSchema>;
|
||||||
|
|
||||||
|
export const ZResendVerifyEmailSchema = z.object({
|
||||||
|
email: z.string().email().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TResendVerifyEmailSchema = z.infer<typeof ZResendVerifyEmailSchema>;
|
||||||
|
|
||||||
export const ZUpdatePasswordSchema = z.object({
|
export const ZUpdatePasswordSchema = z.object({
|
||||||
currentPassword: ZCurrentPasswordSchema,
|
currentPassword: ZCurrentPasswordSchema,
|
||||||
password: ZPasswordSchema,
|
password: ZPasswordSchema,
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import type { Transporter } from 'nodemailer';
|
|||||||
import { createTransport } from 'nodemailer';
|
import { createTransport } from 'nodemailer';
|
||||||
|
|
||||||
import { env } from '@documenso/lib/utils/env';
|
import { env } from '@documenso/lib/utils/env';
|
||||||
|
import { ResendTransport } from '@documenso/nodemailer-resend';
|
||||||
|
|
||||||
// import { ResendTransport } from '@documenso/nodemailer-resend';
|
|
||||||
import { MailChannelsTransport } from './transports/mailchannels';
|
import { MailChannelsTransport } from './transports/mailchannels';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,14 +63,13 @@ const getTransport = (): Transporter => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Todo
|
if (transport === 'resend') {
|
||||||
// if (transport === 'resend') {
|
return createTransport(
|
||||||
// return createTransport(
|
ResendTransport.makeTransport({
|
||||||
// ResendTransport.makeTransport({
|
apiKey: env('NEXT_PRIVATE_RESEND_API_KEY') || '',
|
||||||
// apiKey: env('NEXT_PRIVATE_RESEND_API_KEY') || '',
|
}),
|
||||||
// }),
|
);
|
||||||
// );
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if (transport === 'smtp-api') {
|
if (transport === 'smtp-api') {
|
||||||
if (!env('NEXT_PRIVATE_SMTP_HOST') || !env('NEXT_PRIVATE_SMTP_APIKEY')) {
|
if (!env('NEXT_PRIVATE_SMTP_HOST') || !env('NEXT_PRIVATE_SMTP_APIKEY')) {
|
||||||
|
|||||||
@ -35,11 +35,9 @@
|
|||||||
"@react-email/section": "0.0.10",
|
"@react-email/section": "0.0.10",
|
||||||
"@react-email/tailwind": "0.0.9",
|
"@react-email/tailwind": "0.0.9",
|
||||||
"@react-email/text": "0.0.6",
|
"@react-email/text": "0.0.6",
|
||||||
"@lingui/macro": "^5.2.0",
|
"nodemailer": "6.9.9",
|
||||||
"@lingui/react": "^5.2.0",
|
"react-email": "1.9.5",
|
||||||
"nodemailer": "^6.9.9",
|
"resend": "2.0.0"
|
||||||
"react-email": "^1.9.5",
|
|
||||||
"resend": "^2.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@documenso/tailwind-config": "*",
|
"@documenso/tailwind-config": "*",
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import * as ReactEmail from '@react-email/render';
|
import * as ReactEmail from '@react-email/render';
|
||||||
|
|
||||||
|
import config from '@documenso/tailwind-config';
|
||||||
|
|
||||||
import { Tailwind } from './components';
|
import { Tailwind } from './components';
|
||||||
import { BrandingProvider, type BrandingSettings } from './providers/branding';
|
import { BrandingProvider, type BrandingSettings } from './providers/branding';
|
||||||
|
|
||||||
// Todo:
|
|
||||||
// import config from '@documenso/tailwind-config';
|
|
||||||
|
|
||||||
export type RenderOptions = ReactEmail.Options & {
|
export type RenderOptions = ReactEmail.Options & {
|
||||||
branding?: BrandingSettings;
|
branding?: BrandingSettings;
|
||||||
};
|
};
|
||||||
@ -18,7 +17,7 @@ export const render = (element: React.ReactNode, options?: RenderOptions) => {
|
|||||||
config={{
|
config={{
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
// colors: config.theme.extend.colors,
|
colors: config.theme.extend.colors,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -37,7 +36,7 @@ export const renderAsync = async (element: React.ReactNode, options?: RenderOpti
|
|||||||
config={{
|
config={{
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
// colors: config.theme.extend.colors,
|
colors: config.theme.extend.colors,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ type SupportedLanguages = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
|||||||
export async function loadCatalog(lang: SupportedLanguages): Promise<{
|
export async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||||
[k: string]: Messages;
|
[k: string]: Messages;
|
||||||
}> {
|
}> {
|
||||||
const extension = env('NODE_ENV') === 'development' ? 'po' : 'js';
|
const extension = env('NODE_ENV') === 'development' ? 'po' : 'mjs';
|
||||||
|
|
||||||
const { messages } = await import(`../../translations/${lang}/web.${extension}`);
|
const { messages } = await import(`../../translations/${lang}/web.${extension}`);
|
||||||
|
|
||||||
|
|||||||
1
packages/lib/translations/.gitignore
vendored
1
packages/lib/translations/.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
|
|
||||||
# Compiled translations.
|
# Compiled translations.
|
||||||
*.js
|
*.js
|
||||||
|
*.mjs
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
/// <reference types="@documenso/tsconfig/process-env.d.ts" />
|
/// <reference types="@documenso/tsconfig/process-env.d.ts" />
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__ENV__?: Record<string, string | undefined>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type EnvironmentVariable = keyof NodeJS.ProcessEnv;
|
type EnvironmentVariable = keyof NodeJS.ProcessEnv;
|
||||||
|
|
||||||
export const env = (variable: EnvironmentVariable | (string & object)): string | undefined => {
|
export const env = (variable: EnvironmentVariable | (string & object)): string | undefined => {
|
||||||
// console.log({
|
|
||||||
// ['typeof window']: typeof window,
|
|
||||||
// ['process.env']: process.env,
|
|
||||||
// ['window.__ENV__']: typeof window !== 'undefined' && window.__ENV__,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// This may need ot be import.meta.env.SSR depending on vite.
|
|
||||||
if (typeof window !== 'undefined' && typeof window.__ENV__ === 'object') {
|
if (typeof window !== 'undefined' && typeof window.__ENV__ === 'object') {
|
||||||
return window.__ENV__[variable];
|
return window.__ENV__[variable];
|
||||||
}
|
}
|
||||||
|
|
||||||
return process.env[variable];
|
return process?.env?.[variable];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Todo: Test
|
||||||
|
export const createPublicEnv = () =>
|
||||||
|
Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_PUBLIC_')));
|
||||||
|
|||||||
@ -3,13 +3,11 @@ import { i18n } from '@lingui/core';
|
|||||||
|
|
||||||
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
||||||
import { APP_I18N_OPTIONS } from '../constants/i18n';
|
import { APP_I18N_OPTIONS } from '../constants/i18n';
|
||||||
|
import { env } from './env';
|
||||||
|
|
||||||
export async function dynamicActivate(locale: string) {
|
export async function dynamicActivate(locale: string) {
|
||||||
// const extension = import.meta.env.PROD env('NODE_ENV') === 'development' ? 'po' : 'js';
|
const extension = env('NODE_ENV') === 'development' ? 'po' : 'mjs';
|
||||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
||||||
const extension = import.meta.env.PROD ? 'js' : 'po';
|
|
||||||
|
|
||||||
// Todo: Use extension (currently breaks).
|
|
||||||
const { messages } = await import(`../translations/${locale}/web.${extension}`);
|
const { messages } = await import(`../translations/${locale}/web.${extension}`);
|
||||||
|
|
||||||
i18n.loadAndActivate({ locale, messages });
|
i18n.loadAndActivate({ locale, messages });
|
||||||
|
|||||||
Reference in New Issue
Block a user