diff --git a/e2e/oauth/oauth-overlay.spec.ts b/e2e/oauth/oauth-overlay.spec.ts index 569719d98..6788bb24e 100644 --- a/e2e/oauth/oauth-overlay.spec.ts +++ b/e2e/oauth/oauth-overlay.spec.ts @@ -35,7 +35,7 @@ test.describe("OAuth Overlay", () => { ); test.beforeEach(async ({ page }) => { await prepareOAuthTestPage(page); - await page.goto("/"); + await page.goto("/week"); await waitForAppReady(page); }); @@ -204,7 +204,7 @@ test.describe("OAuth Overlay - Edge Cases", () => { test.beforeEach(async ({ page }) => { await prepareOAuthTestPage(page); - await page.goto("/"); + await page.goto("/week"); await waitForAppReady(page); }); diff --git a/e2e/utils/event-test-utils.ts b/e2e/utils/event-test-utils.ts index 59e52f652..d0207de66 100644 --- a/e2e/utils/event-test-utils.ts +++ b/e2e/utils/event-test-utils.ts @@ -77,7 +77,7 @@ const ensureWeekView = async (page: Page) => { await viewButton.waitFor({ state: "visible", timeout: 5000 }); await viewButton.click(); await page.getByRole("option", { name: "Week" }).click(); - await page.waitForURL((url) => url.pathname === "/", { timeout: 10000 }); + await page.waitForURL((url) => url.pathname === "/week", { timeout: 10000 }); // Verify we actually switched to Week view await weekViewButton.waitFor({ state: "visible", timeout: 5000 }); @@ -105,7 +105,7 @@ export const prepareCalendarPage = async (page: Page) => { localStorage.setItem("compass.onboarding", JSON.stringify(value)); }, ONBOARDING_STATE); - await page.goto("/", { waitUntil: "networkidle" }); + await page.goto("/week", { waitUntil: "networkidle" }); // Wait for React app to mount by checking for root element with content await page.waitForFunction( diff --git a/packages/web/src/common/constants/routes.ts b/packages/web/src/common/constants/routes.ts index 8360eef0c..872e4fc28 100644 --- a/packages/web/src/common/constants/routes.ts +++ b/packages/web/src/common/constants/routes.ts @@ -3,6 +3,7 @@ export const ROOT_ROUTES = { LOGOUT: "/logout", CLEANUP: "/cleanup", ROOT: "/", + WEEK: "/week", DAY: "/day", DAY_DATE: "/day/:dateString", NOW: "/now", diff --git a/packages/web/src/components/SelectView/SelectView.test.tsx b/packages/web/src/components/SelectView/SelectView.test.tsx index 469042ec2..aa9bbab38 100644 --- a/packages/web/src/components/SelectView/SelectView.test.tsx +++ b/packages/web/src/components/SelectView/SelectView.test.tsx @@ -27,7 +27,7 @@ describe("SelectView", () => { const renderWithRouter = ( component: React.ReactElement, - initialRoute: string = "/", + initialRoute: string = ROOT_ROUTES.WEEK, ) => { return render( { describe("Component Rendering", () => { it("renders button with current view label for Week view", () => { - renderWithRouter(, ROOT_ROUTES.ROOT); + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); expect(button).toBeInTheDocument(); @@ -131,8 +131,8 @@ describe("SelectView", () => { expect(button).toHaveTextContent("Day"); }); - it("detects Week view when on / route", () => { - renderWithRouter(, ROOT_ROUTES.ROOT); + it("detects Week view when on /week route", () => { + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); expect(button).toHaveTextContent("Week"); @@ -329,7 +329,7 @@ describe("SelectView", () => { await user.click(weekOption); }); - expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.ROOT); + expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.WEEK); expect(mockNavigate).toHaveBeenCalledTimes(1); }); @@ -417,7 +417,7 @@ describe("SelectView", () => { describe("Keyboard Navigation", () => { it("navigates to next option with ArrowDown", async () => { const user = userEvent.setup(); - renderWithRouter(, ROOT_ROUTES.ROOT); + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); await act(async () => { @@ -475,7 +475,7 @@ describe("SelectView", () => { it("selects highlighted option with Enter key", async () => { const user = userEvent.setup(); - renderWithRouter(, ROOT_ROUTES.ROOT); + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); await act(async () => { @@ -521,7 +521,7 @@ describe("SelectView", () => { it("selects highlighted option with Space key", async () => { const user = userEvent.setup(); - renderWithRouter(, ROOT_ROUTES.ROOT); + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); await act(async () => { @@ -586,7 +586,7 @@ describe("SelectView", () => { it("wraps navigation from last to first option", async () => { const user = userEvent.setup(); - renderWithRouter(, ROOT_ROUTES.ROOT); + renderWithRouter(, ROOT_ROUTES.WEEK); const button = screen.getByRole("button"); await act(async () => { diff --git a/packages/web/src/components/SelectView/SelectView.tsx b/packages/web/src/components/SelectView/SelectView.tsx index f601c18e5..0d91222c4 100644 --- a/packages/web/src/components/SelectView/SelectView.tsx +++ b/packages/web/src/components/SelectView/SelectView.tsx @@ -40,7 +40,7 @@ export const SelectView = ({ ) { return "Day"; } - if (pathname === ROOT_ROUTES.ROOT) { + if (pathname === ROOT_ROUTES.WEEK) { return "Week"; } return "Week"; @@ -93,7 +93,7 @@ export const SelectView = ({ const options = [ { route: ROOT_ROUTES.NOW, label: "Now", view: "Now" as const }, { route: ROOT_ROUTES.DAY, label: "Day", view: "Day" as const }, - { route: ROOT_ROUTES.ROOT, label: "Week", view: "Week" as const }, + { route: ROOT_ROUTES.WEEK, label: "Week", view: "Week" as const }, ]; const dropdownId = "view-select-dropdown"; diff --git a/packages/web/src/routers/index.tsx b/packages/web/src/routers/index.tsx index 4795a53a5..e0a0c586a 100644 --- a/packages/web/src/routers/index.tsx +++ b/packages/web/src/routers/index.tsx @@ -10,6 +10,7 @@ import { AbsoluteOverflowLoader } from "@web/components/AbsoluteOverflowLoader"; import { loadDayData, loadOnboardingStatus, + loadRootData, loadSpecificDayData, } from "@web/routers/loaders"; @@ -79,14 +80,18 @@ export const router = createBrowserRouter( ), }, { - path: ROOT_ROUTES.ROOT, + path: ROOT_ROUTES.WEEK, lazy: async () => - import(/* webpackChunkName: "home" */ "@web/views/Calendar").then( + import(/* webpackChunkName: "week" */ "@web/views/Calendar").then( (module) => ({ Component: module.CalendarView, }), ), }, + { + path: ROOT_ROUTES.ROOT, + loader: loadRootData, + }, ], }, ...devOnlyRoutes, diff --git a/packages/web/src/routers/loaders.test.ts b/packages/web/src/routers/loaders.test.ts new file mode 100644 index 000000000..0e132075f --- /dev/null +++ b/packages/web/src/routers/loaders.test.ts @@ -0,0 +1,14 @@ +import { ROOT_ROUTES } from "@web/common/constants/routes"; +import { loadRootData, loadTodayData } from "@web/routers/loaders"; + +describe("loadRootData", () => { + it("redirects root route to day route with today's date", async () => { + const { dateString } = loadTodayData(); + const response = await loadRootData(); + + expect(response.status).toBe(302); + expect(response.headers.get("Location")).toBe( + `${ROOT_ROUTES.DAY}/${dateString}`, + ); + }); +}); diff --git a/packages/web/src/routers/loaders.ts b/packages/web/src/routers/loaders.ts index 149eb49f7..2543f3e6f 100644 --- a/packages/web/src/routers/loaders.ts +++ b/packages/web/src/routers/loaders.ts @@ -46,6 +46,10 @@ export async function loadDayData() { return redirect(`${ROOT_ROUTES.DAY}/${dateString}`); } +export async function loadRootData() { + return loadDayData(); +} + export async function loadSpecificDayData({ params, }: LoaderFunctionArgs): Promise { diff --git a/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.test.ts b/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.test.ts index edb204830..e2887438f 100644 --- a/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.test.ts +++ b/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.test.ts @@ -55,10 +55,10 @@ describe("useGlobalShortcuts", () => { expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.DAY); }); - it("should navigate to ROOT when '3' is pressed", () => { + it("should navigate to WEEK when '3' is pressed", () => { renderHook(() => useGlobalShortcuts()); pressKey("3"); - expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.ROOT); + expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.WEEK); }); it("should navigate to LOGOUT when 'z' is pressed", () => { diff --git a/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.ts b/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.ts index 0d96588bf..2b0cda246 100644 --- a/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.ts +++ b/packages/web/src/views/Calendar/hooks/shortcuts/useGlobalShortcuts.ts @@ -31,7 +31,7 @@ export function useGlobalShortcuts() { useKeyUpEvent({ combination: ["3"], deps: [navigate], - handler: () => navigate(ROOT_ROUTES.ROOT), + handler: () => navigate(ROOT_ROUTES.WEEK), }); useKeyUpEvent({ diff --git a/packages/web/src/views/Calendar/hooks/useRefetch.test.ts b/packages/web/src/views/Calendar/hooks/useRefetch.test.ts index e1e754bbc..f753ad335 100644 --- a/packages/web/src/views/Calendar/hooks/useRefetch.test.ts +++ b/packages/web/src/views/Calendar/hooks/useRefetch.test.ts @@ -43,7 +43,7 @@ describe("useRefetch", () => { describe("Week view behavior", () => { it("should fetch week events when on week view and fetch is needed", () => { - mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.ROOT }); + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK }); mockUseParams.mockReturnValue({}); const weekStart = "2024-01-15T00:00:00Z"; @@ -82,7 +82,7 @@ describe("useRefetch", () => { }); it("should map SOCKET_EVENT_CHANGED to WEEK_VIEW_CHANGE for week view", () => { - mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.ROOT }); + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK }); mockUseParams.mockReturnValue({}); const weekStart = "2024-01-15T00:00:00Z"; @@ -125,7 +125,7 @@ describe("useRefetch", () => { }); it("should not fetch when fetch is not needed", () => { - mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.ROOT }); + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK }); mockUseParams.mockReturnValue({}); mockUseAppSelector @@ -269,7 +269,7 @@ describe("useRefetch", () => { describe("Someday events handling", () => { it("should fetch someday events when SOCKET_SOMEDAY_EVENT_CHANGED reason", () => { - mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.ROOT }); + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK }); mockUseParams.mockReturnValue({}); const weekStart = "2024-01-15T00:00:00Z"; @@ -413,8 +413,8 @@ describe("useRefetch", () => { ); }); - it("should detect week view when pathname is /", () => { - mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.ROOT }); + it("should detect week view when pathname is /week", () => { + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK }); mockUseParams.mockReturnValue({}); const weekStart = "2024-01-15T00:00:00Z"; diff --git a/packages/web/src/views/Day/components/DayCmdPalette.test.tsx b/packages/web/src/views/Day/components/DayCmdPalette.test.tsx index 40cd6182f..00a7aeb4c 100644 --- a/packages/web/src/views/Day/components/DayCmdPalette.test.tsx +++ b/packages/web/src/views/Day/components/DayCmdPalette.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import { render } from "@web/__tests__/__mocks__/mock.render"; import * as useGoogleAuthModule from "@web/auth/hooks/oauth/useGoogleAuth"; import * as useSessionModule from "@web/auth/hooks/session/useSession"; +import { ROOT_ROUTES } from "@web/common/constants/routes"; import { keyPressed$ } from "@web/common/utils/dom/event-emitter.util"; import * as eventUtil from "@web/common/utils/event/event.util"; import { getModifierKey } from "@web/common/utils/shortcut/shortcut.util"; @@ -152,7 +153,7 @@ describe("DayCmdPalette", () => { await act(() => user.click(screen.getByText("Go to Week [3]"))); - expect(mockNavigate).toHaveBeenCalledWith("/"); + expect(mockNavigate).toHaveBeenCalledWith(ROOT_ROUTES.WEEK); }); it("calls onGoToToday when Go to Today is clicked", async () => { diff --git a/packages/web/src/views/Onboarding/constants/onboarding.constants.ts b/packages/web/src/views/Onboarding/constants/onboarding.constants.ts index f1f756e32..413d77e0c 100644 --- a/packages/web/src/views/Onboarding/constants/onboarding.constants.ts +++ b/packages/web/src/views/Onboarding/constants/onboarding.constants.ts @@ -1,3 +1,4 @@ +import { ROOT_ROUTES } from "@web/common/constants/routes"; import { OnboardingGuideViewConfig, OnboardingStepConfig, @@ -26,7 +27,10 @@ export const ONBOARDING_STEP_CONFIGS: readonly OnboardingStepConfig[] = [ id: ONBOARDING_STEPS.NAVIGATE_TO_DAY, order: 0, detectionType: "route", - detectionConfig: { route: "/day", routePrefixes: ["/day/"] }, + detectionConfig: { + route: ROOT_ROUTES.DAY, + routePrefixes: [`${ROOT_ROUTES.DAY}/`], + }, guide: { instructionsByView: { day: [{ type: "text", value: "You're already on the Day view." }], @@ -56,7 +60,10 @@ export const ONBOARDING_STEP_CONFIGS: readonly OnboardingStepConfig[] = [ id: ONBOARDING_STEPS.NAVIGATE_TO_NOW, order: 2, detectionType: "route", - detectionConfig: { route: "/now", routePrefixes: ["/now/"] }, + detectionConfig: { + route: ROOT_ROUTES.NOW, + routePrefixes: [`${ROOT_ROUTES.NOW}/`], + }, guide: { instructionsByView: { default: [ @@ -71,7 +78,7 @@ export const ONBOARDING_STEP_CONFIGS: readonly OnboardingStepConfig[] = [ id: ONBOARDING_STEPS.NAVIGATE_TO_WEEK, order: 3, detectionType: "route", - detectionConfig: { route: "/" }, + detectionConfig: { route: ROOT_ROUTES.WEEK }, guide: { instructionsByView: { default: [ @@ -105,21 +112,21 @@ export const ONBOARDING_GUIDE_VIEWS: readonly OnboardingGuideViewConfig[] = [ { id: "now", label: "Now", - routes: ["/now"], - routePrefixes: ["/now/"], + routes: [ROOT_ROUTES.NOW], + routePrefixes: [`${ROOT_ROUTES.NOW}/`], overlayVariant: "pinned", }, { id: "day", label: "Day", - routes: ["/day"], - routePrefixes: ["/day/"], + routes: [ROOT_ROUTES.DAY], + routePrefixes: [`${ROOT_ROUTES.DAY}/`], overlayVariant: "centered", }, { id: "week", label: "Week", - routes: ["/"], + routes: [ROOT_ROUTES.WEEK], overlayVariant: "centered", }, { diff --git a/packages/web/src/views/Onboarding/hooks/useGuideOverlayState.test.ts b/packages/web/src/views/Onboarding/hooks/useGuideOverlayState.test.ts index d1954345f..5f6864f7a 100644 --- a/packages/web/src/views/Onboarding/hooks/useGuideOverlayState.test.ts +++ b/packages/web/src/views/Onboarding/hooks/useGuideOverlayState.test.ts @@ -1,5 +1,6 @@ import { useLocation } from "react-router-dom"; import { renderHook } from "@testing-library/react"; +import { ROOT_ROUTES } from "@web/common/constants/routes"; import { ONBOARDING_STEPS } from "../constants/onboarding.constants"; import { markStepCompleted } from "../utils/onboarding.storage.util"; import { useGuideOverlayState } from "./useGuideOverlayState"; @@ -41,8 +42,8 @@ describe("useGuideOverlayState", () => { expect(result.current.currentView).toBe("now"); }); - it("should return 'week' for / pathname", () => { - mockUseLocation.mockReturnValue({ pathname: "/" } as any); + it("should return 'week' for /week pathname", () => { + mockUseLocation.mockReturnValue({ pathname: ROOT_ROUTES.WEEK } as any); const { result } = renderHook(() => useGuideOverlayState({ currentStep: ONBOARDING_STEPS.NAVIGATE_TO_DAY, diff --git a/packages/web/src/views/Onboarding/hooks/useStepDetection.ts b/packages/web/src/views/Onboarding/hooks/useStepDetection.ts index dabbf1151..4bafd77c6 100644 --- a/packages/web/src/views/Onboarding/hooks/useStepDetection.ts +++ b/packages/web/src/views/Onboarding/hooks/useStepDetection.ts @@ -1,7 +1,6 @@ import { useEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; import { useSession } from "@web/auth/hooks/session/useSession"; -import { ROOT_ROUTES } from "@web/common/constants/routes"; import { CompassTasksSavedEvent } from "@web/common/utils/storage/storage.types"; import { COMPASS_TASKS_SAVED_EVENT_NAME, @@ -108,9 +107,8 @@ export function useStepDetection({ | undefined; if (!routeConfig) return; - // Normalize route paths for comparison - const targetRoute = - routeConfig.route === "/" ? ROOT_ROUTES.ROOT : routeConfig.route; + // Get route paths for comparison + const targetRoute = routeConfig.route; const currentPath = location.pathname; const matchesPrefix = routeConfig.routePrefixes?.some((prefix) =>