From 2388fa857ddfc242659249b56600a107c1a410cc Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:44:13 +0200 Subject: [PATCH 01/16] Return to page after sign up (#2340) * Return to page after sign up and onboarding * Update test to actually prove the navigation. Use next-router-mock --- authentication/views.py | 5 ++-- authentication/views_test.py | 5 +++- frontends/main/package.json | 1 + .../OnboardingPage/OnboardingPage.test.tsx | 27 ++++++++++++++----- .../OnboardingPage/OnboardingPage.tsx | 15 ++++++++++- yarn.lock | 3 ++- 6 files changed, 45 insertions(+), 11 deletions(-) diff --git a/authentication/views.py b/authentication/views.py index 8e7f92c780..d052d20593 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -4,7 +4,7 @@ from django.contrib.auth import logout from django.shortcuts import redirect -from django.utils.http import url_has_allowed_host_and_scheme +from django.utils.http import url_has_allowed_host_and_scheme, urlencode from django.views import View from main import settings @@ -80,7 +80,8 @@ def get( not profile.completed_onboarding and request.GET.get("skip_onboarding", "0") == "0" ): - redirect_url = settings.MITOL_NEW_USER_LOGIN_URL + params = urlencode({"next": redirect_url}) + redirect_url = f"{settings.MITOL_NEW_USER_LOGIN_URL}?{params}" profile.completed_onboarding = True profile.save() return redirect(redirect_url) diff --git a/authentication/views_test.py b/authentication/views_test.py index 9333c006e6..71acfce998 100644 --- a/authentication/views_test.py +++ b/authentication/views_test.py @@ -120,6 +120,9 @@ def test_custom_login_view_authenticated_user_with_onboarding(mocker): request.user = MagicMock(is_anonymous=False) request.user.profile = MagicMock(completed_onboarding=False) mocker.patch("authentication.views.get_redirect_url", return_value="/dashboard") + mocker.patch( + "authentication.views.urlencode", return_value="next=/search?resource=184" + ) mocker.patch( "authentication.views.settings.MITOL_NEW_USER_LOGIN_URL", "/onboarding" ) @@ -127,7 +130,7 @@ def test_custom_login_view_authenticated_user_with_onboarding(mocker): response = CustomLoginView().get(request) assert response.status_code == 302 - assert response.url == "/onboarding" + assert response.url == "/onboarding?next=/search?resource=184" def test_custom_login_view_authenticated_user_skip_onboarding(mocker): diff --git a/frontends/main/package.json b/frontends/main/package.json index 30b5c38df2..895a750008 100644 --- a/frontends/main/package.json +++ b/frontends/main/package.json @@ -56,6 +56,7 @@ "jest": "^29.7.0", "jest-extended": "^5.0.0", "jest-next-dynamic-ts": "^0.1.1", + "next-router-mock": "^1.0.2", "ol-test-utilities": "0.0.0", "ts-jest": "^29.2.4", "typescript": "^5" diff --git a/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx index 3b40667b6d..cdeed7290d 100644 --- a/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx +++ b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.test.tsx @@ -1,6 +1,6 @@ import React from "react" import { merge, times } from "lodash" - +import mockRouter from "next-router-mock" import { renderWithProviders, screen, @@ -18,9 +18,12 @@ import { CertificateDesiredEnum, type Profile, } from "api/v0" - import OnboardingPage from "./OnboardingPage" +jest.mock("next/navigation", () => + jest.requireActual("next-router-mock/navigation"), +) + const STEPS_DATA: Partial[] = [ { topic_interests: [factories.learningResources.topic()], @@ -59,7 +62,7 @@ const STEP_TITLES = [ const PROFILES_FOR_STEPS = times(STEPS_DATA.length, profileForStep) -const setup = async (profile: Profile) => { +const setup = async (profile: Profile, url?: string) => { allowConsoleErrors() setMockResponse.get(urls.userMe.get(), factories.user.user()) setMockResponse.get(urls.profileMe.get(), profile) @@ -68,12 +71,12 @@ const setup = async (profile: Profile) => { ...req, })) - renderWithProviders() + renderWithProviders(, url ? { url } : undefined) } // this function sets up the test and progresses the UI to the designated step -const setupAndProgressToStep = async (step: number) => { - await setup(PROFILES_FOR_STEPS[step]) +const setupAndProgressToStep = async (step: number, url?: string) => { + await setup(PROFILES_FOR_STEPS[step], url) for (let stepIdx = 0; stepIdx < step; stepIdx++) { await user.click(await findNextButton()) @@ -152,4 +155,16 @@ describe("OnboardingPage", () => { ) }, ) + + test("Redirects to next url after completion if present", async () => { + const nextUrl = encodeURIComponent( + `${process.env.NEXT_PUBLIC_ORIGIN}/search?resource=184`, + ) + const url = `${process.env.NEXT_PUBLIC_ORIGIN}/onboarding?next=${nextUrl}` + await setupAndProgressToStep(STEPS_DATA.length - 1, url) + const finishButton = await findFinishButton() + await user.click(finishButton) + + expect(mockRouter.asPath).toEqual("/search?resource=184") + }) }) diff --git a/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx index b3ea0dec5a..a267654a6e 100644 --- a/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx +++ b/frontends/main/src/app-pages/OnboardingPage/OnboardingPage.tsx @@ -32,6 +32,7 @@ import { DELIVERY_CHOICES, ProfileSchema, } from "@/common/profile" +import { useSearchParams } from "next/navigation" const NUM_STEPS = 5 @@ -154,6 +155,8 @@ const OnboardingPage: React.FC = () => { const { isLoading: userLoading, data: user } = useUserMe() const [activeStep, setActiveStep] = React.useState(0) const router = useRouter() + const searchParams = useSearchParams() + const nextUrl = searchParams.get("next") const formik = useFormik({ enableReinitialize: true, @@ -169,13 +172,23 @@ const OnboardingPage: React.FC = () => { if (activeStep < NUM_STEPS - 1) { setActiveStep((prevActiveStep) => prevActiveStep + 1) } else { - router.push(DASHBOARD_HOME) + if (nextUrl) { + router.push(nextUrl) + } else { + router.push(DASHBOARD_HOME) + } } }, validateOnChange: false, validateOnBlur: false, }) + useEffect(() => { + if (nextUrl) { + router.prefetch(nextUrl) + } + }, [nextUrl, router]) + const handleBack = () => { setActiveStep((prevActiveStep) => prevActiveStep - 1) } diff --git a/yarn.lock b/yarn.lock index 5df3fac7e3..810aca1d87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13388,6 +13388,7 @@ __metadata: moment: "npm:^2.30.1" next: "npm:^15.0.2" next-nprogress-bar: "npm:^2.4.2" + next-router-mock: "npm:^1.0.2" ol-components: "npm:0.0.0" ol-test-utilities: "npm:0.0.0" ol-utilities: "npm:0.0.0" @@ -14707,7 +14708,7 @@ __metadata: languageName: node linkType: hard -"next-router-mock@npm:^1.0.0": +"next-router-mock@npm:^1.0.0, next-router-mock@npm:^1.0.2": version: 1.0.2 resolution: "next-router-mock@npm:1.0.2" peerDependencies: From 1484ab40a34ecf02886415e3c217b9e38d42399d Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Wed, 9 Jul 2025 19:11:30 +0200 Subject: [PATCH 02/16] Remove the chat demo pages (#2341) * Remove /chat and /chat_syllabus pages * Reuse initials fn * Remove dead utility code * Remove unused * Moce test util to ol-test-utilities * Remove dead code and index files * Delist removed routes * Remove unused * Replace assertInstanceOf with invariant check --- .../main/src/app-pages/ChatPage/ChatPage.tsx | 63 ------- .../ChatSyllabusPage/ChatSyllabusPage.tsx | 157 ------------------ frontends/main/src/app/chat/page.tsx | 16 -- frontends/main/src/app/chat_syllabus/page.tsx | 16 -- frontends/main/src/app/robots.ts | 2 - .../ChannelAvatar/ChannelAvatar.tsx | 11 +- .../EmbedlyCard/EmbedlyCard.test.tsx | 7 +- .../src/components/EmbedlyCard/util.test.ts | 8 +- frontends/ol-test-utilities/src/assertions.ts | 4 +- .../src/domQueries/byImageSrc.ts | 8 +- .../ol-test-utilities/src/domQueries/forms.ts | 2 +- .../ol-test-utilities/src/mocks/mocks.test.ts | 16 -- .../ol-test-utilities/src/mocks/mocks.ts | 34 +--- frontends/ol-utilities/package.json | 4 +- frontends/ol-utilities/src/hooks/index.ts | 1 - .../ol-utilities/src/hooks/useResponsive.ts | 19 --- .../ol-utilities/src/hooks/useToggle.test.ts | 2 +- frontends/ol-utilities/src/hooks/useToggle.ts | 4 +- frontends/ol-utilities/src/index.ts | 8 +- frontends/ol-utilities/src/lib/index.ts | 7 - frontends/ol-utilities/src/predicates.ts | 34 +--- .../ol-utilities/src/querystrings.test.ts | 16 -- frontends/ol-utilities/src/querystrings.ts | 16 -- .../src/{lib => string}/utils.test.ts | 28 ---- .../ol-utilities/src/{lib => string}/utils.ts | 20 --- frontends/ol-utilities/src/strings/html.ts | 6 - frontends/ol-utilities/src/styles/colors.ts | 14 -- frontends/ol-utilities/src/styles/index.ts | 14 -- frontends/ol-utilities/src/styles/media.ts | 40 ----- .../ol-utilities/src/test-utils/factories.ts | 15 -- yarn.lock | 9 - 31 files changed, 24 insertions(+), 577 deletions(-) delete mode 100644 frontends/main/src/app-pages/ChatPage/ChatPage.tsx delete mode 100644 frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx delete mode 100644 frontends/main/src/app/chat/page.tsx delete mode 100644 frontends/main/src/app/chat_syllabus/page.tsx delete mode 100644 frontends/ol-test-utilities/src/mocks/mocks.test.ts delete mode 100644 frontends/ol-utilities/src/hooks/index.ts delete mode 100644 frontends/ol-utilities/src/hooks/useResponsive.ts delete mode 100644 frontends/ol-utilities/src/lib/index.ts delete mode 100644 frontends/ol-utilities/src/querystrings.test.ts delete mode 100644 frontends/ol-utilities/src/querystrings.ts rename frontends/ol-utilities/src/{lib => string}/utils.test.ts (57%) rename frontends/ol-utilities/src/{lib => string}/utils.ts (50%) delete mode 100644 frontends/ol-utilities/src/strings/html.ts delete mode 100644 frontends/ol-utilities/src/styles/colors.ts delete mode 100644 frontends/ol-utilities/src/styles/index.ts delete mode 100644 frontends/ol-utilities/src/styles/media.ts delete mode 100644 frontends/ol-utilities/src/test-utils/factories.ts diff --git a/frontends/main/src/app-pages/ChatPage/ChatPage.tsx b/frontends/main/src/app-pages/ChatPage/ChatPage.tsx deleted file mode 100644 index 90846db4bf..0000000000 --- a/frontends/main/src/app-pages/ChatPage/ChatPage.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client" - -import React from "react" -import { styled } from "ol-components" -import { getCsrfToken } from "@/common/utils" -import { AiChat, AiChatProps } from "@mitodl/smoot-design/ai" - -const Container = styled.div({ - margin: "40px auto", - width: "60%", -}) - -const INITIAL_MESSAGES: AiChatProps["initialMessages"] = [ - { - content: "What do you want to learn about today?", - role: "assistant", - }, -] - -export const STARTERS = [ - { - content: - "I'm interested in courses on quantum computing that offer certificates.", - }, - { - content: - "I want to learn about global warming, can you recommend any videos?", - }, - { - content: - "I am curious about AI applications for business. Do you have any free courses about that?", - }, - { - content: - "I would like to learn about linear regression, preferably at no cost.", - }, -] - -const ChatPage = () => { - return ( - - ({ - message: messages[messages.length - 1].content, - }), - }} - /> - - ) -} - -export default ChatPage diff --git a/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx b/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx deleted file mode 100644 index 0a0796951a..0000000000 --- a/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx +++ /dev/null @@ -1,157 +0,0 @@ -"use client" -import React, { useState } from "react" -import { styled, MenuItem } from "ol-components" -import { FeatureFlags } from "@/common/feature_flags" -import { useFeatureFlagEnabled } from "posthog-js/react" -import StyledContainer from "@/page-components/StyledContainer/StyledContainer" -// eslint-disable-next-line -import { InputLabel, Select } from "@mui/material" -import { Alert } from "@mitodl/smoot-design" -import { AiChat, AiChatProps } from "@mitodl/smoot-design/ai" -import { extractJSONFromComment } from "ol-utilities" -import { getCsrfToken } from "@/common/utils" - -const STARTERS: AiChatProps["conversationStarters"] = [ - { content: "What are the prerequisites for this course?" }, - { content: "Does this course include any written assignments?" }, -] - -const INITIAL_MESSAGES: AiChatProps["initialMessages"] = [ - { - content: - "Hello! I'm here to help you with any questions you have about this course.", - role: "assistant", - }, -] - -const FormContainer = styled.div(({ theme }) => ({ - display: "flex", - flexDirection: "column", - paddingTop: "40px", - gap: "40px", - width: "800px", - [theme.breakpoints.down("md")]: { - paddingTop: "24px", - gap: "32px", - }, -})) - -const StyledDebugPre = styled.pre({ - width: "80%", - whiteSpace: "pre-wrap", -}) - -const ChatSyllabusPage = () => { - const botEnabled = useFeatureFlagEnabled(FeatureFlags.RecommendationBot) - const [readableId, setReadableId] = useState("18.06SC+fall_2011") - const [collectionName, setCollectionName] = useState("content_files") - const [debugInfo, setDebugInfo] = useState("") - - return ( - - {botEnabled ? ( - <> -
- -
- Course - - Contentfile Chunk Size - -
-
-
- ({ - message: messages[messages.length - 1].content, - course_id: readableId, - collection_name: collectionName, - }), - onFinish: (message) => { - const contentParts = message.content.split("