Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ac0b2a4ff | ||
|
|
0fc8739d26 |
@@ -97,6 +97,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
if (error.response.status === 401 && isRefreshCall) {
|
if (error.response.status === 401 && isRefreshCall) {
|
||||||
await clearTokens();
|
await clearTokens();
|
||||||
applyAccessToken(null);
|
applyAccessToken(null);
|
||||||
|
// Hash-safe on Capacitor (#24); plain path on web BrowserRouter.
|
||||||
redirectToAppPath("/signin/");
|
redirectToAppPath("/signin/");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
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 { AuthProvider } from './llm-fe/contexts/AuthContext';
|
||||||
import { AccountProvider } from './llm-fe/contexts/AccountContext';
|
import { AccountProvider } from './llm-fe/contexts/AccountContext';
|
||||||
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
|
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
|
||||||
@@ -16,7 +16,7 @@ const root = ReactDOM.createRoot(
|
|||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<AppRouter>
|
||||||
<script async defer src="https://tianji.aimloperations.com/tracker.js" data-website-id="cm7x7m52m03kbddswbswrt17y"></script>
|
<script async defer src="https://tianji.aimloperations.com/tracker.js" data-website-id="cm7x7m52m03kbddswbswrt17y"></script>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<AccountProvider>
|
<AccountProvider>
|
||||||
@@ -33,6 +33,6 @@ root.render(
|
|||||||
</WebSocketProvider>
|
</WebSocketProvider>
|
||||||
</AccountProvider>
|
</AccountProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</BrowserRouter>
|
</AppRouter>
|
||||||
</React.StrictMode>
|
</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.
|
* 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);
|
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