mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
chore(frontend): Migrate from Remix to React Router 7 (#5304)
This commit is contained in:
parent
5069a8700a
commit
a378ff0965
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -7,3 +7,4 @@ node_modules/
|
|||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
.react-router/
|
||||||
|
|||||||
@ -26,8 +26,8 @@ describe("Empty state", () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
vi.mock("@remix-run/react", async (importActual) => ({
|
vi.mock("react-router", async (importActual) => ({
|
||||||
...(await importActual<typeof import("@remix-run/react")>()),
|
...(await importActual<typeof import("react-router")>()),
|
||||||
useRouteLoaderData: vi.fn(() => ({})),
|
useRouteLoaderData: vi.fn(() => ({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -290,8 +290,8 @@ describe.skip("ChatInterface", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render both GitHub buttons initially when ghToken is available", () => {
|
it("should render both GitHub buttons initially when ghToken is available", () => {
|
||||||
vi.mock("@remix-run/react", async (importActual) => ({
|
vi.mock("react-router", async (importActual) => ({
|
||||||
...(await importActual<typeof import("@remix-run/react")>()),
|
...(await importActual<typeof import("react-router")>()),
|
||||||
useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })),
|
useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -315,8 +315,8 @@ describe.skip("ChatInterface", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render only 'Push changes to PR' button after PR is created", async () => {
|
it("should render only 'Push changes to PR' button after PR is created", async () => {
|
||||||
vi.mock("@remix-run/react", async (importActual) => ({
|
vi.mock("react-router", async (importActual) => ({
|
||||||
...(await importActual<typeof import("@remix-run/react")>()),
|
...(await importActual<typeof import("react-router")>()),
|
||||||
useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })),
|
useRouteLoaderData: vi.fn(() => ({ ghToken: "test-token" })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||||
import { createRemixStub } from "@remix-run/testing";
|
import { createRoutesStub } from "react-router";
|
||||||
import { screen, waitFor, within } from "@testing-library/react";
|
import { screen, waitFor, within } from "@testing-library/react";
|
||||||
import { renderWithProviders } from "test-utils";
|
import { renderWithProviders } from "test-utils";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
@ -8,7 +8,7 @@ import * as CaptureConsent from "#/utils/handle-capture-consent";
|
|||||||
import i18n from "#/i18n";
|
import i18n from "#/i18n";
|
||||||
|
|
||||||
describe("frontend/routes/_oh", () => {
|
describe("frontend/routes/_oh", () => {
|
||||||
const RemixStub = createRemixStub([{ Component: MainApp, path: "/" }]);
|
const RouteStub = createRoutesStub([{ Component: MainApp, path: "/" }]);
|
||||||
|
|
||||||
const { userIsAuthenticatedMock, settingsAreUpToDateMock } = vi.hoisted(
|
const { userIsAuthenticatedMock, settingsAreUpToDateMock } = vi.hoisted(
|
||||||
() => ({
|
() => ({
|
||||||
@ -34,26 +34,26 @@ describe("frontend/routes/_oh", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should render", async () => {
|
it("should render", async () => {
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
await screen.findByTestId("root-layout");
|
await screen.findByTestId("root-layout");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the AI config modal if the user is authed", async () => {
|
it("should render the AI config modal if the user is authed", async () => {
|
||||||
// Our mock return value is true by default
|
// Our mock return value is true by default
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
await screen.findByTestId("ai-config-modal");
|
await screen.findByTestId("ai-config-modal");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render the AI config modal if settings are not up-to-date", async () => {
|
it("should render the AI config modal if settings are not up-to-date", async () => {
|
||||||
settingsAreUpToDateMock.mockReturnValue(false);
|
settingsAreUpToDateMock.mockReturnValue(false);
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
await screen.findByTestId("ai-config-modal");
|
await screen.findByTestId("ai-config-modal");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not render the AI config modal if the settings are up-to-date", async () => {
|
it("should not render the AI config modal if the settings are up-to-date", async () => {
|
||||||
settingsAreUpToDateMock.mockReturnValue(true);
|
settingsAreUpToDateMock.mockReturnValue(true);
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
|
expect(screen.queryByTestId("ai-config-modal")).not.toBeInTheDocument();
|
||||||
@ -67,7 +67,7 @@ describe("frontend/routes/_oh", () => {
|
|||||||
"handleCaptureConsent",
|
"handleCaptureConsent",
|
||||||
);
|
);
|
||||||
|
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
// The user has not consented to tracking
|
// The user has not consented to tracking
|
||||||
const consentForm = await screen.findByTestId("user-capture-consent-form");
|
const consentForm = await screen.findByTestId("user-capture-consent-form");
|
||||||
@ -89,7 +89,7 @@ describe("frontend/routes/_oh", () => {
|
|||||||
|
|
||||||
it("should not render the user consent form if the user has already made a decision", async () => {
|
it("should not render the user consent form if the user has already made a decision", async () => {
|
||||||
localStorage.setItem("analytics-consent", "true");
|
localStorage.setItem("analytics-consent", "true");
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
@ -101,12 +101,12 @@ describe("frontend/routes/_oh", () => {
|
|||||||
// TODO: Likely failing due to how tokens are now handled in context. Move to e2e tests
|
// TODO: Likely failing due to how tokens are now handled in context. Move to e2e tests
|
||||||
it.skip("should render a new project button if a token is set", async () => {
|
it.skip("should render a new project button if a token is set", async () => {
|
||||||
localStorage.setItem("token", "test-token");
|
localStorage.setItem("token", "test-token");
|
||||||
const { rerender } = renderWithProviders(<RemixStub />);
|
const { rerender } = renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
await screen.findByTestId("new-project-button");
|
await screen.findByTestId("new-project-button");
|
||||||
|
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
rerender(<RemixStub />);
|
rerender(<RouteStub />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(
|
expect(
|
||||||
@ -118,17 +118,17 @@ describe("frontend/routes/_oh", () => {
|
|||||||
// TODO: Move to e2e tests
|
// TODO: Move to e2e tests
|
||||||
it.skip("should update the i18n language when the language settings change", async () => {
|
it.skip("should update the i18n language when the language settings change", async () => {
|
||||||
const changeLanguageSpy = vi.spyOn(i18n, "changeLanguage");
|
const changeLanguageSpy = vi.spyOn(i18n, "changeLanguage");
|
||||||
const { rerender } = renderWithProviders(<RemixStub />);
|
const { rerender } = renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
// The default language is English
|
// The default language is English
|
||||||
expect(changeLanguageSpy).toHaveBeenCalledWith("en");
|
expect(changeLanguageSpy).toHaveBeenCalledWith("en");
|
||||||
|
|
||||||
localStorage.setItem("LANGUAGE", "es");
|
localStorage.setItem("LANGUAGE", "es");
|
||||||
|
|
||||||
rerender(<RemixStub />);
|
rerender(<RouteStub />);
|
||||||
expect(changeLanguageSpy).toHaveBeenCalledWith("es");
|
expect(changeLanguageSpy).toHaveBeenCalledWith("es");
|
||||||
|
|
||||||
rerender(<RemixStub />);
|
rerender(<RouteStub />);
|
||||||
// The language has not changed, so the spy should not have been called again
|
// The language has not changed, so the spy should not have been called again
|
||||||
expect(changeLanguageSpy).toHaveBeenCalledTimes(2);
|
expect(changeLanguageSpy).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
@ -139,7 +139,7 @@ describe("frontend/routes/_oh", () => {
|
|||||||
localStorage.setItem("ghToken", "test-token");
|
localStorage.setItem("ghToken", "test-token");
|
||||||
|
|
||||||
// const logoutCleanupSpy = vi.spyOn(LogoutCleanup, "logoutCleanup");
|
// const logoutCleanupSpy = vi.spyOn(LogoutCleanup, "logoutCleanup");
|
||||||
renderWithProviders(<RemixStub />);
|
renderWithProviders(<RouteStub />);
|
||||||
|
|
||||||
const userActions = await screen.findByTestId("user-actions");
|
const userActions = await screen.findByTestId("user-actions");
|
||||||
const userAvatar = within(userActions).getByTestId("user-avatar");
|
const userAvatar = within(userActions).getByTestId("user-avatar");
|
||||||
|
|||||||
10674
frontend/package-lock.json
generated
10674
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,11 +9,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@nextui-org/react": "^2.4.8",
|
"@nextui-org/react": "^2.4.8",
|
||||||
|
"@react-router/node": "^7.0.1",
|
||||||
|
"@react-router/serve": "^7.0.1",
|
||||||
"@react-types/shared": "^3.25.0",
|
"@react-types/shared": "^3.25.0",
|
||||||
"@reduxjs/toolkit": "^2.3.0",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"@remix-run/node": "^2.11.2",
|
|
||||||
"@remix-run/react": "^2.11.2",
|
|
||||||
"@remix-run/serve": "^2.11.2",
|
|
||||||
"@tanstack/react-query": "^5.60.5",
|
"@tanstack/react-query": "^5.60.5",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
@ -36,7 +35,7 @@
|
|||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router": "^7.0.1",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
"react-textarea-autosize": "^8.5.4",
|
"react-textarea-autosize": "^8.5.4",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
@ -48,9 +47,9 @@
|
|||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run make-i18n && cross-env VITE_MOCK_API=false remix vite:dev",
|
"dev": "npm run make-i18n && cross-env VITE_MOCK_API=false react-router dev",
|
||||||
"dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true remix vite:dev",
|
"dev:mock": "npm run make-i18n && cross-env VITE_MOCK_API=true react-router dev",
|
||||||
"build": "npm run make-i18n && tsc && remix vite:build",
|
"build": "npm run make-i18n && tsc && react-router build",
|
||||||
"start": "npx sirv-cli build/ --single",
|
"start": "npx sirv-cli build/ --single",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
@ -61,7 +60,8 @@
|
|||||||
"prelint": "npm run make-i18n",
|
"prelint": "npm run make-i18n",
|
||||||
"lint": "eslint src --ext .ts,.tsx,.js && prettier --check src/**/*.{ts,tsx}",
|
"lint": "eslint src --ext .ts,.tsx,.js && prettier --check src/**/*.{ts,tsx}",
|
||||||
"lint:fix": "eslint src --ext .ts,.tsx,.js --fix && prettier --write src/**/*.{ts,tsx}",
|
"lint:fix": "eslint src --ext .ts,.tsx,.js --fix && prettier --write src/**/*.{ts,tsx}",
|
||||||
"prepare": "cd .. && husky frontend/.husky"
|
"prepare": "cd .. && husky frontend/.husky",
|
||||||
|
"typecheck": "react-router typegen && tsc"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@ -76,8 +76,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.48.2",
|
"@playwright/test": "^1.48.2",
|
||||||
"@remix-run/dev": "^2.11.2",
|
"@react-router/dev": "^7.0.1",
|
||||||
"@remix-run/testing": "^2.11.2",
|
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@tailwindcss/typography": "^0.5.15",
|
||||||
"@tanstack/eslint-plugin-query": "^5.60.1",
|
"@tanstack/eslint-plugin-query": "^5.60.1",
|
||||||
"@testing-library/jest-dom": "^6.6.1",
|
"@testing-library/jest-dom": "^6.6.1",
|
||||||
|
|||||||
35
frontend/react-router.config.ts
Normal file
35
frontend/react-router.config.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { Config } from "@react-router/dev/config";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This script is used to unpack the client directory from the frontend build directory.
|
||||||
|
* Remix SPA mode builds the client directory into the build directory. This function
|
||||||
|
* moves the contents of the client directory to the build directory and then removes the
|
||||||
|
* client directory.
|
||||||
|
*
|
||||||
|
* This script is used in the buildEnd function of the Vite config.
|
||||||
|
*/
|
||||||
|
const unpackClientDirectory = async () => {
|
||||||
|
const fs = await import("fs");
|
||||||
|
const path = await import("path");
|
||||||
|
|
||||||
|
const buildDir = path.resolve(__dirname, "build");
|
||||||
|
const clientDir = path.resolve(buildDir, "client");
|
||||||
|
|
||||||
|
const files = await fs.promises.readdir(clientDir);
|
||||||
|
await Promise.all(
|
||||||
|
files.map((file) =>
|
||||||
|
fs.promises.rename(
|
||||||
|
path.resolve(clientDir, file),
|
||||||
|
path.resolve(buildDir, file),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.promises.rmdir(clientDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
appDirectory: "src",
|
||||||
|
buildEnd: unpackClientDirectory,
|
||||||
|
ssr: false,
|
||||||
|
} satisfies Config;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router";
|
||||||
import { useAuth } from "#/context/auth-context";
|
import { useAuth } from "#/context/auth-context";
|
||||||
import { useUserPrefs } from "#/context/user-prefs-context";
|
import { useUserPrefs } from "#/context/user-prefs-context";
|
||||||
import { useGitHubUser } from "#/hooks/query/use-github-user";
|
import { useGitHubUser } from "#/hooks/query/use-github-user";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router";
|
||||||
import { cn } from "#/utils/utils";
|
import { cn } from "#/utils/utils";
|
||||||
import { BetaBadge } from "./beta-badge";
|
import { BetaBadge } from "./beta-badge";
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useLocation } from "@remix-run/react";
|
import { useLocation } from "react-router";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate, useNavigation } from "@remix-run/react";
|
import { useNavigate, useNavigation } from "react-router";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import posthog from "posthog-js";
|
import posthog from "posthog-js";
|
||||||
import { RootState } from "#/store";
|
import { RootState } from "#/store";
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
* For more information, see https://remix.run/file-conventions/entry.client
|
* For more information, see https://remix.run/file-conventions/entry.client
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RemixBrowser } from "@remix-run/react";
|
import { HydratedRouter } from "react-router/dom";
|
||||||
import React, { startTransition, StrictMode } from "react";
|
import React, { startTransition, StrictMode } from "react";
|
||||||
import { hydrateRoot } from "react-dom/client";
|
import { hydrateRoot } from "react-dom/client";
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from "react-redux";
|
||||||
@ -74,7 +74,7 @@ prepareApp().then(() =>
|
|||||||
<UserPrefsProvider>
|
<UserPrefsProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<RemixBrowser />
|
<HydratedRouter />
|
||||||
<PosthogInit />
|
<PosthogInit />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { useNavigate } from "@remix-run/react";
|
import { useNavigate } from "react-router";
|
||||||
import { useAuth } from "#/context/auth-context";
|
import { useAuth } from "#/context/auth-context";
|
||||||
import {
|
import {
|
||||||
initialState as browserInitialState,
|
initialState as browserInitialState,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
Outlet,
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "react-router";
|
||||||
import "./tailwind.css";
|
import "./tailwind.css";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|||||||
19
frontend/src/routes.ts
Normal file
19
frontend/src/routes.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
type RouteConfig,
|
||||||
|
layout,
|
||||||
|
index,
|
||||||
|
route,
|
||||||
|
} from "@react-router/dev/routes";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
layout("routes/_oh/route.tsx", [
|
||||||
|
index("routes/_oh._index/route.tsx"),
|
||||||
|
route("app", "routes/_oh.app/route.tsx", [
|
||||||
|
index("routes/_oh.app._index/route.tsx"),
|
||||||
|
route("browser", "routes/_oh.app.browser.tsx"),
|
||||||
|
route("jupyter", "routes/_oh.app.jupyter.tsx"),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
route("oauth", "routes/oauth.github.callback.tsx"),
|
||||||
|
] satisfies RouteConfig;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { useLocation, useNavigate } from "@remix-run/react";
|
import { useLocation, useNavigate } from "react-router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { setImportedProjectZip } from "#/state/initial-query-slice";
|
import { setImportedProjectZip } from "#/state/initial-query-slice";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useRouteError } from "@remix-run/react";
|
import { useRouteError } from "react-router";
|
||||||
import { editor } from "monaco-editor";
|
import { editor } from "monaco-editor";
|
||||||
import { EditorProps } from "@monaco-editor/react";
|
import { EditorProps } from "@monaco-editor/react";
|
||||||
import { RootState } from "#/store";
|
import { RootState } from "#/store";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useDisclosure } from "@nextui-org/react";
|
import { useDisclosure } from "@nextui-org/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Outlet } from "@remix-run/react";
|
import { Outlet } from "react-router";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { Controls } from "#/components/features/controls/controls";
|
import { Controls } from "#/components/features/controls/controls";
|
||||||
import { RootState } from "#/store";
|
import { RootState } from "#/store";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouteError, isRouteErrorResponse, Outlet } from "@remix-run/react";
|
import { useRouteError, isRouteErrorResponse, Outlet } from "react-router";
|
||||||
import i18n from "#/i18n";
|
import i18n from "#/i18n";
|
||||||
import { useGitHubAuthUrl } from "#/hooks/use-github-auth-url";
|
import { useGitHubAuthUrl } from "#/hooks/use-github-auth-url";
|
||||||
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
import { useIsAuthed } from "#/hooks/query/use-is-authed";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useNavigate, useSearchParams } from "@remix-run/react";
|
import { useNavigate, useSearchParams } from "react-router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import OpenHands from "#/api/open-hands";
|
import OpenHands from "#/api/open-hands";
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
"**/.server/**/*.ts",
|
"**/.server/**/*.ts",
|
||||||
"**/.server/**/*.tsx",
|
"**/.server/**/*.tsx",
|
||||||
"**/.client/**/*.ts",
|
"**/.client/**/*.ts",
|
||||||
"**/.client/**/*.tsx"
|
"**/.client/**/*.tsx",
|
||||||
|
".react-router/types/**/*",
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": [
|
"lib": [
|
||||||
@ -15,9 +16,13 @@
|
|||||||
],
|
],
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"types": [
|
"types": [
|
||||||
"@remix-run/node",
|
"@react-router/node",
|
||||||
"vite/client",
|
"vite/client",
|
||||||
],
|
],
|
||||||
|
"rootDirs": [
|
||||||
|
".",
|
||||||
|
"./.react-router/types"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
/// <reference types="vitest" />
|
/// <reference types="vitest" />
|
||||||
/// <reference types="vite-plugin-svgr/client" />
|
/// <reference types="vite-plugin-svgr/client" />
|
||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import viteTsconfigPaths from "vite-tsconfig-paths";
|
import viteTsconfigPaths from "vite-tsconfig-paths";
|
||||||
import svgr from "vite-plugin-svgr";
|
import svgr from "vite-plugin-svgr";
|
||||||
import { vitePlugin as remix } from "@remix-run/dev";
|
import { reactRouter } from "@react-router/dev/vite";
|
||||||
import { configDefaults } from "vitest/config";
|
import { configDefaults } from "vitest/config";
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
@ -24,47 +23,9 @@ export default defineConfig(({ mode }) => {
|
|||||||
const WS_URL = `${WS_PROTOCOL}://${VITE_BACKEND_HOST}/`;
|
const WS_URL = `${WS_PROTOCOL}://${VITE_BACKEND_HOST}/`;
|
||||||
const FE_PORT = Number.parseInt(VITE_FRONTEND_PORT, 10);
|
const FE_PORT = Number.parseInt(VITE_FRONTEND_PORT, 10);
|
||||||
|
|
||||||
/**
|
|
||||||
* This script is used to unpack the client directory from the frontend build directory.
|
|
||||||
* Remix SPA mode builds the client directory into the build directory. This function
|
|
||||||
* moves the contents of the client directory to the build directory and then removes the
|
|
||||||
* client directory.
|
|
||||||
*
|
|
||||||
* This script is used in the buildEnd function of the Vite config.
|
|
||||||
*/
|
|
||||||
const unpackClientDirectory = async () => {
|
|
||||||
const fs = await import("fs");
|
|
||||||
const path = await import("path");
|
|
||||||
|
|
||||||
const buildDir = path.resolve(__dirname, "build");
|
|
||||||
const clientDir = path.resolve(buildDir, "client");
|
|
||||||
|
|
||||||
const files = await fs.promises.readdir(clientDir);
|
|
||||||
await Promise.all(
|
|
||||||
files.map((file) =>
|
|
||||||
fs.promises.rename(
|
|
||||||
path.resolve(clientDir, file),
|
|
||||||
path.resolve(buildDir, file),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await fs.promises.rmdir(clientDir);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
!process.env.VITEST &&
|
!process.env.VITEST && reactRouter(),
|
||||||
remix({
|
|
||||||
future: {
|
|
||||||
v3_fetcherPersist: true,
|
|
||||||
v3_relativeSplatPath: true,
|
|
||||||
v3_throwAbortReason: true,
|
|
||||||
},
|
|
||||||
appDirectory: "src",
|
|
||||||
buildEnd: unpackClientDirectory,
|
|
||||||
ssr: false,
|
|
||||||
}),
|
|
||||||
viteTsconfigPaths(),
|
viteTsconfigPaths(),
|
||||||
svgr(),
|
svgr(),
|
||||||
],
|
],
|
||||||
@ -87,8 +48,8 @@ export default defineConfig(({ mode }) => {
|
|||||||
ws: true,
|
ws: true,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: !INSECURE_SKIP_VERIFY,
|
secure: !INSECURE_SKIP_VERIFY,
|
||||||
//rewriteWsOrigin: true,
|
// rewriteWsOrigin: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ssr: {
|
ssr: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user