Skip to content

Commit e40ca95

Browse files
committed
feat(auth): update authentication flow to redirect to Day view
- Refactored authentication handling to redirect users to the Day view instead of the Login page upon sign-out and when encountering authentication errors. - Removed references to the Login route in various components and tests, streamlining the onboarding and authentication process. - Updated tests to reflect changes in the authentication flow, ensuring accurate behavior when users are not authenticated. - Enhanced the AuthPrompt component to initiate Google login directly, improving user experience during sign-in.
1 parent 5a9d041 commit e40ca95

File tree

10 files changed

+50
-77
lines changed

10 files changed

+50
-77
lines changed

packages/web/src/common/apis/compass.api.test.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
} from "axios";
77
import { signOut } from "supertokens-web-js/recipe/session";
88
import { Status } from "@core/errors/status.codes";
9-
import { AUTH_FAILURE_REASONS } from "@web/common/constants/auth.constants";
109
import { ROOT_ROUTES } from "@web/common/constants/routes";
1110
import { CompassApi } from "./compass.api";
1211

@@ -72,20 +71,18 @@ describe("CompassApi interceptor auth handling", () => {
7271
CompassApi.defaults.adapter = originalAdapter;
7372
});
7473

75-
it("signs out and redirects to login with GAUTH reason when Google token is invalid", async () => {
74+
it("signs out and redirects to day when Google token is invalid", async () => {
7675
await triggerErrorResponse(Status.NOT_FOUND);
7776

7877
expect(window.alert).toHaveBeenCalledWith(
7978
"Login required, cuz security 😇",
8079
);
8180
expect(signOut).toHaveBeenCalledTimes(1);
82-
expect(assignMock).toHaveBeenCalledWith(
83-
`${ROOT_ROUTES.LOGIN}?reason=${AUTH_FAILURE_REASONS.GAUTH_SESSION_EXPIRED}`,
84-
);
81+
expect(assignMock).toHaveBeenCalledWith(ROOT_ROUTES.DAY);
8582
});
8683

87-
it("does not redirect if user is already on the login route", async () => {
88-
setLocationPath(ROOT_ROUTES.LOGIN);
84+
it("does not redirect if user is already on the day route", async () => {
85+
setLocationPath(ROOT_ROUTES.DAY);
8986

9087
await triggerErrorResponse(Status.NOT_FOUND);
9188

packages/web/src/common/apis/compass.api.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import axios, { AxiosError } from "axios";
22
import { Status } from "@core/errors/status.codes";
33
import { session } from "@web/common/classes/Session";
4-
import { AUTH_FAILURE_REASONS } from "@web/common/constants/auth.constants";
54
import { ENV_WEB } from "@web/common/constants/env.constants";
65
import { ROOT_ROUTES } from "@web/common/constants/routes";
76

@@ -21,18 +20,10 @@ const _signOut = async (status: SignoutStatus) => {
2120

2221
await session.signOut();
2322

24-
if (window.location.pathname === ROOT_ROUTES.LOGIN) {
23+
if (window.location.pathname.startsWith(ROOT_ROUTES.DAY)) {
2524
return;
2625
}
27-
28-
const searchParams = new URLSearchParams({
29-
reason:
30-
status === Status.NOT_FOUND || status === Status.GONE
31-
? AUTH_FAILURE_REASONS.GAUTH_SESSION_EXPIRED
32-
: AUTH_FAILURE_REASONS.USER_SESSION_EXPIRED,
33-
});
34-
35-
window.location.assign(`${ROOT_ROUTES.LOGIN}?${searchParams.toString()}`);
26+
window.location.assign(ROOT_ROUTES.DAY);
3627
};
3728

3829
CompassApi.interceptors.response.use(

packages/web/src/common/constants/routes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const ROOT_ROUTES = {
22
API: "/api",
3-
LOGIN: "/login",
43
LOGOUT: "/logout",
54
ONBOARDING: "/onboarding",
65
ROOT: "/",

packages/web/src/routers/index.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { AbsoluteOverflowLoader } from "@web/components/AbsoluteOverflowLoader";
88
import {
99
loadDayData,
1010
loadLoggedInData,
11-
loadLoginData,
1211
loadSpecificDayData,
1312
} from "@web/routers/loaders";
1413

@@ -83,16 +82,6 @@ export const router = createBrowserRouter(
8382
Component: module.default || module.OnboardingFlow,
8483
})),
8584
},
86-
{
87-
path: ROOT_ROUTES.LOGIN,
88-
loader: loadLoginData,
89-
lazy: async () =>
90-
import(
91-
/* webpackChunkName: "onboarding" */ "@web/views/Onboarding/OnboardingFlow"
92-
).then((module) => ({
93-
Component: module.default || module.OnboardingFlow,
94-
})),
95-
},
9685
{
9786
path: "*",
9887
lazy: async () =>

packages/web/src/routers/loaders.ts

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { LoaderFunctionArgs, redirect } from "react-router-dom";
22
import { zYearMonthDayString } from "@core/types/type.utils";
33
import dayjs, { Dayjs } from "@core/util/date/dayjs";
4-
import { AUTH_FAILURE_REASONS } from "@web/common/constants/auth.constants";
54
import { ROOT_ROUTES } from "@web/common/constants/routes";
65
import { getOnboardingProgress } from "@web/views/Onboarding/utils/onboarding.storage.util";
76

@@ -33,53 +32,28 @@ export function loadOnboardingData() {
3332
export async function loadLogoutData() {
3433
const { authenticated } = await loadAuthenticated();
3534

36-
if (!authenticated) return redirect(ROOT_ROUTES.LOGIN);
35+
if (!authenticated) return redirect(ROOT_ROUTES.DAY);
3736

3837
return { authenticated };
3938
}
4039

41-
export async function loadLoginData() {
42-
const { authenticated } = await loadAuthenticated();
43-
const { skipOnboarding } = loadOnboardingData();
44-
const { hasCompletedSignup } = loadHasCompletedSignup();
45-
46-
if (authenticated) {
47-
return redirect(skipOnboarding ? ROOT_ROUTES.ROOT : ROOT_ROUTES.ONBOARDING);
48-
}
49-
50-
// For new users (no signup completed), redirect to day view immediately
51-
if (!hasCompletedSignup) {
52-
const { dateString } = loadTodayData();
53-
return redirect(`${ROOT_ROUTES.DAY}/${dateString}`);
54-
}
55-
56-
return { authenticated, skipOnboarding };
57-
}
58-
5940
export async function loadLoggedInData(args?: LoaderFunctionArgs) {
6041
const request = args?.request ?? new Request(window.location.href);
6142
const { authenticated } = await loadAuthenticated();
6243
const { skipOnboarding } = loadOnboardingData();
6344
const { hasCompletedSignup } = loadHasCompletedSignup();
6445

65-
const { USER_SESSION_EXPIRED } = AUTH_FAILURE_REASONS;
66-
const loginRoute = `${ROOT_ROUTES.LOGIN}?reason=${USER_SESSION_EXPIRED}`;
67-
6846
// Check if we're accessing the day route
6947
const url = new URL(request.url);
7048
const pathname = url.pathname;
7149
const isDayRoute = pathname.startsWith(ROOT_ROUTES.DAY);
7250

7351
if (!authenticated) {
74-
if (isDayRoute && !hasCompletedSignup) {
52+
if (isDayRoute) {
7553
return { authenticated: false, skipOnboarding, hasCompletedSignup };
7654
}
7755

78-
return redirect(
79-
skipOnboarding || hasCompletedSignup
80-
? loginRoute
81-
: ROOT_ROUTES.ONBOARDING,
82-
);
56+
return redirect(ROOT_ROUTES.DAY);
8357
}
8458

8559
return { authenticated, skipOnboarding, hasCompletedSignup };

packages/web/src/views/Day/components/AuthPrompt/AuthPrompt.test.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ import { BrowserRouter } from "react-router-dom";
33
import { render, screen } from "@testing-library/react";
44
import userEvent from "@testing-library/user-event";
55
import { STORAGE_KEYS } from "@web/common/constants/storage.constants";
6+
import { useGoogleAuth } from "@web/common/hooks/useGoogleAuth";
67
import { AuthPrompt } from "./AuthPrompt";
78

9+
jest.mock("@web/common/hooks/useGoogleAuth", () => ({
10+
useGoogleAuth: jest.fn(),
11+
}));
12+
813
const renderWithRouter = (component: React.ReactElement) => {
914
return render(
1015
<BrowserRouter
@@ -19,6 +24,19 @@ const renderWithRouter = (component: React.ReactElement) => {
1924
};
2025

2126
describe("AuthPrompt", () => {
27+
const loginMock = jest.fn();
28+
const mockUseGoogleAuth = useGoogleAuth as jest.MockedFunction<
29+
typeof useGoogleAuth
30+
>;
31+
32+
beforeEach(() => {
33+
mockUseGoogleAuth.mockReturnValue({
34+
login: loginMock,
35+
loading: false,
36+
});
37+
loginMock.mockReset();
38+
});
39+
2240
beforeEach(() => {
2341
localStorage.clear();
2442
});
@@ -51,7 +69,7 @@ describe("AuthPrompt", () => {
5169
expect(stored.isAuthPromptDismissed).toBe(true);
5270
});
5371

54-
it("should navigate to login when 'Sign in' button is clicked", async () => {
72+
it("should start Google login when 'Sign in' button is clicked", async () => {
5573
const onDismiss = jest.fn();
5674

5775
renderWithRouter(<AuthPrompt onDismiss={onDismiss} />);
@@ -61,8 +79,6 @@ describe("AuthPrompt", () => {
6179
await userEvent.click(signInButton);
6280
});
6381

64-
// Check that navigation occurred (window.location would change in real app)
65-
// In test environment, we just verify the button click works
66-
expect(signInButton).toBeInTheDocument();
82+
expect(loginMock).toHaveBeenCalledTimes(1);
6783
});
6884
});

packages/web/src/views/Day/components/AuthPrompt/AuthPrompt.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import React from "react";
2-
import { useNavigate } from "react-router-dom";
3-
import { ROOT_ROUTES } from "@web/common/constants/routes";
2+
import { useGoogleAuth } from "@web/common/hooks/useGoogleAuth";
43
import { updateOnboardingProgress } from "@web/views/Onboarding/utils/onboarding.storage.util";
54

65
interface AuthPromptProps {
76
onDismiss: () => void;
87
}
98

109
export const AuthPrompt: React.FC<AuthPromptProps> = ({ onDismiss }) => {
11-
const navigate = useNavigate();
10+
const googleAuth = useGoogleAuth();
11+
const isLoading = googleAuth?.loading ?? false;
1212

1313
const handleDismiss = () => {
1414
updateOnboardingProgress({ isAuthPromptDismissed: true });
1515
onDismiss();
1616
};
1717

1818
const handleSignIn = () => {
19-
navigate(ROOT_ROUTES.LOGIN);
19+
void googleAuth?.login?.();
2020
};
2121

2222
return (
@@ -34,7 +34,9 @@ export const AuthPrompt: React.FC<AuthPromptProps> = ({ onDismiss }) => {
3434
<div className="flex gap-2">
3535
<button
3636
onClick={handleSignIn}
37-
className="bg-accent-primary hover:bg-accent-primary/90 flex-1 rounded px-4 py-2 text-sm font-medium text-white transition-colors"
37+
aria-busy={isLoading}
38+
disabled={isLoading}
39+
className="bg-accent-primary hover:bg-accent-primary/90 flex-1 rounded px-4 py-2 text-sm font-medium text-white transition-colors disabled:cursor-not-allowed disabled:opacity-70"
3840
>
3941
Sign in
4042
</button>

packages/web/src/views/Logout/Logout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const LogoutView = () => {
2020
setIsLoggingOut(false);
2121

2222
alert("You logged out - see ya! ✌");
23-
navigate(ROOT_ROUTES.LOGIN);
23+
navigate(ROOT_ROUTES.DAY);
2424
};
2525

2626
return (

packages/web/src/views/Onboarding/hooks/useOnboardingNotices.test.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { renderHook } from "@testing-library/react";
2+
import { useGoogleAuth } from "@web/common/hooks/useGoogleAuth";
23
import { useAuthPrompt } from "@web/views/Onboarding/hooks/useAuthPrompt";
34
import { useOnboardingNotices } from "@web/views/Onboarding/hooks/useOnboardingNotices";
45
import { useOnboardingOverlay } from "@web/views/Onboarding/hooks/useOnboardingOverlay";
56
import { useOnboardingProgress } from "@web/views/Onboarding/hooks/useOnboardingProgress";
67
import { useStoredTasks } from "@web/views/Onboarding/hooks/useStoredTasks";
78

8-
jest.mock("react-router-dom", () => ({
9-
...jest.requireActual("react-router-dom"),
10-
useNavigate: () => jest.fn(),
9+
jest.mock("@web/common/hooks/useGoogleAuth", () => ({
10+
useGoogleAuth: jest.fn(),
1111
}));
1212

1313
jest.mock("@web/views/Onboarding/hooks/useOnboardingOverlay");
@@ -27,6 +27,9 @@ const mockUseAuthPrompt = useAuthPrompt as jest.MockedFunction<
2727
const mockUseStoredTasks = useStoredTasks as jest.MockedFunction<
2828
typeof useStoredTasks
2929
>;
30+
const mockUseGoogleAuth = useGoogleAuth as jest.MockedFunction<
31+
typeof useGoogleAuth
32+
>;
3033

3134
describe("useOnboardingNotices", () => {
3235
beforeEach(() => {
@@ -35,6 +38,7 @@ describe("useOnboardingNotices", () => {
3538
currentStep: null,
3639
dismissOnboardingOverlay: jest.fn(),
3740
});
41+
mockUseGoogleAuth.mockReturnValue({ login: jest.fn(), loading: false });
3842
mockUseOnboardingProgress.mockReturnValue({ hasNavigatedDates: false });
3943
mockUseStoredTasks.mockReturnValue([]);
4044
});

packages/web/src/views/Onboarding/hooks/useOnboardingNotices.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useMemo } from "react";
2-
import { useNavigate } from "react-router-dom";
3-
import { ROOT_ROUTES } from "@web/common/constants/routes";
2+
import { useGoogleAuth } from "@web/common/hooks/useGoogleAuth";
43
import { useAuthPrompt } from "@web/views/Onboarding/hooks/useAuthPrompt";
54
import { useOnboardingOverlay } from "@web/views/Onboarding/hooks/useOnboardingOverlay";
65
import { useOnboardingProgress } from "@web/views/Onboarding/hooks/useOnboardingProgress";
@@ -12,7 +11,7 @@ interface UseOnboardingNoticesReturn {
1211
}
1312

1413
export function useOnboardingNotices(): UseOnboardingNoticesReturn {
15-
const navigate = useNavigate();
14+
const googleAuth = useGoogleAuth();
1615
const tasks = useStoredTasks();
1716
const { hasNavigatedDates } = useOnboardingProgress();
1817
const { showOnboardingOverlay } = useOnboardingOverlay();
@@ -34,15 +33,17 @@ export function useOnboardingNotices(): UseOnboardingNoticesReturn {
3433
body: "Your tasks are saved locally. Sign in to sync with Google Calendar and access your data from any device.",
3534
primaryAction: {
3635
label: "Sign in",
37-
onClick: () => navigate(ROOT_ROUTES.LOGIN),
36+
onClick: () => {
37+
void googleAuth?.login?.();
38+
},
3839
},
3940
secondaryAction: {
4041
label: "Later",
4142
onClick: dismissAuthPrompt,
4243
},
4344
},
4445
];
45-
}, [dismissAuthPrompt, navigate, showAuthPrompt]);
46+
}, [dismissAuthPrompt, googleAuth, showAuthPrompt]);
4647

4748
return { notices };
4849
}

0 commit comments

Comments
 (0)