From 15922d447b69f62f0fecb211f713744eda2b632d Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Wed, 12 Feb 2025 16:41:35 +1100 Subject: [PATCH] fix: wip --- ...y-dialog.tsx => passkey-create-dialog.tsx} | 4 +- .../dialogs/template-create-dialog.tsx | 24 +- .../dialogs/template-use-dialog.tsx | 18 +- apps/remix/app/components/forms/password.tsx | 4 +- apps/remix/app/components/forms/signin.tsx | 6 +- .../forms/team-branding-preferences-form.tsx | 29 +- .../app/components/general/app-header.tsx | 4 +- .../document-signing-auth-2fa.tsx | 14 +- .../document-signing-auth-account.tsx | 2 +- .../document-signing-auth-page.tsx | 2 +- .../document-signing-auth-passkey.tsx | 4 +- .../document-signing-auth-provider.tsx | 7 +- .../general/document/document-status.tsx | 2 +- .../general/document/document-upload.tsx | 24 +- .../app/components/general/menu-switcher.tsx | 4 +- .../general/upcoming-profile-claim-teaser.tsx | 55 ---- .../general/verify-email-banner.tsx | 18 +- .../documents-table-action-dropdown.tsx | 2 +- apps/remix/app/root.tsx | 6 + .../_authenticated+/admin+/subscriptions.tsx | 2 +- .../_authenticated+/admin+/users._index.tsx | 2 +- .../_authenticated+/documents+/$id._index.tsx | 3 +- .../settings+/public-profile+/index.tsx | 2 +- .../settings+/security+/index.tsx | 34 ++- .../settings+/security+/passkeys+/index.tsx | 4 +- .../routes/_recipient+/d.$token+/_index.tsx | 4 +- apps/remix/package.json | 8 +- apps/remix/rollup.config.mjs | 6 +- apps/remix/server/api/files.ts | 110 +++++++ apps/remix/server/api/files.types.ts | 38 +++ apps/remix/server/middleware.ts | 110 +++---- apps/remix/server/router.ts | 85 +----- apps/remix/tsconfig.json | 3 +- package-lock.json | 284 ++++++++++++++---- packages/api/v1/implementation.ts | 12 +- packages/auth/package.json | 4 +- packages/auth/server/lib/session/session.ts | 57 ++-- packages/auth/server/routes/email-password.ts | 23 +- packages/auth/server/routes/google.ts | 4 +- packages/auth/server/routes/passkey.ts | 6 +- packages/email/ambient.d.ts | 1 - packages/email/render.tsx | 7 +- packages/email/templates/document-invite.tsx | 2 +- .../lib/client-only/providers/session.tsx | 5 +- .../emails/send-signing-email.handler.ts | 4 +- .../internal/seal-document.handler.ts | 8 +- packages/lib/package.json | 2 +- packages/lib/server-only/2fa/disable-2fa.ts | 5 +- .../document/create-document-v2.ts | 8 +- .../server-only/document/create-document.ts | 8 +- .../lib/server-only/document/seal-document.ts | 8 +- .../document/send-completed-email.ts | 4 +- .../server-only/document/send-document.tsx | 8 +- .../get-template-by-direct-link-token.ts | 12 +- .../lib/universal/upload/get-file.server.ts | 50 +++ packages/lib/universal/upload/get-file.ts | 18 +- .../lib/universal/upload/put-file.server.ts | 85 ++++++ packages/lib/universal/upload/put-file.ts | 60 ++-- .../lib/universal/upload/server-actions.ts | 47 +-- packages/prisma/package.json | 2 +- packages/tailwind-config/index.d.ts | 4 + packages/tailwind-config/package.json | 3 +- packages/trpc/server/context.ts | 5 +- .../primitives/document-flow/add-fields.tsx | 16 +- .../advanced-fields/checkbox.tsx | 6 +- .../document-flow/advanced-fields/radio.tsx | 6 +- packages/ui/primitives/password-input.tsx | 3 +- packages/ui/primitives/signature-pad/point.ts | 2 +- .../signature-pad/signature-pad.tsx | 11 +- .../template-flow/add-template-fields.tsx | 10 +- 70 files changed, 889 insertions(+), 551 deletions(-) rename apps/remix/app/components/dialogs/{create-passkey-dialog.tsx => passkey-create-dialog.tsx} (98%) delete mode 100644 apps/remix/app/components/general/upcoming-profile-claim-teaser.tsx create mode 100644 apps/remix/server/api/files.ts create mode 100644 apps/remix/server/api/files.types.ts delete mode 100644 packages/email/ambient.d.ts create mode 100644 packages/lib/universal/upload/get-file.server.ts create mode 100644 packages/lib/universal/upload/put-file.server.ts create mode 100644 packages/tailwind-config/index.d.ts diff --git a/apps/remix/app/components/dialogs/create-passkey-dialog.tsx b/apps/remix/app/components/dialogs/passkey-create-dialog.tsx similarity index 98% rename from apps/remix/app/components/dialogs/create-passkey-dialog.tsx rename to apps/remix/app/components/dialogs/passkey-create-dialog.tsx index d454ac834..6a21895d3 100644 --- a/apps/remix/app/components/dialogs/create-passkey-dialog.tsx +++ b/apps/remix/app/components/dialogs/passkey-create-dialog.tsx @@ -37,7 +37,7 @@ import { import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; -export type CreatePasskeyDialogProps = { +export type PasskeyCreateDialogProps = { trigger?: React.ReactNode; onSuccess?: () => void; } & Omit; @@ -50,7 +50,7 @@ type TCreatePasskeyFormSchema = z.infer; const parser = new UAParser(); -export const CreatePasskeyDialog = ({ trigger, onSuccess, ...props }: CreatePasskeyDialogProps) => { +export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCreateDialogProps) => { const [open, setOpen] = useState(false); const [formError, setFormError] = useState(null); diff --git a/apps/remix/app/components/dialogs/template-create-dialog.tsx b/apps/remix/app/components/dialogs/template-create-dialog.tsx index 01f7a573e..85038ba3d 100644 --- a/apps/remix/app/components/dialogs/template-create-dialog.tsx +++ b/apps/remix/app/components/dialogs/template-create-dialog.tsx @@ -7,7 +7,7 @@ import { FilePlus, Loader } from 'lucide-react'; import { useNavigate } from 'react-router'; import { useSession } from '@documenso/lib/client-only/providers/session'; -import { AppError } from '@documenso/lib/errors/app-error'; +import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -48,27 +48,7 @@ export const TemplateCreateDialog = ({ templateRootPath }: TemplateCreateDialogP setIsUploadingFile(true); try { - // Todo - // const { type, data } = await putPdfFile(file); - - const formData = new FormData(); - formData.append('file', file); - - const response = await fetch('/api/file', { - method: 'POST', - body: formData, - }) - .then(async (res) => await res.json()) - .catch((e) => { - console.error('Upload failed:', e); - throw new AppError('UPLOAD_FAILED'); - }); - - // Why do we run this twice? - // const { id: templateDocumentDataId } = await createDocumentData({ - // type: response.type, - // data: response.data, - // }); + const response = await putPdfFile(file); const { id } = await createTemplate({ title: file.name, diff --git a/apps/remix/app/components/dialogs/template-use-dialog.tsx b/apps/remix/app/components/dialogs/template-use-dialog.tsx index 8225afc84..2f8266ef1 100644 --- a/apps/remix/app/components/dialogs/template-use-dialog.tsx +++ b/apps/remix/app/components/dialogs/template-use-dialog.tsx @@ -17,6 +17,7 @@ import { TEMPLATE_RECIPIENT_NAME_PLACEHOLDER_REGEX, } from '@documenso/lib/constants/template'; import { AppError } from '@documenso/lib/errors/app-error'; +import { putPdfFile } from '@documenso/lib/universal/upload/put-file'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -150,22 +151,7 @@ export function TemplateUseDialog({ let customDocumentDataId: string | undefined = undefined; if (data.useCustomDocument && data.customDocumentData) { - // const customDocumentData = await putPdfFile(data.customDocumentData); - // Todo - - const formData = new FormData(); - formData.append('file', data.customDocumentData); - - const customDocumentData = await fetch('/api/file', { - method: 'POST', - body: formData, - }) - .then(async (res) => await res.json()) - .catch((e) => { - console.error('Upload failed:', e); - throw new AppError('UPLOAD_FAILED'); - }); - + const customDocumentData = await putPdfFile(data.customDocumentData); customDocumentDataId = customDocumentData.id; } diff --git a/apps/remix/app/components/forms/password.tsx b/apps/remix/app/components/forms/password.tsx index 23ae901ab..d397cdcaf 100644 --- a/apps/remix/app/components/forms/password.tsx +++ b/apps/remix/app/components/forms/password.tsx @@ -2,12 +2,12 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { msg } from '@lingui/core/macro'; import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; -import type { User } from '@prisma/client'; import { useForm } from 'react-hook-form'; import { match } from 'ts-pattern'; import { z } from 'zod'; import { authClient } from '@documenso/auth/client'; +import type { SessionUser } from '@documenso/auth/server/lib/session/session'; import { AppError } from '@documenso/lib/errors/app-error'; import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema'; import { cn } from '@documenso/ui/lib/utils'; @@ -38,7 +38,7 @@ export type TPasswordFormSchema = z.infer; export type PasswordFormProps = { className?: string; - user: User; + user: SessionUser; }; export const PasswordForm = ({ className }: PasswordFormProps) => { diff --git a/apps/remix/app/components/forms/signin.tsx b/apps/remix/app/components/forms/signin.tsx index 143455bd1..9154aeb92 100644 --- a/apps/remix/app/components/forms/signin.tsx +++ b/apps/remix/app/components/forms/signin.tsx @@ -97,7 +97,7 @@ export const SignInForm = ({ const [isPasskeyLoading, setIsPasskeyLoading] = useState(false); - const redirectUrl = useMemo(() => { + const redirectPath = useMemo(() => { // Handle SSR if (typeof window === 'undefined') { return LOGIN_REDIRECT_PATH; @@ -171,7 +171,7 @@ export const SignInForm = ({ await authClient.passkey.signIn({ credential: JSON.stringify(credential), csrfToken: sessionId, - redirectUrl, + redirectPath, }); } catch (err) { setIsPasskeyLoading(false); @@ -211,7 +211,7 @@ export const SignInForm = ({ password, totpCode, backupCode, - redirectUrl, + redirectPath, }); } catch (err) { console.log(err); diff --git a/apps/remix/app/components/forms/team-branding-preferences-form.tsx b/apps/remix/app/components/forms/team-branding-preferences-form.tsx index 8136314c4..f33345d3b 100644 --- a/apps/remix/app/components/forms/team-branding-preferences-form.tsx +++ b/apps/remix/app/components/forms/team-branding-preferences-form.tsx @@ -9,6 +9,8 @@ import { Loader } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; +import { getFile } from '@documenso/lib/universal/upload/get-file'; +import { putFile } from '@documenso/lib/universal/upload/put-file'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -78,8 +80,7 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref let uploadedBrandingLogo = settings?.brandingLogo; if (brandingLogo) { - // Todo - // uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo)); + uploadedBrandingLogo = JSON.stringify(await putFile(brandingLogo)); } if (brandingLogo === null) { @@ -116,26 +117,12 @@ export function TeamBrandingPreferencesForm({ team, settings }: TeamBrandingPref const file = JSON.parse(settings.brandingLogo); if ('type' in file && 'data' in file) { - // Todo - // Todo - // Todo - void fetch(`/api/file?key=${file.key}`, { - method: 'GET', - }) - .then(async (res) => await res.json()) - .then((data) => { - const objectUrl = URL.createObjectURL(new Blob([data.binaryData])); + void getFile(file).then((binaryData) => { + const objectUrl = URL.createObjectURL(new Blob([binaryData])); - setPreviewUrl(objectUrl); - setHasLoadedPreview(true); - }); - - // void getFile(file).then((binaryData) => { - // const objectUrl = URL.createObjectURL(new Blob([binaryData])); - - // setPreviewUrl(objectUrl); - // setHasLoadedPreview(true); - // }); + setPreviewUrl(objectUrl); + setHasLoadedPreview(true); + }); return; } diff --git a/apps/remix/app/components/general/app-header.tsx b/apps/remix/app/components/general/app-header.tsx index edc2d1f6b..4d8361e69 100644 --- a/apps/remix/app/components/general/app-header.tsx +++ b/apps/remix/app/components/general/app-header.tsx @@ -1,9 +1,9 @@ import { type HTMLAttributes, useEffect, useState } from 'react'; -import type { User } from '@prisma/client'; import { MenuIcon, SearchIcon } from 'lucide-react'; import { Link, useLocation, useParams } from 'react-router'; +import type { SessionUser } from '@documenso/auth/server/lib/session/session'; import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams'; import { getRootHref } from '@documenso/lib/utils/params'; import { cn } from '@documenso/ui/lib/utils'; @@ -16,7 +16,7 @@ import { AppNavMobile } from './app-nav-mobile'; import { MenuSwitcher } from './menu-switcher'; export type HeaderProps = HTMLAttributes & { - user: User; + user: SessionUser; teams: TGetTeamsResponse; }; diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx index 4f8be2bba..e0b5ca2b1 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx @@ -109,14 +109,12 @@ export const DocumentSigningAuth2FA = ({ )}

- {user?.identityProvider === 'DOCUMENSO' && ( -

- - By enabling 2FA, you will be required to enter a code from your authenticator app - every time you sign in. - -

- )} +

+ + By enabling 2FA, you will be required to enter a code from your authenticator app + every time you sign in using email password. + +

diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx index 9f93144d1..b2877f2e6 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx @@ -35,7 +35,7 @@ export const DocumentSigningAuthAccount = ({ setIsSigningOut(true); await authClient.signOut({ - redirectUrl: `/signin#email=${email}`, + redirectPath: `/signin#email=${email}`, }); } catch { setIsSigningOut(false); diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx index 98fcb8b9a..21b1be6ca 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx @@ -27,7 +27,7 @@ export const DocumentSigningAuthPageView = ({ setIsSigningOut(true); await authClient.signOut({ - redirectUrl: emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`, + redirectPath: emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`, }); } catch { toast({ diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx index 523ef9c8e..22e641713 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx @@ -32,7 +32,7 @@ import { SelectValue, } from '@documenso/ui/primitives/select'; -import { CreatePasskeyDialog } from '~/components/dialogs/create-passkey-dialog'; +import { PasskeyCreateDialog } from '~/components/dialogs/passkey-create-dialog'; import { useRequiredDocumentSigningAuthContext } from './document-signing-auth-provider'; @@ -190,7 +190,7 @@ export const DocumentSigningAuthPasskey = ({ Cancel - refetchPasskeys()} trigger={