Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac0b2a4ff | ||
|
|
0fc8739d26 |
@@ -97,6 +97,7 @@ axiosInstance.interceptors.response.use(
|
||||
if (error.response.status === 401 && isRefreshCall) {
|
||||
await clearTokens();
|
||||
applyAccessToken(null);
|
||||
// Hash-safe on Capacitor (#24); plain path on web BrowserRouter.
|
||||
redirectToAppPath("/signin/");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import AppRouter from './llm-fe/platform/AppRouter';
|
||||
import { AuthProvider } from './llm-fe/contexts/AuthContext';
|
||||
import { AccountProvider } from './llm-fe/contexts/AccountContext';
|
||||
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
|
||||
@@ -16,7 +16,7 @@ const root = ReactDOM.createRoot(
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<AppRouter>
|
||||
<script async defer src="https://tianji.aimloperations.com/tracker.js" data-website-id="cm7x7m52m03kbddswbswrt17y"></script>
|
||||
<AuthProvider>
|
||||
<AccountProvider>
|
||||
@@ -33,6 +33,6 @@ root.render(
|
||||
</WebSocketProvider>
|
||||
</AccountProvider>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
</AppRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import AppRouter from './AppRouter';
|
||||
import { getAppRouter, isNativePlatform } from './nativePlatform';
|
||||
|
||||
describe('AppRouter / getAppRouter (#24)', () => {
|
||||
const originalCapacitor = window.Capacitor;
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCapacitor === undefined) {
|
||||
delete window.Capacitor;
|
||||
} else {
|
||||
window.Capacitor = originalCapacitor;
|
||||
}
|
||||
});
|
||||
|
||||
it('uses BrowserRouter on web', () => {
|
||||
delete window.Capacitor;
|
||||
expect(isNativePlatform()).toBe(false);
|
||||
const Router = getAppRouter();
|
||||
expect(Router.name).toBe('BrowserRouter');
|
||||
});
|
||||
|
||||
it('uses HashRouter on native Capacitor', () => {
|
||||
window.Capacitor = { isNativePlatform: () => true };
|
||||
expect(isNativePlatform()).toBe(true);
|
||||
const Router = getAppRouter();
|
||||
expect(Router.name).toBe('HashRouter');
|
||||
});
|
||||
|
||||
it('renders nested routes under HashRouter without blank screen', () => {
|
||||
window.Capacitor = { isNativePlatform: () => true };
|
||||
window.location.hash = '#/account/';
|
||||
|
||||
render(
|
||||
<AppRouter>
|
||||
<Routes>
|
||||
<Route path="/account/" element={<div>Account Page</div>} />
|
||||
<Route path="/" element={<div>Home</div>} />
|
||||
</Routes>
|
||||
</AppRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Account Page')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nested routes under BrowserRouter on web', () => {
|
||||
delete window.Capacitor;
|
||||
window.history.pushState({}, '', '/account/');
|
||||
|
||||
render(
|
||||
<AppRouter>
|
||||
<Routes>
|
||||
<Route path="/account/" element={<div>Account Page</div>} />
|
||||
<Route path="/" element={<div>Home</div>} />
|
||||
</Routes>
|
||||
</AppRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Account Page')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { getAppRouter } from './nativePlatform';
|
||||
|
||||
type AppRouterProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single place that chooses BrowserRouter (web) vs HashRouter (Capacitor).
|
||||
* Avoids sprinkling Capacitor.isNativePlatform() through the route tree (#24).
|
||||
*/
|
||||
const AppRouter = ({ children }: AppRouterProps): JSX.Element => {
|
||||
const Router = getAppRouter();
|
||||
return <Router>{children}</Router>;
|
||||
};
|
||||
|
||||
export default AppRouter;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Capacitor / WebView platform helpers.
|
||||
* Capacitor / WebView platform helpers (#24 / #22).
|
||||
* Prefer window.Capacitor so unit tests and web builds need no Capacitor package.
|
||||
*/
|
||||
|
||||
@@ -50,3 +50,16 @@ export function redirectToAppPath(path) {
|
||||
}
|
||||
window.location.href = appHref(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Router class for the current runtime.
|
||||
* HashRouter on Capacitor so nested routes reload without a server rewrite.
|
||||
* BrowserRouter on web (nginx already rewrites to index.html).
|
||||
*
|
||||
* @returns {typeof import('react-router-dom').HashRouter | typeof import('react-router-dom').BrowserRouter}
|
||||
*/
|
||||
export function getAppRouter() {
|
||||
// Lazy require keeps this module usable from axios (no React import cycle in tests).
|
||||
const { BrowserRouter, HashRouter } = require("react-router-dom");
|
||||
return isNativePlatform() ? HashRouter : BrowserRouter;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user