diff --git a/apps/remix/package.json b/apps/remix/package.json index 1f69bf91a..bbbe66ed0 100644 --- a/apps/remix/package.json +++ b/apps/remix/package.json @@ -25,26 +25,45 @@ "@hono/node-server": "^1.13.7", "@hono/trpc-server": "^0.3.4", "@hono/zod-validator": "^0.4.2", + "@hookform/resolvers": "^3.1.0", "@lingui/core": "^4.11.3", "@lingui/detect-locale": "^4.11.1", "@lingui/macro": "^4.11.3", "@lingui/react": "^4.11.3", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.3", "@react-router/node": "^7.1.5", "@react-router/serve": "^7.1.5", "autoprefixer": "^10.4.13", + "colord": "^2.9.3", "framer-motion": "^10.12.8", "hono": "4.6.15", "hono-react-router-adapter": "^0.6.2", + "input-otp": "^1.2.4", + "luxon": "^3.4.0", + "lucide-react": "^0.279.0", "isbot": "^5.1.17", "jsonwebtoken": "^9.0.2", + "papaparse": "^5.4.1", "posthog-js": "^1.75.3", "posthog-node": "^3.1.1", "react": "^18", "react-dom": "^18", "react-router": "^7.1.5", + "react-call": "^1.3.0", + "react-dropzone": "^14.2.3", + "react-hook-form": "^7.43.9", + "react-hotkeys-hook": "^4.4.1", + "react-icons": "^4.11.0", + "react-rnd": "^10.4.1", + "recharts": "^2.7.2", + "remeda": "^2.17.3", "remix-themes": "^2.0.4", + "sharp": "0.32.6", + "uqr": "^0.1.2", + "ua-parser-js": "^1.0.37", "tailwindcss": "^3.4.15", "ts-pattern": "^5.0.5" }, @@ -55,6 +74,10 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@simplewebauthn/types": "^9.0.1", + "@types/formidable": "^2.0.6", + "@types/luxon": "^3.3.1", + "@types/ua-parser-js": "^0.7.39", "cross-env": "^7.0.3", "remix-flat-routes": "^0.8.4", "tsx": "^4.11.0", @@ -63,4 +86,4 @@ "vite-plugin-babel-macros": "^1.0.6", "vite-tsconfig-paths": "^5.1.4" } -} +} \ No newline at end of file diff --git a/apps/web/README.md b/apps/web/README.md deleted file mode 100644 index 2df72cac5..000000000 --- a/apps/web/README.md +++ /dev/null @@ -1 +0,0 @@ -# @documenso/web diff --git a/apps/web/ambient.d.ts b/apps/web/ambient.d.ts deleted file mode 100644 index 54b8c1d7c..000000000 --- a/apps/web/ambient.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@documenso/tailwind-config'; diff --git a/apps/web/example/cert.p12 b/apps/web/example/cert.p12 deleted file mode 100644 index 532ee19ab..000000000 Binary files a/apps/web/example/cert.p12 and /dev/null differ diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts deleted file mode 100644 index fd36f9494..000000000 --- a/apps/web/next-env.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/// -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/web/next.config.js b/apps/web/next.config.js deleted file mode 100644 index 5b8f3e60b..000000000 --- a/apps/web/next.config.js +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const fs = require('fs'); -const path = require('path'); -const { version } = require('./package.json'); -const { withAxiom } = require('next-axiom'); - -const ENV_FILES = ['.env', '.env.local', `.env.${process.env.NODE_ENV || 'development'}`]; - -ENV_FILES.forEach((file) => { - require('dotenv').config({ - path: path.join(__dirname, `../../${file}`), - }); -}); - -// !: This is a temp hack to get caveat working without placing it back in the public directory. -// !: By inlining this at build time we should be able to sign faster. -const FONT_CAVEAT_BYTES = fs.readFileSync( - path.join(__dirname, '../../packages/assets/fonts/caveat.ttf'), -); - -const FONT_NOTO_SANS_BYTES = fs.readFileSync( - path.join(__dirname, '../../packages/assets/fonts/noto-sans.ttf'), -); - -/** @type {import('next').NextConfig} */ -const config = { - output: process.env.DOCKER_OUTPUT ? 'standalone' : undefined, - experimental: { - outputFileTracingRoot: path.join(__dirname, '../../'), - serverComponentsExternalPackages: ['@node-rs/bcrypt', '@documenso/pdf-sign', 'playwright'], - serverActions: { - bodySizeLimit: '50mb', - }, - swcPlugins: [['@lingui/swc-plugin', {}]], - }, - reactStrictMode: true, - transpilePackages: [ - '@documenso/assets', - '@documenso/ee', - '@documenso/lib', - '@documenso/prisma', - '@documenso/tailwind-config', - '@documenso/trpc', - '@documenso/ui', - ], - env: { - APP_VERSION: version, - NEXT_PUBLIC_PROJECT: 'web', - FONT_CAVEAT_URI: `data:font/ttf;base64,${FONT_CAVEAT_BYTES.toString('base64')}`, - FONT_NOTO_SANS_URI: `data:font/ttf;base64,${FONT_NOTO_SANS_BYTES.toString('base64')}`, - }, - modularizeImports: { - 'lucide-react': { - transform: 'lucide-react/dist/esm/icons/{{ kebabCase member }}', - }, - }, - webpack: (config, { isServer }) => { - // fixes: Module not found: Can’t resolve ‘../build/Release/canvas.node’ - if (isServer) { - config.resolve.alias.canvas = false; - } - - config.module.rules.push({ - test: /\.po$/, - use: { - loader: '@lingui/loader', - }, - }); - - return config; - }, - async rewrites() { - return [ - { - source: '/ingest/:path*', - destination: 'https://eu.posthog.com/:path*', - }, - ]; - }, - async redirects() { - return [ - { - permanent: true, - source: '/documents/:id/sign', - destination: '/sign/:token', - has: [ - { - type: 'query', - key: 'token', - }, - ], - }, - { - permanent: true, - source: '/documents/:id/signed', - destination: '/sign/:token', - has: [ - { - type: 'query', - key: 'token', - }, - ], - }, - ]; - }, -}; - -module.exports = withAxiom(config); diff --git a/apps/web/package.json b/apps/web/package.json deleted file mode 100644 index 9f5053e1e..000000000 --- a/apps/web/package.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "name": "@documenso/web", - "version": "1.9.0-rc.11", - "private": true, - "license": "AGPL-3.0", - "scripts": { - "dev": "next dev -p 3000", - "build": "npm run translate --prefix ../../ && next build", - "start": "next start", - "lint": "next lint", - "e2e:prepare": "next build && next start", - "lint:fix": "next lint --fix", - "clean": "rimraf .next && rimraf node_modules", - "copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs" - }, - "dependencies": { - "@documenso/api": "*", - "@documenso/assets": "*", - "@documenso/ee": "*", - "@documenso/lib": "*", - "@documenso/prisma": "*", - "@documenso/trpc": "*", - "@documenso/ui": "*", - "@hookform/resolvers": "^3.1.0", - "@lingui/macro": "^4.11.3", - "@lingui/react": "^4.11.3", - "@simplewebauthn/browser": "^9.0.1", - "@simplewebauthn/server": "^9.0.3", - "colord": "^2.9.3", - "cookie-es": "^1.0.0", - "formidable": "^2.1.1", - "framer-motion": "^10.12.8", - "input-otp": "^1.2.4", - "lucide-react": "^0.279.0", - "luxon": "^3.4.0", - "micro": "^10.0.1", - "next": "14.2.6", - "next-auth": "4.24.5", - "next-axiom": "^1.5.1", - "next-plausible": "^3.10.1", - "next-themes": "^0.2.1", - "papaparse": "^5.4.1", - "perfect-freehand": "^1.2.0", - "posthog-js": "^1.75.3", - "posthog-node": "^3.1.1", - "react": "^18", - "react-call": "^1.3.0", - "react-dom": "^18", - "react-dropzone": "^14.2.3", - "react-hook-form": "^7.43.9", - "react-hotkeys-hook": "^4.4.1", - "react-icons": "^4.11.0", - "react-rnd": "^10.4.1", - "recharts": "^2.7.2", - "remeda": "^2.17.3", - "sharp": "0.32.6", - "trpc-to-openapi": "2.0.4", - "ts-pattern": "^5.0.5", - "ua-parser-js": "^1.0.37", - "uqr": "^0.1.2", - "zod": "3.24.1" - }, - "devDependencies": { - "@documenso/tailwind-config": "*", - "@lingui/loader": "^4.11.3", - "@lingui/swc-plugin": "4.0.8", - "@simplewebauthn/types": "^9.0.1", - "@types/formidable": "^2.0.6", - "@types/luxon": "^3.3.1", - "@types/node": "^20", - "@types/papaparse": "^5.3.14", - "@types/react": "^18", - "@types/react-dom": "^18", - "@types/ua-parser-js": "^0.7.39", - "typescript": "5.6.2" - } -} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js deleted file mode 100644 index 12a703d90..000000000 --- a/apps/web/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/web/process-env.d.ts b/apps/web/process-env.d.ts deleted file mode 100644 index ae9995df7..000000000 --- a/apps/web/process-env.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -declare namespace NodeJS { - export interface ProcessEnv { - NEXT_PUBLIC_WEBAPP_URL?: string; - NEXT_PUBLIC_MARKETING_URL?: string; - NEXT_PRIVATE_INTERNAL_WEBAPP_URL?:string; - - NEXT_PRIVATE_DATABASE_URL: string; - - NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID: string; - - NEXT_PRIVATE_STRIPE_API_KEY: string; - NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: string; - - NEXT_PRIVATE_GOOGLE_CLIENT_ID: string; - NEXT_PRIVATE_GOOGLE_CLIENT_SECRET: string; - - NEXT_PRIVATE_OIDC_WELL_KNOWN: string; - NEXT_PRIVATE_OIDC_CLIENT_ID: string; - NEXT_PRIVATE_OIDC_CLIENT_SECRET: string; - NEXT_PRIVATE_OIDC_ALLOW_SIGNUP?: string; - NEXT_PRIVATE_OIDC_SKIP_VERIFY?: string; - } -} 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 deleted file mode 100644 index 214cacd38..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/admin-actions.tsx +++ /dev/null @@ -1,81 +0,0 @@ -'use client'; - -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'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@documenso/ui/primitives/tooltip'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type AdminActionsProps = { - className?: string; - document: Document; - recipients: Recipient[]; -}; - -export const AdminActions = ({ className, document, recipients }: AdminActionsProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const { mutate: resealDocument, isPending: isResealDocumentLoading } = - trpc.admin.resealDocument.useMutation({ - onSuccess: () => { - toast({ - title: _(msg`Success`), - description: _(msg`Document resealed`), - }); - }, - onError: () => { - toast({ - title: _(msg`Error`), - description: _(msg`Failed to reseal document`), - variant: 'destructive', - }); - }, - }); - - return ( -
- - - - - - - - - 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 deleted file mode 100644 index 524dd1f1e..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/page.tsx +++ /dev/null @@ -1,99 +0,0 @@ -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, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@documenso/ui/primitives/accordion'; -import { Badge } from '@documenso/ui/primitives/badge'; - -import { DocumentStatus } from '~/components/formatter/document-status'; - -import { AdminActions } from './admin-actions'; -import { RecipientItem } from './recipient-item'; -import { SuperDeleteDocumentDialog } from './super-delete-document-dialog'; - -type AdminDocumentDetailsPageProps = { - params: { - id: string; - }; -}; - -export default async function AdminDocumentDetailsPage({ params }: AdminDocumentDetailsPageProps) { - const { i18n } = await setupI18nSSR(); - - const document = await getEntireDocument({ id: Number(params.id) }); - - return ( -
-
-
-

{document.title}

- -
- - {document.deletedAt && ( - - Deleted - - )} -
- -
-
- Created on: {i18n.date(document.createdAt, DateTime.DATETIME_MED)} -
- -
- Last updated at: {i18n.date(document.updatedAt, DateTime.DATETIME_MED)} -
-
- -
- -

- Admin Actions -

- - - -
-

- Recipients -

- -
- - {document.recipients.map((recipient) => ( - - -
-

{recipient.name}

- - {recipient.email} - -
-
- - - - -
- ))} -
-
- -
- - {document && } -
- ); -} 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 deleted file mode 100644 index 8696dab06..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/recipient-item.tsx +++ /dev/null @@ -1,196 +0,0 @@ -'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'; - -import { - type Field, - type Recipient, - type Signature, - SigningStatus, -} 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, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@documenso/ui/primitives/form/form'; -import { Input } from '@documenso/ui/primitives/input'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -const ZAdminUpdateRecipientFormSchema = z.object({ - name: z.string().min(1), - email: z.string().email(), -}); - -type TAdminUpdateRecipientFormSchema = z.infer; - -export type RecipientItemProps = { - recipient: Recipient & { - fields: Array< - Field & { - signature: Signature | null; - } - >; - }; -}; - -export const RecipientItem = ({ recipient }: RecipientItemProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const router = useRouter(); - - const form = useForm({ - defaultValues: { - name: recipient.name, - email: recipient.email, - }, - }); - - 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)['fields'][number]>[]; - }, []); - - const onUpdateRecipientFormSubmit = async ({ name, email }: TAdminUpdateRecipientFormSchema) => { - try { - await updateRecipient({ - id: recipient.id, - name, - email, - }); - - toast({ - title: _(msg`Recipient updated`), - description: _(msg`The recipient has been updated successfully`), - }); - - router.refresh(); - } catch (error) { - toast({ - title: _(msg`Failed to update recipient`), - description: error.message, - variant: 'destructive', - }); - } - }; - - return ( -
-
- -
- ( - - - Name - - - - - - - - - )} - /> - - ( - - - Email - - - - - - - - - )} - /> - -
- -
-
-
- - -
- -

- Fields -

- - -
- ); -}; 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 deleted file mode 100644 index bf142133f..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/[id]/super-delete-document-dialog.tsx +++ /dev/null @@ -1,135 +0,0 @@ -'use client'; - -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 { trpc } from '@documenso/trpc/react'; -import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@documenso/ui/primitives/dialog'; -import { Input } from '@documenso/ui/primitives/input'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -export type SuperDeleteDocumentDialogProps = { - document: Document; -}; - -export const SuperDeleteDocumentDialog = ({ document }: SuperDeleteDocumentDialogProps) => { - const { _ } = useLingui(); - const { toast } = useToast(); - - const router = useRouter(); - - const [reason, setReason] = useState(''); - - const { mutateAsync: deleteDocument, isPending: isDeletingDocument } = - trpc.admin.deleteDocument.useMutation(); - - const handleDeleteDocument = async () => { - try { - if (!reason) { - return; - } - - await deleteDocument({ id: document.id, reason }); - - toast({ - title: _(msg`Document deleted`), - description: 'The Document has been deleted successfully.', - duration: 5000, - }); - - router.push('/admin/documents'); - } catch (err) { - toast({ - title: _(msg`An unknown error occurred`), - variant: 'destructive', - description: - 'We encountered an unknown error while attempting to delete your document. Please try again later.', - }); - } - }; - - return ( -
-
- -
- - Delete Document - - - - Delete the document. This action is irreversible so proceed with caution. - - -
- -
- - - - - - - - - Delete Document - - - - - This action is not reversible. Please be certain. - - - - -
- - To confirm, please enter the reason - - - setReason(e.target.value)} - /> -
- - - - -
-
-
-
-
-
- ); -}; diff --git a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx b/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx deleted file mode 100644 index 98854f296..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/document-results.tsx +++ /dev/null @@ -1,165 +0,0 @@ -'use client'; - -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'; -import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; -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'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; - -import { DocumentStatus } from '~/components/formatter/document-status'; - -// export type AdminDocumentResultsProps = {}; - -export const AdminDocumentResults = () => { - const { _, i18n } = useLingui(); - - const searchParams = useSearchParams(); - - const updateSearchParams = useUpdateSearchParams(); - - const [term, setTerm] = useState(() => searchParams?.get?.('term') ?? ''); - const debouncedTerm = useDebouncedValue(term, 500); - - const page = searchParams?.get?.('page') ? Number(searchParams.get('page')) : undefined; - const perPage = searchParams?.get?.('perPage') ? Number(searchParams.get('perPage')) : undefined; - - const { data: findDocumentsData, isPending: isFindDocumentsLoading } = - trpc.admin.findDocuments.useQuery( - { - query: debouncedTerm, - page: page || 1, - perPage: perPage || 20, - }, - { - placeholderData: (previousData) => previousData, - }, - ); - - const results = findDocumentsData ?? { - data: [], - perPage: 20, - currentPage: 1, - totalPages: 1, - }; - - const columns = useMemo(() => { - return [ - { - header: _(msg`Created`), - accessorKey: 'createdAt', - cell: ({ row }) => i18n.date(row.original.createdAt), - }, - { - 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 }) => i18n.date(row.original.updatedAt), - }, - ] satisfies DataTableColumnDef<(typeof results)['data'][number]>[]; - }, []); - - const onPaginationChange = (newPage: number, newPerPage: number) => { - updateSearchParams({ - page: newPage, - perPage: newPerPage, - }); - }; - - return ( -
- setTerm(e.target.value)} - /> - -
- - {(table) => } - - - {isFindDocumentsLoading && ( -
- -
- )} -
-
- ); -}; diff --git a/apps/web/src/app/(dashboard)/admin/documents/page.tsx b/apps/web/src/app/(dashboard)/admin/documents/page.tsx deleted file mode 100644 index b658959bc..000000000 --- a/apps/web/src/app/(dashboard)/admin/documents/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Trans } from '@lingui/macro'; - -import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server'; - -import { AdminDocumentResults } from './document-results'; - -export default async function AdminDocumentsPage() { - await setupI18nSSR(); - - return ( -
-

- Manage documents -

- -
- -
-
- ); -} diff --git a/apps/web/src/app/(dashboard)/admin/layout.tsx b/apps/web/src/app/(dashboard)/admin/layout.tsx deleted file mode 100644 index 964267872..000000000 --- a/apps/web/src/app/(dashboard)/admin/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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'; - -import { AdminNav } from './nav'; - -export type AdminSectionLayoutProps = { - children: React.ReactNode; -}; - -export default async function AdminSectionLayout({ children }: AdminSectionLayoutProps) { - await setupI18nSSR(); - - const { user } = await getRequiredServerComponentSession(); - - if (!isAdmin(user)) { - redirect('/documents'); - } - - return ( -
-
- - -
{children}
-
-
- ); -} diff --git a/apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx b/apps/web/src/app/(dashboard)/admin/leaderboard/data-table-leaderboard.tsx deleted file mode 100644 index 596f0051d..000000000 --- a/apps/web/src/app/(dashboard)/admin/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/web/src/app/(dashboard)/admin/leaderboard/fetch-leaderboard.actions.ts b/apps/web/src/app/(dashboard)/admin/leaderboard/fetch-leaderboard.actions.ts deleted file mode 100644 index 42fc20c97..000000000 --- a/apps/web/src/app/(dashboard)/admin/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/web/src/app/(dashboard)/admin/leaderboard/page.tsx b/apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx deleted file mode 100644 index 0d1c5172a..000000000 --- a/apps/web/src/app/(dashboard)/admin/leaderboard/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Trans } from '@lingui/macro'; - -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'; - -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 Leaderboard({ searchParams = {} }: AdminLeaderboardProps) { - await setupI18nSSR(); - - const { user } = await getRequiredServerComponentSession(); - - if (!isAdmin(user)) { - throw new Error('Unauthorized'); - } - - 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/web/src/app/(dashboard)/admin/nav.tsx b/apps/web/src/app/(dashboard)/admin/nav.tsx deleted file mode 100644 index bcae0fc75..000000000 --- a/apps/web/src/app/(dashboard)/admin/nav.tsx +++ /dev/null @@ -1,112 +0,0 @@ -'use client'; - -import type { HTMLAttributes } from 'react'; - -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; - -import { Trans } from '@lingui/macro'; -import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; - -export type AdminNavProps = HTMLAttributes; - -export const AdminNav = ({ className, ...props }: AdminNavProps) => { - const pathname = usePathname(); - - return ( -
- - - - - - - - - - - -
- ); -}; diff --git a/apps/web/src/app/(dashboard)/admin/page.tsx b/apps/web/src/app/(dashboard)/admin/page.tsx deleted file mode 100644 index 5fe030685..000000000 --- a/apps/web/src/app/(dashboard)/admin/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function Admin() { - redirect('/admin/stats'); -} 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 deleted file mode 100644 index 6903f5e17..000000000 --- a/apps/web/src/app/(dashboard)/admin/site-settings/banner-form.tsx +++ /dev/null @@ -1,208 +0,0 @@ -'use client'; - -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'; - -import type { TSiteSettingsBannerSchema } from '@documenso/lib/server-only/site-settings/schemas/banner'; -import { - SITE_SETTINGS_BANNER_ID, - ZSiteSettingsBannerSchema, -} from '@documenso/lib/server-only/site-settings/schemas/banner'; -import { trpc as trpcReact } from '@documenso/trpc/react'; -import { Button } from '@documenso/ui/primitives/button'; -import { ColorPicker } from '@documenso/ui/primitives/color-picker'; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@documenso/ui/primitives/form/form'; -import { Switch } from '@documenso/ui/primitives/switch'; -import { Textarea } from '@documenso/ui/primitives/textarea'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -const ZBannerFormSchema = ZSiteSettingsBannerSchema; - -type TBannerFormSchema = z.infer; - -export type BannerFormProps = { - banner?: TSiteSettingsBannerSchema; -}; - -export function BannerForm({ banner }: BannerFormProps) { - const { toast } = useToast(); - const { _ } = useLingui(); - - const router = useRouter(); - - const form = useForm({ - resolver: zodResolver(ZBannerFormSchema), - defaultValues: { - id: SITE_SETTINGS_BANNER_ID, - enabled: banner?.enabled ?? false, - data: { - content: banner?.data?.content ?? '', - bgColor: banner?.data?.bgColor ?? '#000000', - textColor: banner?.data?.textColor ?? '#FFFFFF', - }, - }, - }); - - const enabled = form.watch('enabled'); - - const { mutateAsync: updateSiteSetting, isPending: isUpdateSiteSettingLoading } = - trpcReact.admin.updateSiteSetting.useMutation(); - - const onBannerUpdate = async ({ id, enabled, data }: TBannerFormSchema) => { - try { - await updateSiteSetting({ - id, - enabled, - data, - }); - - toast({ - title: _(msg`Banner Updated`), - description: _(msg`Your banner has been updated successfully.`), - duration: 5000, - }); - - router.refresh(); - } catch (err) { - toast({ - title: _(msg`An unknown error occurred`), - variant: 'destructive', - description: _( - msg`We encountered an unknown error while attempting to update the banner. Please try again later.`, - ), - }); - } - }; - - return ( -
-

- 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. - -

- -
- -
- ( - - - Enabled - - - -
- -
-
-
- )} - /> - -
- ( - - - Background Color - - - -
- -
-
- - -
- )} - /> - - ( - - - Text Color - - - -
- -
-
- - -
- )} - /> -
-
- -
- ( - - - Content - - - -