fix: sync mobile routing with browser URL for direct navigation (refs #163)
URL-to-screen sync on mount and screen-to-URL sync via replaceState enable direct URL navigation, page refresh, and bookmarks on mobile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,7 +81,7 @@ import { useOptimisticVehicles } from './features/vehicles/hooks/useOptimisticVe
|
|||||||
import { CreateVehicleRequest } from './features/vehicles/types/vehicles.types';
|
import { CreateVehicleRequest } from './features/vehicles/types/vehicles.types';
|
||||||
import { MobileSettingsScreen } from './features/settings/mobile/MobileSettingsScreen';
|
import { MobileSettingsScreen } from './features/settings/mobile/MobileSettingsScreen';
|
||||||
import { SecurityMobileScreen } from './features/settings/mobile/SecurityMobileScreen';
|
import { SecurityMobileScreen } from './features/settings/mobile/SecurityMobileScreen';
|
||||||
import { useNavigationStore, useUserStore } from './core/store';
|
import { useNavigationStore, useUserStore, routeToScreen, screenToRoute } from './core/store';
|
||||||
import { useNeedsVehicleSelection, useDowngrade } from './features/subscription/hooks/useSubscription';
|
import { useNeedsVehicleSelection, useDowngrade } from './features/subscription/hooks/useSubscription';
|
||||||
import { useVehicles } from './features/vehicles/hooks/useVehicles';
|
import { useVehicles } from './features/vehicles/hooks/useVehicles';
|
||||||
import { VehicleSelectionDialog } from './features/subscription/components/VehicleSelectionDialog';
|
import { VehicleSelectionDialog } from './features/subscription/components/VehicleSelectionDialog';
|
||||||
@@ -364,6 +364,22 @@ function App() {
|
|||||||
const [selectedVehicle, setSelectedVehicle] = useState<Vehicle | null>(null);
|
const [selectedVehicle, setSelectedVehicle] = useState<Vehicle | null>(null);
|
||||||
const [showAddVehicle, setShowAddVehicle] = useState(false);
|
const [showAddVehicle, setShowAddVehicle] = useState(false);
|
||||||
|
|
||||||
|
// Sync browser URL to Zustand screen state on mount (enables direct URL navigation on mobile)
|
||||||
|
useEffect(() => {
|
||||||
|
const screen = routeToScreen[window.location.pathname];
|
||||||
|
if (screen && screen !== activeScreen) {
|
||||||
|
navigateToScreen(screen, { source: 'url-sync' });
|
||||||
|
}
|
||||||
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- intentionally runs once on mount
|
||||||
|
|
||||||
|
// Sync Zustand screen changes back to browser URL (enables bookmarks and URL sharing)
|
||||||
|
useEffect(() => {
|
||||||
|
const targetPath = screenToRoute[activeScreen];
|
||||||
|
if (targetPath && window.location.pathname !== targetPath) {
|
||||||
|
window.history.replaceState(null, '', targetPath);
|
||||||
|
}
|
||||||
|
}, [activeScreen]);
|
||||||
|
|
||||||
// Update mobile mode on window resize
|
// Update mobile mode on window resize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkMobileMode = () => {
|
const checkMobileMode = () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Export navigation store
|
// Export navigation store
|
||||||
export { useNavigationStore } from './navigation';
|
export { useNavigationStore, routeToScreen, screenToRoute } from './navigation';
|
||||||
export type { MobileScreen, VehicleSubScreen } from './navigation';
|
export type { MobileScreen, VehicleSubScreen } from './navigation';
|
||||||
|
|
||||||
// Export user store
|
// Export user store
|
||||||
|
|||||||
@@ -5,6 +5,45 @@ import { safeStorage } from '../utils/safe-storage';
|
|||||||
export type MobileScreen = 'Dashboard' | 'Vehicles' | 'Log Fuel' | 'Maintenance' | 'Stations' | 'Documents' | 'Settings' | 'Security' | 'Subscription' | 'AdminUsers' | 'AdminCatalog' | 'AdminCommunityStations' | 'AdminEmailTemplates' | 'AdminBackup' | 'AdminLogs';
|
export type MobileScreen = 'Dashboard' | 'Vehicles' | 'Log Fuel' | 'Maintenance' | 'Stations' | 'Documents' | 'Settings' | 'Security' | 'Subscription' | 'AdminUsers' | 'AdminCatalog' | 'AdminCommunityStations' | 'AdminEmailTemplates' | 'AdminBackup' | 'AdminLogs';
|
||||||
export type VehicleSubScreen = 'list' | 'detail' | 'add' | 'edit';
|
export type VehicleSubScreen = 'list' | 'detail' | 'add' | 'edit';
|
||||||
|
|
||||||
|
/** Maps browser URL paths to mobile screen names for direct URL navigation */
|
||||||
|
export const routeToScreen: Record<string, MobileScreen> = {
|
||||||
|
'/garage': 'Dashboard',
|
||||||
|
'/garage/dashboard': 'Dashboard',
|
||||||
|
'/garage/vehicles': 'Vehicles',
|
||||||
|
'/garage/fuel-logs': 'Log Fuel',
|
||||||
|
'/garage/maintenance': 'Maintenance',
|
||||||
|
'/garage/stations': 'Stations',
|
||||||
|
'/garage/documents': 'Documents',
|
||||||
|
'/garage/settings': 'Settings',
|
||||||
|
'/garage/settings/security': 'Security',
|
||||||
|
'/garage/settings/subscription': 'Subscription',
|
||||||
|
'/garage/settings/admin/users': 'AdminUsers',
|
||||||
|
'/garage/settings/admin/catalog': 'AdminCatalog',
|
||||||
|
'/garage/settings/admin/community-stations': 'AdminCommunityStations',
|
||||||
|
'/garage/settings/admin/email-templates': 'AdminEmailTemplates',
|
||||||
|
'/garage/settings/admin/backup': 'AdminBackup',
|
||||||
|
'/garage/settings/admin/logs': 'AdminLogs',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Reverse mapping: mobile screen name to canonical URL path */
|
||||||
|
export const screenToRoute: Record<MobileScreen, string> = {
|
||||||
|
'Dashboard': '/garage/dashboard',
|
||||||
|
'Vehicles': '/garage/vehicles',
|
||||||
|
'Log Fuel': '/garage/fuel-logs',
|
||||||
|
'Maintenance': '/garage/maintenance',
|
||||||
|
'Stations': '/garage/stations',
|
||||||
|
'Documents': '/garage/documents',
|
||||||
|
'Settings': '/garage/settings',
|
||||||
|
'Security': '/garage/settings/security',
|
||||||
|
'Subscription': '/garage/settings/subscription',
|
||||||
|
'AdminUsers': '/garage/settings/admin/users',
|
||||||
|
'AdminCatalog': '/garage/settings/admin/catalog',
|
||||||
|
'AdminCommunityStations': '/garage/settings/admin/community-stations',
|
||||||
|
'AdminEmailTemplates': '/garage/settings/admin/email-templates',
|
||||||
|
'AdminBackup': '/garage/settings/admin/backup',
|
||||||
|
'AdminLogs': '/garage/settings/admin/logs',
|
||||||
|
};
|
||||||
|
|
||||||
interface NavigationHistory {
|
interface NavigationHistory {
|
||||||
screen: MobileScreen;
|
screen: MobileScreen;
|
||||||
vehicleSubScreen?: VehicleSubScreen;
|
vehicleSubScreen?: VehicleSubScreen;
|
||||||
@@ -196,7 +235,6 @@ export const useNavigationStore = create<NavigationState>()(
|
|||||||
name: 'motovaultpro-mobile-navigation',
|
name: 'motovaultpro-mobile-navigation',
|
||||||
storage: createJSONStorage(() => safeStorage),
|
storage: createJSONStorage(() => safeStorage),
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
activeScreen: state.activeScreen,
|
|
||||||
vehicleSubScreen: state.vehicleSubScreen,
|
vehicleSubScreen: state.vehicleSubScreen,
|
||||||
selectedVehicleId: state.selectedVehicleId,
|
selectedVehicleId: state.selectedVehicleId,
|
||||||
formStates: state.formStates,
|
formStates: state.formStates,
|
||||||
|
|||||||
Reference in New Issue
Block a user