RouterContext.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
  2. type PageType = 'home' | 'about' | 'blog' | 'careers' | 'privacy' | 'terms' | 'faq' | 'contact';
  3. interface RouterContextType {
  4. currentPage: PageType;
  5. navigateTo: (page: PageType, hash?: string) => void;
  6. }
  7. const RouterContext = createContext<RouterContextType | undefined>(undefined);
  8. // Map page types to URL paths
  9. const pageToPath: Record<PageType, string> = {
  10. home: '/',
  11. about: '/about',
  12. blog: '/blog',
  13. careers: '/careers',
  14. privacy: '/privacy',
  15. terms: '/terms',
  16. faq: '/faq',
  17. contact: '/contact',
  18. };
  19. // Map URL paths to page types
  20. const pathToPage: Record<string, PageType> = {
  21. '/': 'home',
  22. '/about': 'about',
  23. '/blog': 'blog',
  24. '/careers': 'careers',
  25. '/privacy': 'privacy',
  26. '/terms': 'terms',
  27. '/faq': 'faq',
  28. '/contact': 'contact',
  29. };
  30. // Helper function to scroll to an element with retry logic
  31. function scrollToElement(elementId: string, initialDelay: number = 150) {
  32. let attempts = 0;
  33. const maxAttempts = 10;
  34. const tryScroll = () => {
  35. const element = document.getElementById(elementId);
  36. if (element) {
  37. element.scrollIntoView({ behavior: 'smooth', block: 'start' });
  38. return true;
  39. }
  40. return false;
  41. };
  42. // Try immediately
  43. setTimeout(() => {
  44. if (!tryScroll()) {
  45. // If element not found, retry with increasing delays
  46. const retryInterval = setInterval(() => {
  47. attempts++;
  48. if (tryScroll() || attempts >= maxAttempts) {
  49. clearInterval(retryInterval);
  50. }
  51. }, 100);
  52. }
  53. }, initialDelay);
  54. }
  55. export function RouterProvider({ children }: { children: ReactNode }) {
  56. // Initialize page based on current URL path
  57. const getInitialPage = (): PageType => {
  58. const path = window.location.pathname;
  59. return pathToPage[path] || 'home';
  60. };
  61. const [currentPage, setCurrentPage] = useState<PageType>(getInitialPage);
  62. // Handle initial page load
  63. useEffect(() => {
  64. const pathname = window.location.pathname;
  65. // Ensure pathname is normalized
  66. if (pathname === '' || pathname === null) {
  67. window.history.replaceState({ page: 'home' }, '', '/');
  68. }
  69. }, []);
  70. const navigateTo = (page: PageType, hash?: string) => {
  71. setCurrentPage(page);
  72. const path = pageToPath[page];
  73. const url = hash ? `${path}#${hash}` : path;
  74. // Update browser URL without reloading
  75. window.history.pushState({ page }, '', url);
  76. if (hash) {
  77. // If there's a hash, scroll to the section
  78. scrollToElement(hash, 150);
  79. } else {
  80. window.scrollTo({ top: 0, behavior: 'smooth' });
  81. }
  82. };
  83. // Handle browser back/forward buttons
  84. useEffect(() => {
  85. const handlePopState = (event: PopStateEvent) => {
  86. const path = window.location.pathname;
  87. const page = pathToPage[path] || 'home';
  88. setCurrentPage(page);
  89. // Handle hash if present
  90. const hash = window.location.hash.replace('#', '');
  91. if (hash) {
  92. scrollToElement(hash);
  93. } else {
  94. window.scrollTo({ top: 0, behavior: 'smooth' });
  95. }
  96. };
  97. window.addEventListener('popstate', handlePopState);
  98. return () => {
  99. window.removeEventListener('popstate', handlePopState);
  100. };
  101. }, []);
  102. // Handle initial hash on page load (separate effect to run after render)
  103. useEffect(() => {
  104. const hash = window.location.hash.replace('#', '');
  105. if (hash) {
  106. // Use longer delay for initial page load to ensure all components are mounted
  107. scrollToElement(hash, 800);
  108. }
  109. }, [currentPage]);
  110. // Additional effect to handle hash on first mount
  111. useEffect(() => {
  112. const hash = window.location.hash.replace('#', '');
  113. if (hash) {
  114. // Extra attempt with even longer delay for slow network connections
  115. scrollToElement(hash, 1200);
  116. }
  117. }, []);
  118. return (
  119. <RouterContext.Provider value={{ currentPage, navigateTo }}>
  120. {children}
  121. </RouterContext.Provider>
  122. );
  123. }
  124. export function useRouter() {
  125. const context = useContext(RouterContext);
  126. if (context === undefined) {
  127. throw new Error('useRouter must be used within a RouterProvider');
  128. }
  129. return context;
  130. }