diff --git a/web/package-lock.json b/web/package-lock.json index 9de102d0f1..d9d0377baa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -30,7 +30,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-error-boundary": "4.1.2", - "react-router-dom": "6.28.0", + "react-router": "7.0.1", "tailwind-merge": "2.5.4", "tailwindcss": "3.4.15", "tailwindcss-animate": "1.0.7", @@ -2895,15 +2895,6 @@ } } }, - "node_modules/@remix-run/router": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", - "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.27.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.2.tgz", @@ -3417,6 +3408,12 @@ "node": ">=6.9.0" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -4681,6 +4678,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -9502,35 +9508,27 @@ } }, "node_modules/react-router": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", - "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz", + "integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.21.0" + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", - "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.21.0", - "react-router": "6.28.0" + "react": ">=18", + "react-dom": ">=18" }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } } }, "node_modules/read-cache": { @@ -9841,6 +9839,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10633,6 +10637,12 @@ } } }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/web/package.json b/web/package.json index 196125be3e..1f219afebe 100644 --- a/web/package.json +++ b/web/package.json @@ -34,7 +34,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-error-boundary": "4.1.2", - "react-router-dom": "6.28.0", + "react-router": "7.0.1", "tailwind-merge": "2.5.4", "tailwindcss": "3.4.15", "tailwindcss-animate": "1.0.7", diff --git a/web/src/App.tsx b/web/src/App.tsx index 28aa17ba8d..072063f35a 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,10 +1,9 @@ import { RouterProvider, createBrowserRouter, - defer, redirect, useRouteError -} from 'react-router-dom' +} from 'react-router' import EmailList from 'pages/EmailList' import EmailRawView from 'pages/EmailRawView' @@ -40,11 +39,11 @@ const router = createBrowserRouter([ errorElement: , loader: ({ params }) => { if (!params.threadID) return redirect('/inbox') - return defer({ + return { type: 'thread', threadID: params.threadID, thread: getThread(params.threadID) - }) + } } }, { @@ -53,11 +52,11 @@ const router = createBrowserRouter([ errorElement: , loader: ({ params }) => { if (!params.messageID) return redirect('/inbox') - return defer({ + return { type: 'email', messageID: params.messageID, email: getEmail(params.messageID) - }) + } } } ] @@ -76,10 +75,10 @@ const router = createBrowserRouter([ element: , loader: ({ params }) => { if (!params.messageID) return null - return defer({ + return { messageID: params.messageID, email: getEmail(params.messageID) - }) + } } } ] @@ -98,10 +97,10 @@ const router = createBrowserRouter([ element: , loader: ({ params }) => { if (!params.messageID) return redirect('/sent') - return defer({ + return { messageID: params.messageID, email: getEmail(params.messageID) - }) + } } } ] @@ -114,10 +113,10 @@ const router = createBrowserRouter([ errorElement: , loader: ({ params }) => { if (!params.messageID) return redirect('/inbox') - return defer({ + return { messageID: params.messageID, raw: getEmailRaw(params.messageID) - }) + } } } ]) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index e3b32c6770..4de448cc83 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -4,7 +4,7 @@ import { PaperAirplaneIcon } from '@heroicons/react/24/outline' import { ReactElement, forwardRef, useEffect, useState } from 'react' -import { NavLink } from 'react-router-dom' +import { NavLink } from 'react-router' import { getInfo } from 'services/info' diff --git a/web/src/components/emails/EmailTableRow.tsx b/web/src/components/emails/EmailTableRow.tsx index eb51167e27..ea04fd3256 100644 --- a/web/src/components/emails/EmailTableRow.tsx +++ b/web/src/components/emails/EmailTableRow.tsx @@ -1,6 +1,6 @@ import { CheckIcon } from '@heroicons/react/20/solid' import { useContext } from 'react' -import { useNavigate } from 'react-router-dom' +import { useNavigate } from 'react-router' import EmailName from 'components/emails/EmailName' @@ -33,20 +33,20 @@ export default function EmailTableRow(props: EmailTableRowProps) { }) } - const openEmail = () => { + const openEmail = async () => { if (email.type === 'draft') { if (email.threadID) { - navigate(`/inbox/thread/${email.threadID}`) + await navigate(`/inbox/thread/${email.threadID}`) return } void openDraftEmail(email.messageID) // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (email.type === 'inbox' || email.type === 'sent') { if (email.threadID) { - navigate(`/inbox/thread/${email.threadID}`) + await navigate(`/inbox/thread/${email.threadID}`) return } - navigate(`/inbox/${email.messageID}`) + await navigate(`/inbox/${email.messageID}`) } } @@ -79,7 +79,9 @@ export default function EmailTableRow(props: EmailTableRowProps) { backgroundClassName + unreadClassName } - onClick={openEmail} + onClick={() => { + void openEmail() + }} > 0 ? email.from[0] : ''}> @@ -91,7 +93,9 @@ export default function EmailTableRow(props: EmailTableRowProps) { backgroundClassName + unreadClassName } - onClick={openEmail} + onClick={() => { + void openEmail() + }} > {email.subject} @@ -101,7 +105,9 @@ export default function EmailTableRow(props: EmailTableRowProps) { backgroundClassName + (email.unread ? ' md:font-bold' : ' md:dark:font-light') } - onClick={openEmail} + onClick={() => { + void openEmail() + }} > {formatDate( email.timeReceived ?? email.timeUpdated ?? email.timeSent ?? '', diff --git a/web/src/pages/EmailRawView.tsx b/web/src/pages/EmailRawView.tsx index a8d6e4e83f..ca535bf3b1 100644 --- a/web/src/pages/EmailRawView.tsx +++ b/web/src/pages/EmailRawView.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Await, useLoaderData } from 'react-router-dom' +import { Await, useLoaderData } from 'react-router' import { Toaster } from '@ui/toaster' import { toast } from '@ui/use-toast' @@ -7,7 +7,7 @@ import { toast } from '@ui/use-toast' import { reparseEmail } from 'services/emails' export default function EmailRawView() { - const data = useLoaderData() as { messageID: string; raw: string } + const data: { messageID: string; raw: string } = useLoaderData() const [isRequesting, setIsRequesting] = React.useState(false) diff --git a/web/src/pages/EmailRoot.tsx b/web/src/pages/EmailRoot.tsx index 6d45141d33..8937b895d9 100644 --- a/web/src/pages/EmailRoot.tsx +++ b/web/src/pages/EmailRoot.tsx @@ -3,7 +3,7 @@ * This is the root component for inbox, draft, and sent pages. */ import { useContext, useEffect, useState } from 'react' -import { Outlet, useOutletContext } from 'react-router-dom' +import { Outlet, useOutletContext } from 'react-router' import DraftEmailsTabs from 'components/emails/DraftEmailsTabs' import FullScreenContent from 'components/emails/FullScreenContent' diff --git a/web/src/pages/EmailView.tsx b/web/src/pages/EmailView.tsx index 1f539250d9..78dce4625e 100644 --- a/web/src/pages/EmailView.tsx +++ b/web/src/pages/EmailView.tsx @@ -6,7 +6,7 @@ import { } from '@heroicons/react/24/outline' import React, { useContext, useEffect, useRef, useState } from 'react' import { ErrorBoundary } from 'react-error-boundary' -import { Await, useLoaderData, useNavigate } from 'react-router-dom' +import { Await, useLoaderData, useNavigate } from 'react-router' import { toast } from '@ui/use-toast' @@ -37,9 +37,9 @@ import { parseEmailContent, parseEmailName } from 'utils/emails' import { formatDate } from 'utils/time' export default function EmailView() { - const data = useLoaderData() as + const data: | { type: 'email'; messageID: string; email: Email } - | { type: 'thread'; threadID: string; thread: Thread } + | { type: 'thread'; threadID: string; thread: Thread } = useLoaderData() const navigate = useNavigate() @@ -154,7 +154,7 @@ export default function EmailView() { } else { await trashEmail(data.messageID) } - navigate(-1) + await navigate(-1) } const handleRead = async () => { @@ -189,13 +189,17 @@ export default function EmailView() { } } + const handleBack = async () => { + await navigate(-1) + } + return ( <>
{ - navigate(-1) + void handleBack() }} showOperations={true} handleDelete={() => { diff --git a/web/src/pages/Root.tsx b/web/src/pages/Root.tsx index 837417681d..6a63c8069b 100644 --- a/web/src/pages/Root.tsx +++ b/web/src/pages/Root.tsx @@ -1,6 +1,6 @@ import { Bars3Icon } from '@heroicons/react/24/outline' import { useReducer, useRef, useState } from 'react' -import { Outlet } from 'react-router-dom' +import { Outlet } from 'react-router' import { Toaster } from '@ui/toaster'