diff --git a/apps/remix/app/components/forms/password.tsx b/apps/remix/app/components/forms/password.tsx
index eb02e2a20..86dc3cd4b 100644
--- a/apps/remix/app/components/forms/password.tsx
+++ b/apps/remix/app/components/forms/password.tsx
@@ -6,8 +6,8 @@ import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
+import { authClient } from '@documenso/auth/client';
import { AppError } from '@documenso/lib/errors/app-error';
-import { trpc } from '@documenso/trpc/react';
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -55,11 +55,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
const isSubmitting = form.formState.isSubmitting;
- const { mutateAsync: updatePassword } = trpc.profile.updatePassword.useMutation();
-
const onFormSubmit = async ({ currentPassword, password }: TPasswordFormSchema) => {
try {
- await updatePassword({
+ await authClient.updatePassword({
currentPassword,
password,
});
diff --git a/apps/remix/app/components/forms/public-profile-claim-dialog.tsx b/apps/remix/app/components/forms/public-profile-claim-dialog.tsx
index 57002c78b..308e8a866 100644
--- a/apps/remix/app/components/forms/public-profile-claim-dialog.tsx
+++ b/apps/remix/app/components/forms/public-profile-claim-dialog.tsx
@@ -88,12 +88,12 @@ export const ClaimPublicProfileDialogForm = ({
} catch (err) {
const error = AppError.parseError(err);
- if (error.code === AppErrorCode.PROFILE_URL_TAKEN) {
+ if (error.code === 'PROFILE_URL_TAKEN') {
form.setError('url', {
type: 'manual',
message: _(msg`This username is already taken`),
});
- } else if (error.code === AppErrorCode.PREMIUM_PROFILE_URL) {
+ } else if (error.code === 'PREMIUM_PROFILE_URL') {
form.setError('url', {
type: 'manual',
message: error.message,
diff --git a/apps/remix/app/components/forms/public-profile-form.tsx b/apps/remix/app/components/forms/public-profile-form.tsx
index 9bb6e7521..1cec987cf 100644
--- a/apps/remix/app/components/forms/public-profile-form.tsx
+++ b/apps/remix/app/components/forms/public-profile-form.tsx
@@ -11,7 +11,7 @@ import { useForm } from 'react-hook-form';
import type { z } from 'zod';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
-import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
+import { AppError } from '@documenso/lib/errors/app-error';
import { formatUserProfilePath } from '@documenso/lib/utils/public-profiles';
import {
MAX_PROFILE_BIO_LENGTH,
@@ -88,8 +88,8 @@ export const PublicProfileForm = ({
const error = AppError.parseError(err);
switch (error.code) {
- case AppErrorCode.PREMIUM_PROFILE_URL:
- case AppErrorCode.PROFILE_URL_TAKEN:
+ case 'PREMIUM_PROFILE_URL':
+ case 'PROFILE_URL_TAKEN':
form.setError('url', {
type: 'manual',
message: error.message,
diff --git a/apps/remix/app/components/forms/signin.tsx b/apps/remix/app/components/forms/signin.tsx
index 0762c57d4..adff07e02 100644
--- a/apps/remix/app/components/forms/signin.tsx
+++ b/apps/remix/app/components/forms/signin.tsx
@@ -15,7 +15,6 @@ import { z } from 'zod';
import { authClient } from '@documenso/auth/client';
import { AuthenticationErrorCode } from '@documenso/auth/server/lib/errors/error-codes';
import { AppError } from '@documenso/lib/errors/app-error';
-import { ErrorCode } from '@documenso/lib/next-auth/error-codes';
import { trpc } from '@documenso/trpc/react';
import { ZCurrentPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
import { cn } from '@documenso/ui/lib/utils';
@@ -46,8 +45,6 @@ const CommonErrorMessages = {
[AuthenticationErrorCode.AccountDisabled]: msg`This account has been disabled. Please contact support.`,
};
-const TwoFactorEnabledErrorCode = ErrorCode.TWO_FACTOR_MISSING_CREDENTIALS;
-
const LOGIN_REDIRECT_PATH = '/documents';
export const ZSignInFormSchema = z.object({
@@ -90,7 +87,7 @@ export const SignInForm = ({
const [isPasskeyLoading, setIsPasskeyLoading] = useState(false);
- const callbackUrl = useMemo(() => {
+ const redirectUrl = useMemo(() => {
// Handle SSR
if (typeof window === 'undefined') {
return LOGIN_REDIRECT_PATH;
@@ -161,15 +158,16 @@ export const SignInForm = ({
const credential = await startAuthentication(options);
- const result = await authClient.passkey.signIn({
+ await authClient.passkey.signIn({
credential: JSON.stringify(credential),
csrfToken: sessionId,
+ redirectUrl,
// callbackUrl,
// redirect: false,
});
// Todo: Can't use navigate because of embed?
- window.location.href = callbackUrl;
+ // window.location.href = callbackUrl;
} catch (err) {
setIsPasskeyLoading(false);
@@ -208,17 +206,18 @@ export const SignInForm = ({
password,
totpCode,
backupCode,
+ redirectUrl,
// callbackUrl,
// redirect: false,
});
- window.location.href = callbackUrl;
+ // window.location.href = callbackUrl; // Todo: Handle redirect.
} catch (err) {
console.log(err);
const error = AppError.parseError(err);
- if (error.code === TwoFactorEnabledErrorCode) {
+ if (error.code === 'TWO_FACTOR_MISSING_CREDENTIALS') {
setIsTwoFactorAuthenticationDialogOpen(true);
return;
}
@@ -257,12 +256,7 @@ export const SignInForm = ({
const onSignInWithGoogleClick = async () => {
try {
- // await signIn('google', {
- // callbackUrl,
- // });
-
- const result = await authClient.google.signIn();
- console.log(result);
+ await authClient.google.signIn(); // Todo: Handle redirect.
} catch (err) {
toast({
title: _(msg`An unknown error occurred`),
diff --git a/apps/remix/app/components/forms/signup.tsx b/apps/remix/app/components/forms/signup.tsx
index de822c39b..17b63047b 100644
--- a/apps/remix/app/components/forms/signup.tsx
+++ b/apps/remix/app/components/forms/signup.tsx
@@ -12,9 +12,9 @@ import { Link, useNavigate, useSearchParams } from 'react-router';
import { z } from 'zod';
import communityCardsImage from '@documenso/assets/images/community-cards.png';
+import { authClient } from '@documenso/auth/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
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';
@@ -71,8 +71,8 @@ export const signupErrorMessages: Record
= {
SIGNUP_DISABLED: msg`Signups are disabled.`,
[AppErrorCode.ALREADY_EXISTS]: msg`User with this email already exists. Please use a different email address.`,
[AppErrorCode.INVALID_REQUEST]: msg`We were unable to create your account. Please review the information you provided and try again.`,
- [AppErrorCode.PROFILE_URL_TAKEN]: msg`This username has already been taken`,
- [AppErrorCode.PREMIUM_PROFILE_URL]: msg`Only subscribers can have a username shorter than 6 characters`,
+ PROFILE_URL_TAKEN: msg`This username has already been taken`,
+ PREMIUM_PROFILE_URL: msg`Only subscribers can have a username shorter than 6 characters`,
};
export type TSignUpFormSchema = z.infer;
@@ -93,7 +93,7 @@ export const SignUpForm = ({
const { _ } = useLingui();
const { toast } = useToast();
- // const analytics = useAnalytics();
+ // const analytics = useAnalytics(); // Todo
const navigate = useNavigate();
const [searchParams] = useSearchParams();
@@ -120,11 +120,9 @@ export const SignUpForm = ({
const name = form.watch('name');
const url = form.watch('url');
- const { mutateAsync: signup } = trpc.auth.signup.useMutation();
-
const onFormSubmit = async ({ name, email, password, signature, url }: TSignUpFormSchema) => {
try {
- await signup({ name, email, password, signature, url });
+ await authClient.emailPassword.signUp({ name, email, password, signature, url });
void navigate(`/unverified-account`);
@@ -146,10 +144,7 @@ export const SignUpForm = ({
const errorMessage = signupErrorMessages[error.code] ?? signupErrorMessages.INVALID_REQUEST;
- if (
- error.code === AppErrorCode.PROFILE_URL_TAKEN ||
- error.code === AppErrorCode.PREMIUM_PROFILE_URL
- ) {
+ if (error.code === 'PROFILE_URL_TAKEN' || error.code === 'PREMIUM_PROFILE_URL') {
form.setError('url', {
type: 'manual',
message: _(errorMessage),
@@ -175,8 +170,7 @@ export const SignUpForm = ({
const onSignUpWithGoogleClick = async () => {
try {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- // await signIn('google', { callbackUrl: SIGN_UP_REDIRECT_PATH });
+ await authClient.google.signIn();
} catch (err) {
toast({
title: _(msg`An unknown error occurred`),
diff --git a/apps/remix/app/components/hidden/leaderboard+/data-table-leaderboard.tsx b/apps/remix/app/components/hidden/leaderboard+/data-table-leaderboard.tsx
deleted file mode 100644
index 596f0051d..000000000
--- a/apps/remix/app/components/hidden/leaderboard+/data-table-leaderboard.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-'use client';
-
-import { useEffect, useMemo, useState, useTransition } from 'react';
-
-import { msg } from '@lingui/macro';
-import { useLingui } from '@lingui/react';
-import { ChevronDownIcon as CaretSortIcon, Loader } from 'lucide-react';
-
-import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
-import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
-import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
-import { DataTable } from '@documenso/ui/primitives/data-table';
-import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
-import { Input } from '@documenso/ui/primitives/input';
-
-export type SigningVolume = {
- id: number;
- name: string;
- signingVolume: number;
- createdAt: Date;
- planId: string;
-};
-
-type LeaderboardTableProps = {
- signingVolume: SigningVolume[];
- totalPages: number;
- perPage: number;
- page: number;
- sortBy: 'name' | 'createdAt' | 'signingVolume';
- sortOrder: 'asc' | 'desc';
-};
-
-export const LeaderboardTable = ({
- signingVolume,
- totalPages,
- perPage,
- page,
- sortBy,
- sortOrder,
-}: LeaderboardTableProps) => {
- const { _, i18n } = useLingui();
-
- const [isPending, startTransition] = useTransition();
- const updateSearchParams = useUpdateSearchParams();
- const [searchString, setSearchString] = useState('');
- const debouncedSearchString = useDebouncedValue(searchString, 1000);
-
- const columns = useMemo(() => {
- return [
- {
- header: () => (
- handleColumnSort('name')}
- >
- {_(msg`Name`)}
-
-
- ),
- accessorKey: 'name',
- cell: ({ row }) => {
- return (
-
- );
- },
- size: 250,
- },
- {
- header: () => (
- handleColumnSort('signingVolume')}
- >
- {_(msg`Signing Volume`)}
-
-
- ),
- accessorKey: 'signingVolume',
- cell: ({ row }) => {Number(row.getValue('signingVolume'))}
,
- },
- {
- header: () => {
- return (
- handleColumnSort('createdAt')}
- >
- {_(msg`Created`)}
-
-
- );
- },
- accessorKey: 'createdAt',
- cell: ({ row }) => i18n.date(row.original.createdAt),
- },
- ] satisfies DataTableColumnDef[];
- }, [sortOrder]);
-
- useEffect(() => {
- startTransition(() => {
- updateSearchParams({
- search: debouncedSearchString,
- page: 1,
- perPage,
- sortBy,
- sortOrder,
- });
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [debouncedSearchString]);
-
- const onPaginationChange = (page: number, perPage: number) => {
- startTransition(() => {
- updateSearchParams({
- page,
- perPage,
- });
- });
- };
-
- const handleChange = (e: React.ChangeEvent) => {
- setSearchString(e.target.value);
- };
-
- const handleColumnSort = (column: 'name' | 'createdAt' | 'signingVolume') => {
- startTransition(() => {
- updateSearchParams({
- sortBy: column,
- sortOrder: sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc',
- });
- });
- };
-
- return (
-
-
-
- {(table) => }
-
-
- {isPending && (
-
-
-
- )}
-
- );
-};
diff --git a/apps/remix/app/components/hidden/leaderboard+/fetch-leaderboard.actions.ts b/apps/remix/app/components/hidden/leaderboard+/fetch-leaderboard.actions.ts
deleted file mode 100644
index 42fc20c97..000000000
--- a/apps/remix/app/components/hidden/leaderboard+/fetch-leaderboard.actions.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-'use server';
-
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
-import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
-import { getSigningVolume } from '@documenso/lib/server-only/admin/get-signing-volume';
-
-type SearchOptions = {
- search: string;
- page: number;
- perPage: number;
- sortBy: 'name' | 'createdAt' | 'signingVolume';
- sortOrder: 'asc' | 'desc';
-};
-
-export async function search({ search, page, perPage, sortBy, sortOrder }: SearchOptions) {
- const { user } = await getRequiredServerComponentSession();
-
- if (!isAdmin(user)) {
- throw new Error('Unauthorized');
- }
-
- const results = await getSigningVolume({ search, page, perPage, sortBy, sortOrder });
-
- return results;
-}
diff --git a/apps/remix/app/components/hidden/leaderboard+/page.tsx b/apps/remix/app/components/hidden/leaderboard+/page.tsx
deleted file mode 100644
index 87a9c792c..000000000
--- a/apps/remix/app/components/hidden/leaderboard+/page.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Trans } from '@lingui/macro';
-
-import { LeaderboardTable } from './data-table-leaderboard';
-import { search } from './fetch-leaderboard.actions';
-
-type AdminLeaderboardProps = {
- searchParams?: {
- search?: string;
- page?: number;
- perPage?: number;
- sortBy?: 'name' | 'createdAt' | 'signingVolume';
- sortOrder?: 'asc' | 'desc';
- };
-};
-
-export default async function LeaderboardPage({ searchParams = {} }: AdminLeaderboardProps) {
- const page = Number(searchParams.page) || 1;
- const perPage = Number(searchParams.perPage) || 10;
- const searchString = searchParams.search || '';
- const sortBy = searchParams.sortBy || 'signingVolume';
- const sortOrder = searchParams.sortOrder || 'desc';
-
- const { leaderboard: signingVolume, totalPages } = await search({
- search: searchString,
- page,
- perPage,
- sortBy,
- sortOrder,
- });
-
- return (
-
-
- Signing Volume
-
-
-
-
-
- );
-}
diff --git a/apps/remix/app/_[id]/logs/download-audit-log-button.tsx b/apps/remix/app/components/pages/document/document-audit-log-download-button.tsx
similarity index 92%
rename from apps/remix/app/_[id]/logs/download-audit-log-button.tsx
rename to apps/remix/app/components/pages/document/document-audit-log-download-button.tsx
index d6be5318c..ee35389ed 100644
--- a/apps/remix/app/_[id]/logs/download-audit-log-button.tsx
+++ b/apps/remix/app/components/pages/document/document-audit-log-download-button.tsx
@@ -9,13 +9,15 @@ import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
-export type DownloadAuditLogButtonProps = {
+export type DocumentAuditLogDownloadButtonProps = {
className?: string;
- teamId?: number;
documentId: number;
};
-export const DownloadAuditLogButton = ({ className, documentId }: DownloadAuditLogButtonProps) => {
+export const DocumentAuditLogDownloadButton = ({
+ className,
+ documentId,
+}: DocumentAuditLogDownloadButtonProps) => {
const { toast } = useToast();
const { _ } = useLingui();
diff --git a/apps/remix/app/_[id]/logs/download-certificate-button.tsx b/apps/remix/app/components/pages/document/document-certificate-download-button.tsx
similarity index 92%
rename from apps/remix/app/_[id]/logs/download-certificate-button.tsx
rename to apps/remix/app/components/pages/document/document-certificate-download-button.tsx
index 45c78f3b6..73ec237fb 100644
--- a/apps/remix/app/_[id]/logs/download-certificate-button.tsx
+++ b/apps/remix/app/components/pages/document/document-certificate-download-button.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DocumentStatus } from '@prisma/client';
@@ -10,19 +8,17 @@ import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
-export type DownloadCertificateButtonProps = {
+export type DocumentCertificateDownloadButtonProps = {
className?: string;
documentId: number;
documentStatus: DocumentStatus;
- teamId?: number;
};
-export const DownloadCertificateButton = ({
+export const DocumentCertificateDownloadButton = ({
className,
documentId,
documentStatus,
- teamId,
-}: DownloadCertificateButtonProps) => {
+}: DocumentCertificateDownloadButtonProps) => {
const { toast } = useToast();
const { _ } = useLingui();
diff --git a/apps/remix/app/_[id]/edit-document.tsx b/apps/remix/app/components/pages/document/document-edit-form.tsx
similarity index 95%
rename from apps/remix/app/_[id]/edit-document.tsx
rename to apps/remix/app/components/pages/document/document-edit-form.tsx
index 1e8f2cca3..b6ff86bec 100644
--- a/apps/remix/app/_[id]/edit-document.tsx
+++ b/apps/remix/app/components/pages/document/document-edit-form.tsx
@@ -3,6 +3,7 @@ import { useEffect, useState } from 'react';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DocumentDistributionMethod, DocumentStatus } from '@prisma/client';
+import { useNavigate, useSearchParams } from 'react-router';
import { isValidLanguageCode } from '@documenso/lib/constants/i18n';
import {
@@ -29,7 +30,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
import { useOptionalCurrentTeam } from '~/providers/team';
-export type EditDocumentFormProps = {
+export type DocumentEditFormProps = {
className?: string;
initialDocument: TDocument;
documentRootPath: string;
@@ -39,17 +40,18 @@ export type EditDocumentFormProps = {
type EditDocumentStep = 'settings' | 'signers' | 'fields' | 'subject';
const EditDocumentSteps: EditDocumentStep[] = ['settings', 'signers', 'fields', 'subject'];
-export const EditDocumentForm = ({
+export const DocumentEditForm = ({
className,
initialDocument,
documentRootPath,
isDocumentEnterprise,
-}: EditDocumentFormProps) => {
+}: DocumentEditFormProps) => {
const { toast } = useToast();
const { _ } = useLingui();
- const router = useRouter();
- const searchParams = useSearchParams();
+ const navigate = useNavigate();
+
+ const [searchParams] = useSearchParams();
const team = useOptionalCurrentTeam();
const [isDocumentPdfLoaded, setIsDocumentPdfLoaded] = useState(false);
@@ -194,9 +196,6 @@ export const EditDocumentForm = ({
},
});
- // Router refresh is here to clear the router cache for when navigating to /documents.
- router.refresh();
-
setStep('signers');
} catch (err) {
console.error(err);
@@ -227,9 +226,6 @@ export const EditDocumentForm = ({
}),
]);
- // Router refresh is here to clear the router cache for when navigating to /documents.
- router.refresh();
-
setStep('fields');
} catch (err) {
console.error(err);
@@ -265,9 +261,6 @@ export const EditDocumentForm = ({
}
}
- // Router refresh is here to clear the router cache for when navigating to /documents.
- router.refresh();
-
setStep('subject');
} catch (err) {
console.error(err);
@@ -301,7 +294,7 @@ export const EditDocumentForm = ({
duration: 5000,
});
- router.push(documentRootPath);
+ void navigate(documentRootPath);
return;
}
@@ -312,7 +305,7 @@ export const EditDocumentForm = ({
duration: 5000,
});
} else {
- router.push(`${documentRootPath}/${document.id}`);
+ void navigate(`${documentRootPath}/${document.id}`);
}
} catch (err) {
console.error(err);
diff --git a/apps/remix/app/_[id]/document-page-view-button.tsx b/apps/remix/app/components/pages/document/document-page-view-button.tsx
similarity index 91%
rename from apps/remix/app/_[id]/document-page-view-button.tsx
rename to apps/remix/app/components/pages/document/document-page-view-button.tsx
index ff38f285c..141e51e56 100644
--- a/apps/remix/app/_[id]/document-page-view-button.tsx
+++ b/apps/remix/app/components/pages/document/document-page-view-button.tsx
@@ -3,7 +3,7 @@ import { useLingui } from '@lingui/react';
import type { Document, Recipient, Team, User } from '@prisma/client';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { CheckCircle, Download, EyeIcon, Pencil } from 'lucide-react';
-import { useSession } from 'next-auth/react';
+import { Link } from 'react-router';
import { match } from 'ts-pattern';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
@@ -12,25 +12,23 @@ import { trpc as trpcClient } from '@documenso/trpc/client';
import { Button } from '@documenso/ui/primitives/button';
import { useToast } from '@documenso/ui/primitives/use-toast';
+import { useAuth } from '~/providers/auth';
+
export type DocumentPageViewButtonProps = {
document: Document & {
user: Pick;
recipients: Recipient[];
team: Pick | null;
};
- team?: Pick;
};
export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps) => {
- const { data: session } = useSession();
+ const { user } = useAuth();
+
const { toast } = useToast();
const { _ } = useLingui();
- if (!session) {
- return null;
- }
-
- const recipient = document.recipients.find((recipient) => recipient.email === session.user.email);
+ const recipient = document.recipients.find((recipient) => recipient.email === user.email);
const isRecipient = !!recipient;
const isPending = document.status === DocumentStatus.PENDING;
@@ -77,7 +75,7 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
})
.with({ isRecipient: true, isPending: true, isSigned: false }, () => (
-
+
{match(role)
.with(RecipientRole.SIGNER, () => (
<>
@@ -102,7 +100,7 @@ export const DocumentPageViewButton = ({ document }: DocumentPageViewButtonProps
))
.with({ isComplete: false }, () => (
-
+
Edit
diff --git a/apps/remix/app/_[id]/document-page-view-dropdown.tsx b/apps/remix/app/components/pages/document/document-page-view-dropdown.tsx
similarity index 84%
rename from apps/remix/app/_[id]/document-page-view-dropdown.tsx
rename to apps/remix/app/components/pages/document/document-page-view-dropdown.tsx
index 2b74460f9..d6fb61781 100644
--- a/apps/remix/app/_[id]/document-page-view-dropdown.tsx
+++ b/apps/remix/app/components/pages/document/document-page-view-dropdown.tsx
@@ -3,7 +3,7 @@ import { useState } from 'react';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DocumentStatus } from '@prisma/client';
-import type { Document, Recipient, Team, TeamEmail, User } from '@prisma/client';
+import type { Document, Recipient, Team, User } from '@prisma/client';
import {
Copy,
Download,
@@ -14,7 +14,7 @@ import {
Share,
Trash2,
} from 'lucide-react';
-import { useSession } from 'next-auth/react';
+import { Link } from 'react-router';
import { downloadPDF } from '@documenso/lib/client-only/download-pdf';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
@@ -29,11 +29,12 @@ import {
} from '@documenso/ui/primitives/dropdown-menu';
import { useToast } from '@documenso/ui/primitives/use-toast';
+import { DocumentDeleteDialog } from '~/components/dialogs/document-delete-dialog';
+import { DocumentDuplicateDialog } from '~/components/dialogs/document-duplicate-dialog';
+import { DocumentResendDialog } from '~/components/dialogs/document-resend-dialog';
import { DocumentRecipientLinkCopyDialog } from '~/components/document/document-recipient-link-copy-dialog';
-
-import { DeleteDocumentDialog } from '../delete-document-dialog';
-import { DuplicateDocumentDialog } from '../duplicate-document-dialog';
-import { ResendDocumentActionItem } from '../resend-document-dialog';
+import { useAuth } from '~/providers/auth';
+import { useOptionalCurrentTeam } from '~/providers/team';
export type DocumentPageViewDropdownProps = {
document: Document & {
@@ -41,24 +42,21 @@ export type DocumentPageViewDropdownProps = {
recipients: Recipient[];
team: Pick | null;
};
- team?: Pick & { teamEmail: TeamEmail | null };
};
-export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDropdownProps) => {
- const { data: session } = useSession();
+export const DocumentPageViewDropdown = ({ document }: DocumentPageViewDropdownProps) => {
+ const { user } = useAuth();
const { toast } = useToast();
const { _ } = useLingui();
+ const team = useOptionalCurrentTeam();
+
const [isDeleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [isDuplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
- if (!session) {
- return null;
- }
+ const recipient = document.recipients.find((recipient) => recipient.email === user.email);
- const recipient = document.recipients.find((recipient) => recipient.email === session.user.email);
-
- const isOwner = document.user.id === session.user.id;
+ const isOwner = document.user.id === user.id;
const isDraft = document.status === DocumentStatus.DRAFT;
const isPending = document.status === DocumentStatus.PENDING;
const isDeleted = document.deletedAt !== null;
@@ -112,7 +110,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
{(isOwner || isCurrentTeamDocument) && !isComplete && (
-
+
Edit
@@ -127,7 +125,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
)}
-
+
Audit Log
@@ -165,11 +163,7 @@ export const DocumentPageViewDropdown = ({ document, team }: DocumentPageViewDro
/>
)}
-
+
-
{isDuplicateDialogOpen && (
-
)}
diff --git a/apps/remix/app/_[id]/document-page-view-information.tsx b/apps/remix/app/components/pages/document/document-page-view-information.tsx
similarity index 100%
rename from apps/remix/app/_[id]/document-page-view-information.tsx
rename to apps/remix/app/components/pages/document/document-page-view-information.tsx
diff --git a/apps/remix/app/_[id]/document-page-view-recent-activity.tsx b/apps/remix/app/components/pages/document/document-page-view-recent-activity.tsx
similarity index 100%
rename from apps/remix/app/_[id]/document-page-view-recent-activity.tsx
rename to apps/remix/app/components/pages/document/document-page-view-recent-activity.tsx
diff --git a/apps/remix/app/_[id]/document-page-view-recipients.tsx b/apps/remix/app/components/pages/document/document-page-view-recipients.tsx
similarity index 99%
rename from apps/remix/app/_[id]/document-page-view-recipients.tsx
rename to apps/remix/app/components/pages/document/document-page-view-recipients.tsx
index 4644e39d1..cf9f0cbe0 100644
--- a/apps/remix/app/_[id]/document-page-view-recipients.tsx
+++ b/apps/remix/app/components/pages/document/document-page-view-recipients.tsx
@@ -1,3 +1,7 @@
+'use client';
+
+import Link from 'next/link';
+
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
diff --git a/apps/remix/app/_[id]/logs/document-logs-data-table.tsx b/apps/remix/app/components/tables/document-logs-table.tsx
similarity index 96%
rename from apps/remix/app/_[id]/logs/document-logs-data-table.tsx
rename to apps/remix/app/components/tables/document-logs-table.tsx
index 68dbfbea3..3ebf6d5b3 100644
--- a/apps/remix/app/_[id]/logs/document-logs-data-table.tsx
+++ b/apps/remix/app/components/tables/document-logs-table.tsx
@@ -1,5 +1,9 @@
+'use client';
+
import { useMemo } from 'react';
+import { useSearchParams } from 'next/navigation';
+
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DateTime } from 'luxon';
@@ -16,7 +20,7 @@ import { DataTablePagination } from '@documenso/ui/primitives/data-table-paginat
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
-export type DocumentLogsDataTableProps = {
+export type DocumentLogsTableProps = {
documentId: number;
};
@@ -25,7 +29,7 @@ const dateFormat: DateTimeFormatOptions = {
hourCycle: 'h12',
};
-export const DocumentLogsDataTable = ({ documentId }: DocumentLogsDataTableProps) => {
+export const DocumentLogsTable = ({ documentId }: DocumentLogsTableProps) => {
const { _, i18n } = useLingui();
const searchParams = useSearchParams();
diff --git a/apps/remix/app/documents+/_documents-page-view.tsx b/apps/remix/app/documents+/_documents-page-view.tsx
deleted file mode 100644
index e73462462..000000000
--- a/apps/remix/app/documents+/_documents-page-view.tsx
+++ /dev/null
@@ -1,159 +0,0 @@
-import { Trans } from '@lingui/macro';
-import type { Team, TeamEmail, TeamMemberRole } from '@prisma/client';
-import { Link } from 'react-router';
-
-import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
-import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
-import { parseToIntegerArray } from '@documenso/lib/utils/params';
-import { formatDocumentsPath } from '@documenso/lib/utils/teams';
-import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
-import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
-import { trpc } from '@documenso/trpc/react';
-import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
-import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
-
-import { DocumentSearch } from '~/components/(dashboard)/document-search/document-search';
-import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
-import { isPeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
-import { DocumentUploadDropzone } from '~/components/document/document-upload';
-import { DocumentStatus } from '~/components/formatter/document-status';
-import { DocumentsTable } from '~/components/tables/documents-table';
-import { DocumentsTableEmptyState } from '~/components/tables/documents-table-empty-state';
-import { DocumentsTableSenderFilter } from '~/components/tables/documents-table-sender-filter';
-import { useAuth } from '~/providers/auth';
-
-export interface DocumentsPageViewProps {
- searchParams?: {
- status?: ExtendedDocumentStatus;
- period?: PeriodSelectorValue;
- page?: string;
- perPage?: string;
- senderIds?: string;
- search?: string;
- };
- team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
-}
-
-export const DocumentsPageView = ({ searchParams = {}, team }: DocumentsPageViewProps) => {
- const { user } = useAuth();
-
- const status = isExtendedDocumentStatus(searchParams.status) ? searchParams.status : 'ALL';
- const period = isPeriodSelectorValue(searchParams.period) ? searchParams.period : '';
- const page = Number(searchParams.page) || 1;
- const perPage = Number(searchParams.perPage) || 20;
- const senderIds = parseToIntegerArray(searchParams.senderIds ?? '');
- const search = searchParams.search || '';
- const currentTeam = team
- ? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
- : undefined;
- const currentTeamMemberRole = team?.currentTeamMember?.role;
-
- // const results = await findDocuments({
- // status,
- // orderBy: {
- // column: 'createdAt',
- // direction: 'desc',
- // },
- // page,
- // perPage,
- // period,
- // senderIds,
- // query: search,
- // });
-
- const { data, isLoading, isLoadingError } = trpc.document.findDocuments.useQuery({
- page,
- perPage,
- });
-
- const getTabHref = (value: typeof status) => {
- const params = new URLSearchParams(searchParams);
-
- params.set('status', value);
-
- if (params.has('page')) {
- params.delete('page');
- }
-
- return `${formatDocumentsPath(team?.url)}?${params.toString()}`;
- };
-
- return (
-
-
-
-
-
- {team && (
-
- {team.avatarImageId && (
-
- )}
-
- {team.name.slice(0, 1)}
-
-
- )}
-
-
- Documents
-
-
-
-
-
-
- {[
- ExtendedDocumentStatus.INBOX,
- ExtendedDocumentStatus.PENDING,
- ExtendedDocumentStatus.COMPLETED,
- ExtendedDocumentStatus.DRAFT,
- ExtendedDocumentStatus.ALL,
- ].map((value) => (
-
-
-
-
- {value !== ExtendedDocumentStatus.ALL && (
- todo
- )}
-
-
- ))}
-
-
-
- {team &&
}
-
-
-
-
-
-
-
-
-
-
- {data && data.count === 0 ? (
-
- ) : (
-
- )}
-
-
-
- );
-};
diff --git a/apps/remix/app/documents+/index.tsx b/apps/remix/app/documents+/index.tsx
deleted file mode 100644
index 47c3c961a..000000000
--- a/apps/remix/app/documents+/index.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { useSearchParams } from 'react-router';
-
-export function meta() {
- return [{ title: 'Documents' }];
-}
-
-export default function DocumentsPage() {
- const [searchParams] = useSearchParams();
-
- return (
- <>
- hello
- {/* */}
- >
- );
-}
diff --git a/apps/remix/app/root.tsx b/apps/remix/app/root.tsx
index 10c075e82..13216183b 100644
--- a/apps/remix/app/root.tsx
+++ b/apps/remix/app/root.tsx
@@ -5,6 +5,7 @@ import {
Scripts,
ScrollRestoration,
isRouteErrorResponse,
+ useLoaderData,
} from 'react-router';
import { TrpcProvider } from '@documenso/trpc/react';
@@ -32,7 +33,17 @@ export const links: Route.LinksFunction = () => [
{ rel: 'stylesheet', href: stylesheet },
];
+export function loader() {
+ return {
+ __ENV__: Object.fromEntries(
+ Object.entries(process.env).filter(([key]) => key.startsWith('NEXT_')),
+ ),
+ };
+}
+
export function Layout({ children }: { children: React.ReactNode }) {
+ const { __ENV__ } = useLoaderData() || {};
+
return (
@@ -45,6 +56,12 @@ export function Layout({ children }: { children: React.ReactNode }) {
{children}
+
+
);
diff --git a/apps/remix/app/routes/_authenticated+/admin+/stats.tsx b/apps/remix/app/routes/_authenticated+/admin+/stats.tsx
index 1e8edb439..e3641d403 100644
--- a/apps/remix/app/routes/_authenticated+/admin+/stats.tsx
+++ b/apps/remix/app/routes/_authenticated+/admin+/stats.tsx
@@ -22,6 +22,7 @@ import {
getUsersWithSubscriptionsCount,
} from '@documenso/lib/server-only/admin/get-users-stats';
import { getSignerConversionMonthly } from '@documenso/lib/server-only/user/get-signer-conversion';
+import { env } from '@documenso/lib/utils/env';
import { CardMetric } from '~/components/(dashboard)/metric-card/metric-card';
import { AdminStatsSignerConversionChart } from '~/components/general/admin-stats-signer-conversion-chart';
@@ -87,11 +88,7 @@ export default function AdminStatsPage({ loaderData }: Route.ComponentProps) {
value={usersWithSubscriptionsCount}
/>
-
+
diff --git a/apps/remix/app/_[id]/document-page-view.tsx b/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx
similarity index 78%
rename from apps/remix/app/_[id]/document-page-view.tsx
rename to apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx
index 97e19fbcc..4d67e1127 100644
--- a/apps/remix/app/_[id]/document-page-view.tsx
+++ b/apps/remix/app/routes/_authenticated+/documents+/$id._index.tsx
@@ -1,20 +1,20 @@
import { Plural, Trans } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { DocumentStatus } from '@prisma/client';
-import type { Team, TeamEmail } from '@prisma/client';
import { TeamMemberRole } from '@prisma/client';
import { ChevronLeft, Clock9, Users2 } from 'lucide-react';
+import { Link, redirect } from 'react-router';
import { match } from 'ts-pattern';
+import { getRequiredSession } from '@documenso/auth/server/lib/utils/get-session';
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
-import { getServerComponentFlag } from '@documenso/lib/server-only/feature-flags/get-server-component-feature-flag';
import { getFieldsForDocument } from '@documenso/lib/server-only/field/get-fields-for-document';
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
+import { prisma } from '@documenso/prisma';
import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardContent } from '@documenso/ui/primitives/card';
@@ -28,24 +28,38 @@ import {
DocumentStatus as DocumentStatusComponent,
FRIENDLY_STATUS_MAP,
} from '~/components/formatter/document-status';
+import { DocumentPageViewButton } from '~/components/pages/document/document-page-view-button';
+import { DocumentPageViewDropdown } from '~/components/pages/document/document-page-view-dropdown';
+import { DocumentPageViewInformation } from '~/components/pages/document/document-page-view-information';
+import { DocumentPageViewRecentActivity } from '~/components/pages/document/document-page-view-recent-activity';
+import { DocumentPageViewRecipients } from '~/components/pages/document/document-page-view-recipients';
+import { useAuth } from '~/providers/auth';
-import { DocumentPageViewButton } from './document-page-view-button';
-import { DocumentPageViewDropdown } from './document-page-view-dropdown';
-import { DocumentPageViewInformation } from './document-page-view-information';
-import { DocumentPageViewRecentActivity } from './document-page-view-recent-activity';
-import { DocumentPageViewRecipients } from './document-page-view-recipients';
+import type { Route } from './+types/$id._index';
-export type DocumentPageViewProps = {
- documentId: number;
- team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember: { role: TeamMemberRole } };
-};
+export async function loader({ request, params }: Route.LoaderArgs) {
+ const { id } = params;
-export const DocumentPageView = async ({ documentId, team }: DocumentPageViewProps) => {
- const { _ } = useLingui();
+ const { user } = await getRequiredSession(request);
+
+ // Todo: Get from parent loader, this is just for testing.
+ const team = await prisma.team.findFirst({
+ where: {
+ documents: {
+ some: {
+ id: Number(id),
+ },
+ },
+ },
+ });
+
+ const documentId = Number(id);
const documentRootPath = formatDocumentsPath(team?.url);
- const { user } = await getRequiredServerComponentSession();
+ if (!documentId || Number.isNaN(documentId)) {
+ return redirect(documentRootPath);
+ }
const document = await getDocumentById({
documentId,
@@ -54,7 +68,7 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
}).catch(() => null);
if (document?.teamId && !team?.url) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
const documentVisibility = document?.visibility;
@@ -73,19 +87,15 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
.otherwise(() => false);
}
- const isDocumentHistoryEnabled = await getServerComponentFlag(
- 'app_document_page_view_history_sheet',
- );
-
if (!document || !document.documentData || (team && !canAccessDocument)) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
if (team && !canAccessDocument) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
- const { documentData, documentMeta } = document;
+ const { documentMeta } = document;
if (documentMeta?.password) {
const key = DOCUMENSO_ENCRYPTION_KEY;
@@ -104,6 +114,7 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
documentMeta.password = securePassword;
}
+ // Todo: Get full document instead???
const [recipients, fields] = await Promise.all([
getRecipientsForDocument({
documentId,
@@ -122,13 +133,30 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
recipients,
};
+ return {
+ document: documentWithRecipients,
+ documentRootPath,
+ fields,
+ };
+}
+
+export default function DocumentPage({ loaderData }: Route.ComponentProps) {
+ const { _ } = useLingui();
+ const { user } = useAuth();
+
+ const { document, documentRootPath, fields } = loaderData;
+
+ const { recipients, documentData, documentMeta } = document;
+
+ const isDocumentHistoryEnabled = false; // Todo: Was flag
+
return (
{document.status === DocumentStatus.PENDING && (
)}
-
+
Documents
@@ -207,7 +235,7 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
{_(FRIENDLY_STATUS_MAP[document.status].labelExtended)}
-
+
@@ -235,18 +263,15 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
-
+
{/* Document information section. */}
-
+
{/* Recipients section. */}
-
+
{/* Recent activity section. */}
@@ -255,4 +280,4 @@ export const DocumentPageView = async ({ documentId, team }: DocumentPageViewPro
);
-};
+}
diff --git a/apps/remix/app/_[id]/edit/document-edit-page-view.tsx b/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx
similarity index 75%
rename from apps/remix/app/_[id]/edit/document-edit-page-view.tsx
rename to apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx
index 246e063d8..4ec1f072c 100644
--- a/apps/remix/app/_[id]/edit/document-edit-page-view.tsx
+++ b/apps/remix/app/routes/_authenticated+/documents+/$id.edit.tsx
@@ -1,37 +1,49 @@
import { Plural, Trans } from '@lingui/macro';
-import type { Team } from '@prisma/client';
import { TeamMemberRole } from '@prisma/client';
import { DocumentStatus as InternalDocumentStatus } from '@prisma/client';
import { ChevronLeft, Users2 } from 'lucide-react';
+import { Link, redirect } from 'react-router';
import { match } from 'ts-pattern';
+import { getRequiredSession } from '@documenso/auth/server/lib/utils/get-session';
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
import { DOCUMENSO_ENCRYPTION_KEY } from '@documenso/lib/constants/crypto';
-import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { getDocumentWithDetailsById } from '@documenso/lib/server-only/document/get-document-with-details-by-id';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { symmetricDecrypt } from '@documenso/lib/universal/crypto';
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
+import { prisma } from '@documenso/prisma';
import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip';
import { DocumentStatus } from '~/components/formatter/document-status';
+import { DocumentEditForm } from '~/components/pages/document/document-edit-form';
-import { EditDocumentForm } from '../edit-document';
+import type { Route } from './+types/$id.edit';
-export type DocumentEditPageViewProps = {
- documentId: number;
- team?: Team & { currentTeamMember: { role: TeamMemberRole } };
-};
+export async function loader({ request, params }: Route.LoaderArgs) {
+ const { id } = params;
+
+ const { user } = await getRequiredSession(request);
+
+ // Todo: Get from parent loader, this is just for testing.
+ const team = await prisma.team.findFirst({
+ where: {
+ documents: {
+ some: {
+ id: Number(id),
+ },
+ },
+ },
+ });
+
+ const documentId = Number(id);
-export const DocumentEditPageView = async ({ documentId, team }: DocumentEditPageViewProps) => {
const documentRootPath = formatDocumentsPath(team?.url);
if (!documentId || Number.isNaN(documentId)) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
- const { user } = await getRequiredServerComponentSession();
-
const document = await getDocumentWithDetailsById({
documentId,
userId: user.id,
@@ -39,7 +51,7 @@ export const DocumentEditPageView = async ({ documentId, team }: DocumentEditPag
}).catch(() => null);
if (document?.teamId && !team?.url) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
const documentVisibility = document?.visibility;
@@ -59,15 +71,15 @@ export const DocumentEditPageView = async ({ documentId, team }: DocumentEditPag
}
if (!document) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
if (team && !canAccessDocument) {
- redirect(documentRootPath);
+ return redirect(documentRootPath);
}
if (document.status === InternalDocumentStatus.COMPLETED) {
- redirect(`${documentRootPath}/${documentId}`);
+ return redirect(`${documentRootPath}/${documentId}`);
}
const { documentMeta, recipients } = document;
@@ -94,9 +106,21 @@ export const DocumentEditPageView = async ({ documentId, team }: DocumentEditPag
teamId: team?.id,
});
+ return {
+ document,
+ documentRootPath,
+ isDocumentEnterprise,
+ };
+}
+
+export default function DocumentEditPage({ loaderData }: Route.ComponentProps) {
+ const { document, documentRootPath, isDocumentEnterprise } = loaderData;
+
+ const { recipients } = document;
+
return (