From 9186cb4d7b220eb9e65c307fe0f7709286d0b182 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sat, 9 Sep 2023 10:42:03 +0000 Subject: [PATCH 1/7] fix: hide popover when user selects a recipients --- .../primitives/document-flow/add-fields.tsx | 83 ++++++++++--------- packages/ui/primitives/popover.tsx | 4 +- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index c56e91b49..62daf803b 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -22,7 +22,12 @@ import { CommandInput, CommandItem, } from '@documenso/ui/primitives/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; +import { + Popover, + PopoverClose, + PopoverContent, + PopoverTrigger, +} from '@documenso/ui/primitives/popover'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { TAddFieldsFormSchema } from './add-fields.types'; @@ -324,7 +329,7 @@ export const AddFieldsFormPartial = ({ > {selectedSigner?.email && ( - {selectedSigner?.email} ({selectedSigner?.email}) + {selectedSigner?.name} ({selectedSigner?.email}) )} @@ -345,45 +350,49 @@ export const AddFieldsFormPartial = ({ {recipients.map((recipient, index) => ( setSelectedSigner(recipient)} > - {recipient.sendStatus !== SendStatus.SENT ? ( - - ) : ( - - - - - - This document has already been sent to this recipient. You can no - longer edit this recipient. - - - )} + + {recipient.sendStatus !== SendStatus.SENT ? ( + + ) : ( + + + + + + This document has already been sent to this recipient. You can no + longer edit this recipient. + + + )} - {recipient.name && ( - - {recipient.name} ({recipient.email}) - - )} + {recipient.name && ( + + {recipient.name} ({recipient.email}) + + )} - {!recipient.name && ( - - {recipient.email} - - )} + {!recipient.name && ( + + {recipient.email} + + )} + ))} diff --git a/packages/ui/primitives/popover.tsx b/packages/ui/primitives/popover.tsx index e84f6cc6d..640b3087e 100644 --- a/packages/ui/primitives/popover.tsx +++ b/packages/ui/primitives/popover.tsx @@ -10,6 +10,8 @@ const Popover = PopoverPrimitive.Root; const PopoverTrigger = PopoverPrimitive.Trigger; +const PopoverClose = PopoverPrimitive.Close; + const PopoverContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -30,4 +32,4 @@ const PopoverContent = React.forwardRef< PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverTrigger, PopoverContent }; +export { Popover, PopoverTrigger, PopoverContent, PopoverClose }; From f8a193c0f8906cf9c042ed24b06c6a15bc9bf717 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sat, 9 Sep 2023 10:56:45 +0000 Subject: [PATCH 2/7] refactor: replace whole implementation with a state --- .../primitives/document-flow/add-fields.tsx | 89 +++++++++---------- packages/ui/primitives/popover.tsx | 4 +- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 62daf803b..5de43c411 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -22,12 +22,7 @@ import { CommandInput, CommandItem, } from '@documenso/ui/primitives/command'; -import { - Popover, - PopoverClose, - PopoverContent, - PopoverTrigger, -} from '@documenso/ui/primitives/popover'; +import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { TAddFieldsFormSchema } from './add-fields.types'; @@ -107,6 +102,7 @@ export const AddFieldsFormPartial = ({ const [selectedField, setSelectedField] = useState(null); const [selectedSigner, setSelectedSigner] = useState(null); + const [showRecipientsSelector, setShowRecipientsSelector] = useState(false); const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT; @@ -319,7 +315,7 @@ export const AddFieldsFormPartial = ({ ))} {!hideRecipients && ( - + diff --git a/packages/lib/client-only/recipient-avatar-fallback.ts b/packages/lib/client-only/recipient-avatar-fallback.ts new file mode 100644 index 000000000..7a296a5fa --- /dev/null +++ b/packages/lib/client-only/recipient-avatar-fallback.ts @@ -0,0 +1,7 @@ +import { Recipient } from '@documenso/prisma/client'; + +import { initials } from './recipient-initials'; + +export const recipientAvatarFallback = (recipient: Recipient) => { + return initials(recipient.name) || recipient.email.slice(0, 1).toUpperCase(); +}; diff --git a/packages/lib/client-only/recipient-initials.ts b/packages/lib/client-only/recipient-initials.ts index 0712ccd7d..403ed26e4 100644 --- a/packages/lib/client-only/recipient-initials.ts +++ b/packages/lib/client-only/recipient-initials.ts @@ -3,4 +3,4 @@ export const initials = (text: string) => ?.split(' ') .map((name: string) => name.slice(0, 1).toUpperCase()) .slice(0, 2) - .join('') ?? 'UK'; + .join(''); From 24036b0f24afecdb56c659753c1a3fee7fb8040f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fl=C5=8D?= Date: Tue, 22 Aug 2023 08:53:21 +0200 Subject: [PATCH 4/7] fix typo --- apps/marketing/content/blog/next.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/marketing/content/blog/next.mdx b/apps/marketing/content/blog/next.mdx index 4f846a0a7..c241cf3eb 100644 --- a/apps/marketing/content/blog/next.mdx +++ b/apps/marketing/content/blog/next.mdx @@ -12,7 +12,7 @@ tags: Since we launched [Documenso 0.9 on Product Hunt](https://producthunt.com/products/documenso#documenso) last May, the team's been hard at work behind the scenes to ramp up development and design to deliver an excellent next version. -Last week, Lucas shared the reasoning how [why we're doing a rewrite](https://documenso.com/blog/why-were-doing-a-rewrite). +Last week, Lucas shared the reasoning on [why we're doing a rewrite](https://documenso.com/blog/why-were-doing-a-rewrite). Today, I'm pleased to share with you a preview of the next Documenso. From db3f75c42f244734786a87b0721abba70f3f2d9f Mon Sep 17 00:00:00 2001 From: Mythie Date: Tue, 12 Sep 2023 10:37:47 +1000 Subject: [PATCH 5/7] fix: data table links for recipients --- .../documents/data-table-title.tsx | 56 +++++++++++++++++++ .../app/(dashboard)/documents/data-table.tsx | 19 +++---- 2 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/documents/data-table-title.tsx diff --git a/apps/web/src/app/(dashboard)/documents/data-table-title.tsx b/apps/web/src/app/(dashboard)/documents/data-table-title.tsx new file mode 100644 index 000000000..c04f9f13d --- /dev/null +++ b/apps/web/src/app/(dashboard)/documents/data-table-title.tsx @@ -0,0 +1,56 @@ +'use client'; + +import Link from 'next/link'; + +import { useSession } from 'next-auth/react'; +import { match } from 'ts-pattern'; + +import { Document, Recipient, User } from '@documenso/prisma/client'; + +export type DataTableTitleProps = { + row: Document & { + User: Pick; + Recipient: Recipient[]; + }; +}; + +export const DataTableTitle = ({ row }: DataTableTitleProps) => { + const { data: session } = useSession(); + + if (!session) { + return null; + } + + const recipient = row.Recipient.find((recipient) => recipient.email === session.user.email); + + const isOwner = row.User.id === session.user.id; + const isRecipient = !!recipient; + + return match({ + isOwner, + isRecipient, + }) + .with({ isOwner: true }, () => ( + + {row.title} + + )) + .with({ isRecipient: true }, () => ( + + {row.title} + + )) + .otherwise(() => ( + + {row.title} + + )); +}; diff --git a/apps/web/src/app/(dashboard)/documents/data-table.tsx b/apps/web/src/app/(dashboard)/documents/data-table.tsx index 1d6c08e73..245734a8e 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table.tsx @@ -2,9 +2,8 @@ import { useTransition } from 'react'; -import Link from 'next/link'; - import { Loader } from 'lucide-react'; +import { useSession } from 'next-auth/react'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; import { FindResultSet } from '@documenso/lib/types/find-result-set'; @@ -18,6 +17,7 @@ import { LocaleDate } from '~/components/formatter/locale-date'; import { DataTableActionButton } from './data-table-action-button'; import { DataTableActionDropdown } from './data-table-action-dropdown'; +import { DataTableTitle } from './data-table-title'; export type DocumentsDataTableProps = { results: FindResultSet< @@ -29,6 +29,7 @@ export type DocumentsDataTableProps = { }; export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { + const { data: session } = useSession(); const [isPending, startTransition] = useTransition(); const updateSearchParams = useUpdateSearchParams(); @@ -42,6 +43,10 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { }); }; + if (!session) { + return null; + } + return (
{ }, { header: 'Title', - cell: ({ row }) => ( - - {row.original.title} - - ), + cell: ({ row }) => , }, { header: 'Recipient', From e8796a7d86cdf9be56da6fc8b88cb12360b506ab Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 12 Sep 2023 12:33:04 +1000 Subject: [PATCH 6/7] refactor: organise recipient utils --- .../avatar/stack-avatars-with-tooltip.tsx | 10 +++++----- .../components/(dashboard)/avatar/stack-avatars.tsx | 4 ++-- .../(dashboard)/layout/profile-dropdown.tsx | 6 ++++-- .../lib/client-only/recipient-avatar-fallback.ts | 7 ------- packages/lib/client-only/recipient-initials.ts | 6 ------ packages/lib/utils/recipient-formatter.ts | 12 ++++++++++++ 6 files changed, 23 insertions(+), 22 deletions(-) delete mode 100644 packages/lib/client-only/recipient-avatar-fallback.ts delete mode 100644 packages/lib/client-only/recipient-initials.ts create mode 100644 packages/lib/utils/recipient-formatter.ts diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx index 3f6407029..e36415813 100644 --- a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx +++ b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx @@ -1,5 +1,5 @@ -import { recipientAvatarFallback } from '@documenso/lib/client-only/recipient-avatar-fallback'; import { getRecipientType } from '@documenso/lib/client-only/recipient-type'; +import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; import { Recipient } from '@documenso/prisma/client'; import { Tooltip, @@ -56,7 +56,7 @@ export const StackAvatarsWithTooltip = ({ first={true} key={recipient.id} type={getRecipientType(recipient)} - fallbackText={recipientAvatarFallback(recipient)} + fallbackText={recipientAbbreviation(recipient)} /> {recipient.email}
@@ -73,7 +73,7 @@ export const StackAvatarsWithTooltip = ({ first={true} key={recipient.id} type={getRecipientType(recipient)} - fallbackText={recipientAvatarFallback(recipient)} + fallbackText={recipientAbbreviation(recipient)} /> {recipient.email} @@ -90,7 +90,7 @@ export const StackAvatarsWithTooltip = ({ first={true} key={recipient.id} type={getRecipientType(recipient)} - fallbackText={recipientAvatarFallback(recipient)} + fallbackText={recipientAbbreviation(recipient)} /> {recipient.email} @@ -107,7 +107,7 @@ export const StackAvatarsWithTooltip = ({ first={true} key={recipient.id} type={getRecipientType(recipient)} - fallbackText={recipientAvatarFallback(recipient)} + fallbackText={recipientAbbreviation(recipient)} /> {recipient.email} diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatars.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatars.tsx index 678836ffd..91f470f74 100644 --- a/apps/web/src/components/(dashboard)/avatar/stack-avatars.tsx +++ b/apps/web/src/components/(dashboard)/avatar/stack-avatars.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { recipientAvatarFallback } from '@documenso/lib/client-only/recipient-avatar-fallback'; import { getRecipientType } from '@documenso/lib/client-only/recipient-type'; +import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter'; import { Recipient } from '@documenso/prisma/client'; import { StackAvatar } from './stack-avatar'; @@ -26,7 +26,7 @@ export function StackAvatars({ recipients }: { recipients: Recipient[] }) { first={first} zIndex={String(zIndex - index * 10)} type={lastItemText && index === 4 ? 'unsigned' : getRecipientType(recipient)} - fallbackText={lastItemText ? lastItemText : recipientAvatarFallback(recipient)} + fallbackText={lastItemText ? lastItemText : recipientAbbreviation(recipient)} /> ); }); diff --git a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx index e52d9b42f..3b361e885 100644 --- a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx +++ b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx @@ -15,7 +15,7 @@ import { import { signOut } from 'next-auth/react'; import { useTheme } from 'next-themes'; -import { initials } from '@documenso/lib/client-only/recipient-initials'; +import { recipientInitials } from '@documenso/lib/utils/recipient-formatter'; import { User } from '@documenso/prisma/client'; import { Avatar, AvatarFallback } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; @@ -41,7 +41,9 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => { const isBillingEnabled = getFlag('app_billing'); - const avatarFallback = user.name ? initials(user.name) : user.email.slice(0, 1).toUpperCase(); + const avatarFallback = user.name + ? recipientInitials(user.name) + : user.email.slice(0, 1).toUpperCase(); return ( diff --git a/packages/lib/client-only/recipient-avatar-fallback.ts b/packages/lib/client-only/recipient-avatar-fallback.ts deleted file mode 100644 index 7a296a5fa..000000000 --- a/packages/lib/client-only/recipient-avatar-fallback.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Recipient } from '@documenso/prisma/client'; - -import { initials } from './recipient-initials'; - -export const recipientAvatarFallback = (recipient: Recipient) => { - return initials(recipient.name) || recipient.email.slice(0, 1).toUpperCase(); -}; diff --git a/packages/lib/client-only/recipient-initials.ts b/packages/lib/client-only/recipient-initials.ts deleted file mode 100644 index 403ed26e4..000000000 --- a/packages/lib/client-only/recipient-initials.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const initials = (text: string) => - text - ?.split(' ') - .map((name: string) => name.slice(0, 1).toUpperCase()) - .slice(0, 2) - .join(''); diff --git a/packages/lib/utils/recipient-formatter.ts b/packages/lib/utils/recipient-formatter.ts new file mode 100644 index 000000000..da404830b --- /dev/null +++ b/packages/lib/utils/recipient-formatter.ts @@ -0,0 +1,12 @@ +import { Recipient } from '@documenso/prisma/client'; + +export const recipientInitials = (text: string) => + text + .split(' ') + .map((name: string) => name.slice(0, 1).toUpperCase()) + .slice(0, 2) + .join(''); + +export const recipientAbbreviation = (recipient: Recipient) => { + return recipientInitials(recipient.name) || recipient.email.slice(0, 1).toUpperCase(); +}; From 24a2e9e6d4fb254a03a6f300996b3518678e68ee Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 12 Sep 2023 14:29:27 +1000 Subject: [PATCH 7/7] feat: update document table layout (#371) * feat: update document table layout - Removed dashboard page - Removed redundant ID column - Moved date to first column - Added estimated locales for SSR dates --- .../src/app/(dashboard)/dashboard/page.tsx | 124 ------------------ .../documents/[id]/edit-document.tsx | 2 +- .../app/(dashboard)/documents/data-table.tsx | 12 +- .../src/app/(dashboard)/documents/page.tsx | 3 +- .../upload-document.tsx | 0 apps/web/src/app/layout.tsx | 26 ++-- .../src/components/formatter/locale-date.tsx | 23 +++- apps/web/src/components/forms/signin.tsx | 16 +-- packages/lib/client-only/providers/locale.tsx | 37 ++++++ .../ui/primitives/data-table-pagination.tsx | 33 ++++- 10 files changed, 117 insertions(+), 159 deletions(-) delete mode 100644 apps/web/src/app/(dashboard)/dashboard/page.tsx rename apps/web/src/app/(dashboard)/{dashboard => documents}/upload-document.tsx (100%) create mode 100644 packages/lib/client-only/providers/locale.tsx diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx deleted file mode 100644 index 77b18b98c..000000000 --- a/apps/web/src/app/(dashboard)/dashboard/page.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import Link from 'next/link'; - -import { Clock, File, FileCheck } from 'lucide-react'; - -import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; -import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; -import { getStats } from '@documenso/lib/server-only/document/get-stats'; -import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client'; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@documenso/ui/primitives/table'; - -import { StackAvatarsWithTooltip } from '~/components/(dashboard)/avatar/stack-avatars-with-tooltip'; -import { CardMetric } from '~/components/(dashboard)/metric-card/metric-card'; -import { DocumentStatus } from '~/components/formatter/document-status'; -import { LocaleDate } from '~/components/formatter/locale-date'; - -import { UploadDocument } from './upload-document'; - -const CARD_DATA = [ - { - icon: FileCheck, - title: 'Completed', - status: InternalDocumentStatus.COMPLETED, - }, - { - icon: File, - title: 'Drafts', - status: InternalDocumentStatus.DRAFT, - }, - { - icon: Clock, - title: 'Pending', - status: InternalDocumentStatus.PENDING, - }, -]; - -export default async function DashboardPage() { - const user = await getRequiredServerComponentSession(); - - const [stats, results] = await Promise.all([ - getStats({ - user, - }), - findDocuments({ - userId: user.id, - perPage: 10, - }), - ]); - - return ( -
-

Dashboard

- -
- {CARD_DATA.map((card) => ( - - - - ))} -
- -
- - -

Recent Documents

- -
- - - - ID - Title - Reciepient - Status - Created - - - - {results.data.map((document) => { - return ( - - {document.id} - - - {document.title} - - - - - - - - - - - - - - - ); - })} - {results.data.length === 0 && ( - - - No results. - - - )} - -
-
-
-
- ); -} diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 7ed28feca..ba134ac58 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -136,7 +136,7 @@ export const EditDocumentForm = ({ duration: 5000, }); - router.push('/dashboard'); + router.push('/documents'); } catch (err) { console.error(err); diff --git a/apps/web/src/app/(dashboard)/documents/data-table.tsx b/apps/web/src/app/(dashboard)/documents/data-table.tsx index 245734a8e..b8c735b59 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table.tsx @@ -52,8 +52,9 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { , }, { header: 'Title', @@ -71,11 +72,6 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { accessorKey: 'status', cell: ({ row }) => , }, - { - header: 'Created', - accessorKey: 'created', - cell: ({ row }) => , - }, { header: 'Actions', cell: ({ row }) => ( @@ -92,7 +88,7 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { totalPages={results.totalPages} onPaginationChange={onPaginationChange} > - {(table) => } + {(table) => } {isPending && ( diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index 4ea55936b..d1f558806 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -11,8 +11,8 @@ import { PeriodSelector } from '~/components/(dashboard)/period-selector/period- import { PeriodSelectorValue } from '~/components/(dashboard)/period-selector/types'; import { DocumentStatus } from '~/components/formatter/document-status'; -import { UploadDocument } from '../dashboard/upload-document'; import { DocumentsDataTable } from './data-table'; +import { UploadDocument } from './upload-document'; export type DocumentsPageProps = { searchParams?: { @@ -81,6 +81,7 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage {value !== ExtendedDocumentStatus.ALL && ( {Math.min(stats[value], 99)} + {stats[value] > 99 && '+'} )} diff --git a/apps/web/src/app/(dashboard)/dashboard/upload-document.tsx b/apps/web/src/app/(dashboard)/documents/upload-document.tsx similarity index 100% rename from apps/web/src/app/(dashboard)/dashboard/upload-document.tsx rename to apps/web/src/app/(dashboard)/documents/upload-document.tsx diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 1d1e056ae..2ce8744d4 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -2,6 +2,8 @@ import { Suspense } from 'react'; import { Caveat, Inter } from 'next/font/google'; +import { LocaleProvider } from '@documenso/lib/client-only/providers/locale'; +import { getLocale } from '@documenso/lib/server-only/headers/get-locale'; import { TrpcProvider } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Toaster } from '@documenso/ui/primitives/toaster'; @@ -45,6 +47,8 @@ export const metadata = { export default async function RootLayout({ children }: { children: React.ReactNode }) { const flags = await getServerComponentAllFlags(); + const locale = getLocale(); + return ( - - - - - {children} - - - - - + + + + + + {children} + + + + + + ); diff --git a/apps/web/src/components/formatter/locale-date.tsx b/apps/web/src/components/formatter/locale-date.tsx index 837c6aa38..ecefb1e3b 100644 --- a/apps/web/src/components/formatter/locale-date.tsx +++ b/apps/web/src/components/formatter/locale-date.tsx @@ -2,16 +2,31 @@ import { HTMLAttributes, useEffect, useState } from 'react'; +import { DateTime, DateTimeFormatOptions } from 'luxon'; + +import { useLocale } from '@documenso/lib/client-only/providers/locale'; + export type LocaleDateProps = HTMLAttributes & { date: string | number | Date; + format?: DateTimeFormatOptions; }; -export const LocaleDate = ({ className, date, ...props }: LocaleDateProps) => { - const [localeDate, setLocaleDate] = useState(() => new Date(date).toISOString()); +/** + * Formats the date based on the user locale. + * + * Will use the estimated locale from the user headers on SSR, then will use + * the client browser locale once mounted. + */ +export const LocaleDate = ({ className, date, format, ...props }: LocaleDateProps) => { + const { locale } = useLocale(); + + const [localeDate, setLocaleDate] = useState(() => + DateTime.fromJSDate(new Date(date)).setLocale(locale).toLocaleString(format), + ); useEffect(() => { - setLocaleDate(new Date(date).toLocaleString()); - }, [date]); + setLocaleDate(DateTime.fromJSDate(new Date(date)).toLocaleString(format)); + }, [date, format]); return ( diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx index 5e44146ea..d9d727afc 100644 --- a/apps/web/src/components/forms/signin.tsx +++ b/apps/web/src/components/forms/signin.tsx @@ -18,13 +18,15 @@ import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { useToast } from '@documenso/ui/primitives/use-toast'; -const ErrorMessages = { +const ERROR_MESSAGES = { [ErrorCode.CREDENTIALS_NOT_FOUND]: 'The email or password provided is incorrect', [ErrorCode.INCORRECT_EMAIL_PASSWORD]: 'The email or password provided is incorrect', [ErrorCode.USER_MISSING_PASSWORD]: 'This account appears to be using a social login method, please sign in using that method', }; +const LOGIN_REDIRECT_PATH = '/documents'; + export const ZSignInFormSchema = z.object({ email: z.string().email().min(1), password: z.string().min(6).max(72), @@ -37,9 +39,10 @@ export type SignInFormProps = { }; export const SignInForm = ({ className }: SignInFormProps) => { - const { toast } = useToast(); const searchParams = useSearchParams(); + const { toast } = useToast(); + const { register, handleSubmit, @@ -61,7 +64,7 @@ export const SignInForm = ({ className }: SignInFormProps) => { timeout = setTimeout(() => { toast({ variant: 'destructive', - description: ErrorMessages[errorCode] ?? 'An unknown error occurred', + description: ERROR_MESSAGES[errorCode] ?? 'An unknown error occurred', }); }, 0); } @@ -78,12 +81,10 @@ export const SignInForm = ({ className }: SignInFormProps) => { await signIn('credentials', { email, password, - callbackUrl: '/documents', + callbackUrl: LOGIN_REDIRECT_PATH, }).catch((err) => { console.error(err); }); - - // throw new Error('Not implemented'); } catch (err) { toast({ title: 'An unknown error occurred', @@ -95,8 +96,7 @@ export const SignInForm = ({ className }: SignInFormProps) => { const onSignInWithGoogleClick = async () => { try { - await signIn('google', { callbackUrl: '/dashboard' }); - // throw new Error('Not implemented'); + await signIn('google', { callbackUrl: LOGIN_REDIRECT_PATH }); } catch (err) { toast({ title: 'An unknown error occurred', diff --git a/packages/lib/client-only/providers/locale.tsx b/packages/lib/client-only/providers/locale.tsx new file mode 100644 index 000000000..ff8b03e5a --- /dev/null +++ b/packages/lib/client-only/providers/locale.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { createContext, useContext } from 'react'; + +export type LocaleContextValue = { + locale: string; +}; + +export const LocaleContext = createContext(null); + +export const useLocale = () => { + const context = useContext(LocaleContext); + + if (!context) { + throw new Error('useLocale must be used within a LocaleProvider'); + } + + return context; +}; + +export function LocaleProvider({ + children, + locale, +}: { + children: React.ReactNode; + locale: string; +}) { + return ( + + {children} + + ); +} diff --git a/packages/ui/primitives/data-table-pagination.tsx b/packages/ui/primitives/data-table-pagination.tsx index 0ff27ae11..8147c92fb 100644 --- a/packages/ui/primitives/data-table-pagination.tsx +++ b/packages/ui/primitives/data-table-pagination.tsx @@ -1,19 +1,46 @@ import { Table } from '@tanstack/react-table'; import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; +import { match } from 'ts-pattern'; import { Button } from './button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './select'; interface DataTablePaginationProps { table: Table; + + /** + * The type of information to show on the left hand side of the pagination. + * + * Defaults to 'VisibleCount'. + */ + additionalInformation?: 'SelectedCount' | 'VisibleCount' | 'None'; } -export function DataTablePagination({ table }: DataTablePaginationProps) { +export function DataTablePagination({ + table, + additionalInformation = 'VisibleCount', +}: DataTablePaginationProps) { return (
- {table.getFilteredSelectedRowModel().rows.length} of{' '} - {table.getFilteredRowModel().rows.length} row(s) selected. + {match(additionalInformation) + .with('SelectedCount', () => ( + + {table.getFilteredSelectedRowModel().rows.length} of{' '} + {table.getFilteredRowModel().rows.length} row(s) selected. + + )) + .with('VisibleCount', () => { + const visibleRows = table.getFilteredRowModel().rows.length; + + return ( + + Showing {visibleRows} result{visibleRows > 1 && 's'}. + + ); + }) + .with('None', () => null) + .exhaustive()}