diff --git a/.gitignore b/.gitignore index 3b0569b15..b95fcc7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +packages/prisma/generated/types.ts + # dependencies node_modules .pnp diff --git a/apps/marketing/package.json b/apps/marketing/package.json index fb6478fca..3b122d0f4 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -1,6 +1,6 @@ { "name": "@documenso/marketing", - "version": "1.6.1-rc.1", + "version": "1.7.0-rc.2", "private": true, "license": "AGPL-3.0", "scripts": { @@ -20,7 +20,6 @@ "@documenso/trpc": "*", "@documenso/ui": "*", "@hookform/resolvers": "^3.1.0", - "@lingui/macro": "^4.11.1", "@lingui/react": "^4.11.1", "@openstatus/react": "^0.0.3", "cmdk": "^0.2.1", diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 2d8bbe9d9..a3df5af29 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -5,6 +5,8 @@ import { useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { msg } from '@lingui/macro'; + import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app'; import { base64 } from '@documenso/lib/universal/base64'; @@ -46,8 +48,8 @@ export const SinglePlayerClient = () => { const documentFlow: Record = { fields: { - title: 'Add document', - description: 'Upload a document and add fields.', + title: msg`Add document`, + description: msg`Upload a document and add fields.`, stepIndex: 1, onBackStep: uploadedFile ? () => { @@ -58,8 +60,8 @@ export const SinglePlayerClient = () => { onNextStep: () => setStep('sign'), }, sign: { - title: 'Sign', - description: 'Enter your details.', + title: msg`Sign`, + description: msg`Enter your details.`, stepIndex: 2, onBackStep: () => setStep('fields'), }, diff --git a/apps/web/lingui.config.ts b/apps/web/lingui.config.ts index f129b49ce..a5862eb98 100644 --- a/apps/web/lingui.config.ts +++ b/apps/web/lingui.config.ts @@ -8,7 +8,7 @@ const config: LinguiConfig = { locales: APP_I18N_OPTIONS.supportedLangs as unknown as string[], catalogs: [ { - path: '/../../packages/lib/translations/web/{locale}', + path: '/../../packages/lib/translations/{locale}/web', include: ['/apps/web/src'], }, { diff --git a/apps/web/package.json b/apps/web/package.json index f86e5769a..47d7ceda6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@documenso/web", - "version": "1.6.1-rc.1", + "version": "1.7.0-rc.2", "private": true, "license": "AGPL-3.0", "scripts": { @@ -23,7 +23,6 @@ "@documenso/trpc": "*", "@documenso/ui": "*", "@hookform/resolvers": "^3.1.0", - "@lingui/macro": "^4.11.1", "@lingui/react": "^4.11.1", "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.3", @@ -60,9 +59,9 @@ "zod": "^3.22.4" }, "devDependencies": { + "@documenso/tailwind-config": "*", "@lingui/loader": "^4.11.1", "@lingui/swc-plugin": "4.0.6", - "@documenso/tailwind-config": "*", "@simplewebauthn/types": "^9.0.1", "@types/formidable": "^2.0.6", "@types/luxon": "^3.3.1", diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx index f084b5db5..330f31eb1 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx @@ -2,6 +2,9 @@ import Link from 'next/link'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; + import type { Recipient } from '@documenso/prisma/client'; import { type Document, SigningStatus } from '@documenso/prisma/client'; import { trpc } from '@documenso/trpc/react'; @@ -22,20 +25,21 @@ export type AdminActionsProps = { }; export const AdminActions = ({ className, document, recipients }: AdminActionsProps) => { + const { _ } = useLingui(); const { toast } = useToast(); const { mutate: resealDocument, isLoading: isResealDocumentLoading } = trpc.admin.resealDocument.useMutation({ onSuccess: () => { toast({ - title: 'Success', - description: 'Document resealed', + title: _(msg`Success`), + description: _(msg`Document resealed`), }); }, onError: () => { toast({ - title: 'Error', - description: 'Failed to reseal document', + title: _(msg`Error`), + description: _(msg`Failed to reseal document`), variant: 'destructive', }); }, @@ -54,19 +58,23 @@ export const AdminActions = ({ className, document, recipients }: AdminActionsPr )} onClick={() => resealDocument({ id: document.id })} > - Reseal document + Reseal document - Attempts sealing the document again, useful for after a code change has occurred to - resolve an erroneous document. + + Attempts sealing the document again, useful for after a code change has occurred to + resolve an erroneous document. + ); diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx index 563db8d1b..48211fbc0 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx @@ -1,5 +1,7 @@ +import { Trans } from '@lingui/macro'; import { DateTime } from 'luxon'; +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getEntireDocument } from '@documenso/lib/server-only/admin/get-entire-document'; import { Accordion, @@ -23,6 +25,8 @@ type AdminDocumentDetailsPageProps = { }; export default async function AdminDocumentDetailsPage({ params }: AdminDocumentDetailsPageProps) { + setupI18nSSR(); + const document = await getEntireDocument({ id: Number(params.id) }); return ( @@ -35,28 +39,34 @@ export default async function AdminDocumentDetailsPage({ params }: AdminDocument {document.deletedAt && ( - Deleted + Deleted )}
- Created on: + Created on:{' '} +
- Last updated at: + Last updated at:{' '} +

-

Admin Actions

+

+ Admin Actions +


-

Recipients

+

+ Recipients +

diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx index 3bf8c78ab..c4864145c 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx @@ -1,7 +1,11 @@ 'use client'; +import { useMemo } from 'react'; + import { useRouter } from 'next/navigation'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -13,6 +17,7 @@ import { } from '@documenso/prisma/client'; import { trpc } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; +import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table'; import { DataTable } from '@documenso/ui/primitives/data-table'; import { Form, @@ -43,7 +48,9 @@ export type RecipientItemProps = { }; export const RecipientItem = ({ recipient }: RecipientItemProps) => { + const { _ } = useLingui(); const { toast } = useToast(); + const router = useRouter(); const form = useForm({ @@ -55,6 +62,50 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { const { mutateAsync: updateRecipient } = trpc.admin.updateRecipient.useMutation(); + const columns = useMemo(() => { + return [ + { + header: 'ID', + accessorKey: 'id', + cell: ({ row }) =>
{row.original.id}
, + }, + { + header: _(msg`Type`), + accessorKey: 'type', + cell: ({ row }) =>
{row.original.type}
, + }, + { + header: _(msg`Inserted`), + accessorKey: 'inserted', + cell: ({ row }) =>
{row.original.inserted ? 'True' : 'False'}
, + }, + { + header: _(msg`Value`), + accessorKey: 'customText', + cell: ({ row }) =>
{row.original.customText}
, + }, + { + header: _(msg`Signature`), + accessorKey: 'signature', + cell: ({ row }) => ( +
+ {row.original.Signature?.typedSignature && ( + {row.original.Signature.typedSignature} + )} + + {row.original.Signature?.signatureImageAsBase64 && ( + Signature + )} +
+ ), + }, + ] satisfies DataTableColumnDef<(typeof recipient)['Field'][number]>[]; + }, []); + const onUpdateRecipientFormSubmit = async ({ name, email }: TAdminUpdateRecipientFormSchema) => { try { await updateRecipient({ @@ -64,14 +115,14 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { }); toast({ - title: 'Recipient updated', - description: 'The recipient has been updated successfully', + title: _(msg`Recipient updated`), + description: _(msg`The recipient has been updated successfully`), }); router.refresh(); } catch (error) { toast({ - title: 'Failed to update recipient', + title: _(msg`Failed to update recipient`), description: error.message, variant: 'destructive', }); @@ -93,7 +144,9 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { name="name" render={({ field }) => ( - Name + + Name + @@ -109,7 +162,9 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => { name="email" render={({ field }) => ( - Email + + Email + @@ -122,7 +177,7 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
@@ -131,52 +186,11 @@ export const RecipientItem = ({ recipient }: RecipientItemProps) => {
-

Fields

+

+ Fields +

-
{row.original.id}
, - }, - { - header: 'Type', - accessorKey: 'type', - cell: ({ row }) =>
{row.original.type}
, - }, - { - header: 'Inserted', - accessorKey: 'inserted', - cell: ({ row }) =>
{row.original.inserted ? 'True' : 'False'}
, - }, - { - header: 'Value', - accessorKey: 'customText', - cell: ({ row }) =>
{row.original.customText}
, - }, - { - header: 'Signature', - accessorKey: 'signature', - cell: ({ row }) => ( -
- {row.original.Signature?.typedSignature && ( - {row.original.Signature.typedSignature} - )} - - {row.original.Signature?.signatureImageAsBase64 && ( - Signature - )} -
- ), - }, - ]} - /> +
); }; diff --git a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx index 63ad88a3f..337796959 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx @@ -4,6 +4,9 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; + import type { Document } from '@documenso/prisma/client'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; @@ -26,7 +29,9 @@ export type SuperDeleteDocumentDialogProps = { }; export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialogProps) => { + const { _ } = useLingui(); const { toast } = useToast(); + const router = useRouter(); const [reason, setReason] = useState(''); @@ -43,7 +48,7 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo await deleteDocument({ id: document.id, reason }); toast({ - title: 'Document deleted', + title: _(msg`Document deleted`), description: 'The Document has been deleted successfully.', duration: 5000, }); @@ -52,13 +57,13 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ - title: 'An error occurred', + title: _(msg`An error occurred`), description: err.message, variant: 'destructive', }); } else { toast({ - title: 'An unknown error occurred', + title: _(msg`An unknown error occurred`), variant: 'destructive', description: err.message ?? @@ -76,31 +81,41 @@ export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialo variant="neutral" >
- Delete Document + + Delete Document + - Delete the document. This action is irreversible so proceed with caution. + + Delete the document. This action is irreversible so proceed with caution. +
- + - Delete Document + + Delete Document + - This action is not reversible. Please be certain. + This action is not reversible. Please be certain.
- To confirm, please enter the reason + + To confirm, please enter the reason + - {isDeletingDocument ? 'Deleting document...' : 'Delete Document'} + Delete document diff --git a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx b/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx index b7e235981..1686e0d41 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx @@ -1,10 +1,12 @@ 'use client'; -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; +import { msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { Loader } from 'lucide-react'; import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; @@ -12,6 +14,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda import { extractInitials } from '@documenso/lib/utils/recipient-formatter'; import { trpc } from '@documenso/trpc/react'; import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar'; +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'; @@ -23,6 +26,8 @@ import { LocaleDate } from '~/components/formatter/locale-date'; // export type AdminDocumentResultsProps = {}; export const AdminDocumentResults = () => { + const { _ } = useLingui(); + const searchParams = useSearchParams(); const updateSearchParams = useUpdateSearchParams(); @@ -45,6 +50,83 @@ export const AdminDocumentResults = () => { }, ); + const results = findDocumentsData ?? { + data: [], + perPage: 20, + currentPage: 1, + totalPages: 1, + }; + + const columns = useMemo(() => { + return [ + { + header: _(msg`Created`), + accessorKey: 'createdAt', + cell: ({ row }) => , + }, + { + header: _(msg`Title`), + accessorKey: 'title', + cell: ({ row }) => { + return ( + + {row.original.title} + + ); + }, + }, + { + header: _(msg`Status`), + accessorKey: 'status', + cell: ({ row }) => , + }, + { + header: _(msg`Owner`), + accessorKey: 'owner', + cell: ({ row }) => { + const avatarFallbackText = row.original.User.name + ? extractInitials(row.original.User.name) + : row.original.User.email.slice(0, 1).toUpperCase(); + + return ( + + + + + + {avatarFallbackText} + + + + + + + + + {avatarFallbackText} + + + +
+ {row.original.User.name} + {row.original.User.email} +
+
+
+ ); + }, + }, + { + header: 'Last updated', + accessorKey: 'updatedAt', + cell: ({ row }) => , + }, + ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; + }, []); + const onPaginationChange = (newPage: number, newPerPage: number) => { updateSearchParams({ page: newPage, @@ -56,84 +138,18 @@ export const AdminDocumentResults = () => {
setTerm(e.target.value)} />
, - }, - { - header: 'Title', - accessorKey: 'title', - cell: ({ row }) => { - return ( - - {row.original.title} - - ); - }, - }, - { - header: 'Status', - accessorKey: 'status', - cell: ({ row }) => , - }, - { - header: 'Owner', - accessorKey: 'owner', - cell: ({ row }) => { - const avatarFallbackText = row.original.User.name - ? extractInitials(row.original.User.name) - : row.original.User.email.slice(0, 1).toUpperCase(); - - return ( - - - - - - {avatarFallbackText} - - - - - - - - - {avatarFallbackText} - - - -
- {row.original.User.name} - {row.original.User.email} -
-
-
- ); - }, - }, - { - header: 'Last updated', - accessorKey: 'updatedAt', - cell: ({ row }) => , - }, - ]} - data={findDocumentsData?.data ?? []} - perPage={findDocumentsData?.perPage ?? 20} - currentPage={findDocumentsData?.currentPage ?? 1} - totalPages={findDocumentsData?.totalPages ?? 1} + columns={columns} + data={results.data} + perPage={results.perPage ?? 20} + currentPage={results.currentPage ?? 1} + totalPages={results.totalPages ?? 1} onPaginationChange={onPaginationChange} > {(table) => } diff --git a/apps/web/src/app/(dashboard)/admin/documents/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/page.tsx index 96e4dcef8..7f21bf5fa 100644 --- a/apps/web/src/app/(dashboard)/admin/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/admin/documents/page.tsx @@ -1,9 +1,17 @@ +import { Trans } from '@lingui/macro'; + +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; + import { AdminDocumentResults } from './document-results'; export default function AdminDocumentsPage() { + setupI18nSSR(); + return (
-

Manage documents

+

+ Manage documents +

diff --git a/apps/web/src/app/(dashboard)/admin/layout.tsx b/apps/web/src/app/(dashboard)/admin/layout.tsx index 12330679d..c489c34a1 100644 --- a/apps/web/src/app/(dashboard)/admin/layout.tsx +++ b/apps/web/src/app/(dashboard)/admin/layout.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { redirect } from 'next/navigation'; +import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin'; @@ -12,6 +13,8 @@ export type AdminSectionLayoutProps = { }; export default async function AdminSectionLayout({ children }: AdminSectionLayoutProps) { + setupI18nSSR(); + const { user } = await getRequiredServerComponentSession(); if (!isAdmin(user)) { diff --git a/apps/web/src/app/(dashboard)/admin/nav.tsx b/apps/web/src/app/(dashboard)/admin/nav.tsx index 080cb9741..cf0bb81f2 100644 --- a/apps/web/src/app/(dashboard)/admin/nav.tsx +++ b/apps/web/src/app/(dashboard)/admin/nav.tsx @@ -5,6 +5,7 @@ import type { HTMLAttributes } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import { Trans } from '@lingui/macro'; import { BarChart3, FileStack, Settings, Users, Wallet2 } from 'lucide-react'; import { cn } from '@documenso/ui/lib/utils'; @@ -33,7 +34,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Stats + Stats @@ -47,7 +48,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Users + Users @@ -61,7 +62,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Documents + Documents @@ -75,7 +76,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Subscriptions + Subscriptions @@ -89,7 +90,7 @@ export const AdminNav = ({ className, ...props }: AdminNavProps) => { > - Site Settings + Site Settings
diff --git a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx index 351e146ff..d68eed63b 100644 --- a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx +++ b/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx @@ -3,6 +3,8 @@ import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; +import { Trans, msg } from '@lingui/macro'; +import { useLingui } from '@lingui/react'; import { useForm } from 'react-hook-form'; import type { z } from 'zod'; @@ -37,8 +39,10 @@ export type BannerFormProps = { }; export function BannerForm({ banner }: BannerFormProps) { - const router = useRouter(); const { toast } = useToast(); + const { _ } = useLingui(); + + const router = useRouter(); const form = useForm({ resolver: zodResolver(ZBannerFormSchema), @@ -67,8 +71,8 @@ export function BannerForm({ banner }: BannerFormProps) { }); toast({ - title: 'Banner Updated', - description: 'Your banner has been updated successfully.', + title: _(msg`Banner Updated`), + description: _(msg`Your banner has been updated successfully.`), duration: 5000, }); @@ -76,16 +80,17 @@ export function BannerForm({ banner }: BannerFormProps) { } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ - title: 'An error occurred', + title: _(msg`An error occurred`), description: err.message, variant: 'destructive', }); } else { toast({ - title: 'An unknown error occurred', + title: _(msg`An unknown error occurred`), variant: 'destructive', - description: - 'We encountered an unknown error while attempting to update the banner. Please try again later.', + description: _( + msg`We encountered an unknown error while attempting to update the banner. Please try again later.`, + ), }); } } @@ -93,10 +98,14 @@ export function BannerForm({ banner }: BannerFormProps) { return (
-

Site Banner

+

+ Site Banner +

- The site banner is a message that is shown at the top of the site. It can be used to display - important information to your users. + + The site banner is a message that is shown at the top of the site. It can be used to + display important information to your users. +

@@ -110,7 +119,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="enabled" render={({ field }) => ( - Enabled + + Enabled +
@@ -131,7 +142,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.bgColor" render={({ field }) => ( - Background Color + + Background Color +
@@ -149,7 +162,9 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.textColor" render={({ field }) => ( - Text Color + + Text Color +
@@ -170,14 +185,16 @@ export function BannerForm({ banner }: BannerFormProps) { name="data.content" render={({ field }) => ( - Content + + Content +