From e1bea78910a531c2cb72f16c241c883f35c9aff5 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 29 Dec 2025 09:42:44 +0200 Subject: [PATCH 01/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 23 +++++++++++++++++++---- src/app/components/pages/Events.tsx | 10 ++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index ee6cbefdf5..4c6a08d5e5 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -1,5 +1,7 @@ import React, { ReactElement, useEffect, useRef } from "react"; import { UncontrolledTooltip } from "reactstrap"; +import { Button } from "reactstrap"; +import { Link } from "react-router-dom"; import { AUDIENCE_DISPLAY_FIELDS, filterAudienceViewsByProperties, @@ -46,6 +48,7 @@ export interface PageTitleProps { subTitle?: string; disallowLaTeX?: boolean; help?: ReactElement; + boosterVideoButton?: boolean; className?: string; audienceViews?: ViewingContext[]; onTitleEdit?: (newTitle: string) => void; @@ -55,6 +58,7 @@ export const PageTitle = ({ subTitle, disallowLaTeX, help, + boosterVideoButton, className, audienceViews, onTitleEdit, @@ -65,14 +69,14 @@ export const PageTitle = ({ useEffect(() => { dispatch(mainContentIdSlice.actions.set("main-heading")); - }, []); + }, [dispatch]); useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; if (element && (window as any).followedAtLeastOneSoftLink && !openModal) { element.focus(); } - }, [currentPageTitle]); + }, [currentPageTitle, openModal]); return (

@@ -88,7 +92,18 @@ export const PageTitle = ({ {audienceViews && } - {help && ( + + {/* Show booster video button OR help tooltip, but not both */} + {boosterVideoButton ? ( + + ) : help ? (
Help @@ -97,7 +112,7 @@ export const PageTitle = ({ {help} - )} + ) : null}

); }; diff --git a/src/app/components/pages/Events.tsx b/src/app/components/pages/Events.tsx index 086d728f16..fc970f2729 100644 --- a/src/app/components/pages/Events.tsx +++ b/src/app/components/pages/Events.tsx @@ -95,8 +95,6 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => dispatch(getEventMapData(startIndex, -1, typeFilter, statusFilter, stageFilter)); }, [dispatch, typeFilter, statusFilter, stageFilter]); - const pageHelp = Follow the links below to find out more about our FREE events.; - const metaDescriptionCS = "A level and GCSE Computer Science live online training. Revision and extension workshops for students."; @@ -104,7 +102,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => <>
- +
{/* Filters */} @@ -123,7 +121,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => query.show_reservations_only = selectedFilter === EventStatusFilter["My event reservations"] ? true : undefined; query.event_status = selectedFilter == EventStatusFilter["All events"] ? "all" : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {/* Tutors are considered students w.r.t. events currently, so cannot see teacher-only events */} @@ -150,7 +148,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => onChange={(e) => { const selectedType = e.target.value as EventTypeFilter; query.types = selectedType !== EventTypeFilter["All events"] ? selectedType : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {Object.entries(EventTypeFilter).map(([typeLabel, typeValue]) => ( @@ -168,7 +166,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => const selectedStage = e.target.value as EventStageFilter; query.show_stage_only = selectedStage !== EventStageFilter["All stages"] ? selectedStage : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {Object.entries(EventStageFilter) From 0c1f6392d85b459b6e059ececd6015c4c2b7eb2a Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 29 Dec 2025 09:44:32 +0200 Subject: [PATCH 02/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 4c6a08d5e5..9852f9d108 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -73,7 +73,7 @@ export const PageTitle = ({ useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; - if (element && (window as any).followedAtLeastOneSoftLink && !openModal) { + if (element && "followedAtLeastOneSoftLink" in window && window.followedAtLeastOneSoftLink && !openModal) { element.focus(); } }, [currentPageTitle, openModal]); From 4566cd3992132c3b053230d57e99eb4cd07f7537 Mon Sep 17 00:00:00 2001 From: Marius Date: Tue, 30 Dec 2025 10:48:41 +0200 Subject: [PATCH 03/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 9852f9d108..e0d1f7e59f 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -18,6 +18,12 @@ import { Helmet } from "react-helmet"; import { Markup } from "./markup"; import { EditablePageTitle } from "./inputs/EditablePageTitle"; +declare global { + interface Window { + followedAtLeastOneSoftLink?: boolean; + } +} + function AudienceViewer({ audienceViews }: { audienceViews: ViewingContext[] }) { const userContext = useUserContext(); const viewsWithMyStage = audienceViews.filter((vc) => vc.stage === userContext.stage); @@ -53,6 +59,7 @@ export interface PageTitleProps { audienceViews?: ViewingContext[]; onTitleEdit?: (newTitle: string) => void; } + export const PageTitle = ({ currentPageTitle, subTitle, @@ -65,6 +72,7 @@ export const PageTitle = ({ }: PageTitleProps) => { const dispatch = useAppDispatch(); const openModal = useAppSelector((state: AppState) => Boolean(state?.activeModals?.length)); + const user = useAppSelector((state: AppState) => state?.user); // Add this const headerRef = useRef(null); useEffect(() => { @@ -73,7 +81,7 @@ export const PageTitle = ({ useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; - if (element && "followedAtLeastOneSoftLink" in window && window.followedAtLeastOneSoftLink && !openModal) { + if (element && window.followedAtLeastOneSoftLink && !openModal) { element.focus(); } }, [currentPageTitle, openModal]); @@ -97,9 +105,15 @@ export const PageTitle = ({ {boosterVideoButton ? ( From c1c8493bc9a012ff94ed7ba974bd2bccb3ae00a5 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 2 Jan 2026 09:43:12 +0200 Subject: [PATCH 04/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index e0d1f7e59f..88aa10224c 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -109,11 +109,6 @@ export const PageTitle = ({ user?.loggedIn ? "/pages/test_page_booster_recording" : "/login?target=/pages/test_page_booster_recording" } className="primary-button text-light align-self-center ml-sm-2" - style={{ - padding: "12px 32px", - fontSize: "18px", - lineHeight: "27px", - }} > Watch booster videos From d3f8281b0fd0e38448eaa7c3c585044f45c0f2cc Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 29 Dec 2025 09:42:44 +0200 Subject: [PATCH 05/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 23 +++++++++++++++++++---- src/app/components/pages/Events.tsx | 10 ++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index ee6cbefdf5..4c6a08d5e5 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -1,5 +1,7 @@ import React, { ReactElement, useEffect, useRef } from "react"; import { UncontrolledTooltip } from "reactstrap"; +import { Button } from "reactstrap"; +import { Link } from "react-router-dom"; import { AUDIENCE_DISPLAY_FIELDS, filterAudienceViewsByProperties, @@ -46,6 +48,7 @@ export interface PageTitleProps { subTitle?: string; disallowLaTeX?: boolean; help?: ReactElement; + boosterVideoButton?: boolean; className?: string; audienceViews?: ViewingContext[]; onTitleEdit?: (newTitle: string) => void; @@ -55,6 +58,7 @@ export const PageTitle = ({ subTitle, disallowLaTeX, help, + boosterVideoButton, className, audienceViews, onTitleEdit, @@ -65,14 +69,14 @@ export const PageTitle = ({ useEffect(() => { dispatch(mainContentIdSlice.actions.set("main-heading")); - }, []); + }, [dispatch]); useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; if (element && (window as any).followedAtLeastOneSoftLink && !openModal) { element.focus(); } - }, [currentPageTitle]); + }, [currentPageTitle, openModal]); return (

@@ -88,7 +92,18 @@ export const PageTitle = ({ {audienceViews && } - {help && ( + + {/* Show booster video button OR help tooltip, but not both */} + {boosterVideoButton ? ( + + ) : help ? (
Help @@ -97,7 +112,7 @@ export const PageTitle = ({ {help} - )} + ) : null}

); }; diff --git a/src/app/components/pages/Events.tsx b/src/app/components/pages/Events.tsx index 086d728f16..fc970f2729 100644 --- a/src/app/components/pages/Events.tsx +++ b/src/app/components/pages/Events.tsx @@ -95,8 +95,6 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => dispatch(getEventMapData(startIndex, -1, typeFilter, statusFilter, stageFilter)); }, [dispatch, typeFilter, statusFilter, stageFilter]); - const pageHelp = Follow the links below to find out more about our FREE events.; - const metaDescriptionCS = "A level and GCSE Computer Science live online training. Revision and extension workshops for students."; @@ -104,7 +102,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => <>
- +
{/* Filters */} @@ -123,7 +121,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => query.show_reservations_only = selectedFilter === EventStatusFilter["My event reservations"] ? true : undefined; query.event_status = selectedFilter == EventStatusFilter["All events"] ? "all" : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {/* Tutors are considered students w.r.t. events currently, so cannot see teacher-only events */} @@ -150,7 +148,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => onChange={(e) => { const selectedType = e.target.value as EventTypeFilter; query.types = selectedType !== EventTypeFilter["All events"] ? selectedType : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {Object.entries(EventTypeFilter).map(([typeLabel, typeValue]) => ( @@ -168,7 +166,7 @@ export const Events = withRouter(({ history, location }: RouteComponentProps) => const selectedStage = e.target.value as EventStageFilter; query.show_stage_only = selectedStage !== EventStageFilter["All stages"] ? selectedStage : undefined; - history.push({ pathname: location.pathname, search: queryString.stringify(query as any) }); + history.push({ pathname: location.pathname, search: queryString.stringify(query as never) }); }} > {Object.entries(EventStageFilter) From 2dae107832d024ceb3ae25034dd5d8f2a37fb634 Mon Sep 17 00:00:00 2001 From: Marius Date: Mon, 29 Dec 2025 09:44:32 +0200 Subject: [PATCH 06/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 4c6a08d5e5..9852f9d108 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -73,7 +73,7 @@ export const PageTitle = ({ useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; - if (element && (window as any).followedAtLeastOneSoftLink && !openModal) { + if (element && "followedAtLeastOneSoftLink" in window && window.followedAtLeastOneSoftLink && !openModal) { element.focus(); } }, [currentPageTitle, openModal]); From cafa41078ce77f5255c262593e6fb3cec3fd83a0 Mon Sep 17 00:00:00 2001 From: Marius Date: Tue, 30 Dec 2025 10:48:41 +0200 Subject: [PATCH 07/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 9852f9d108..e0d1f7e59f 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -18,6 +18,12 @@ import { Helmet } from "react-helmet"; import { Markup } from "./markup"; import { EditablePageTitle } from "./inputs/EditablePageTitle"; +declare global { + interface Window { + followedAtLeastOneSoftLink?: boolean; + } +} + function AudienceViewer({ audienceViews }: { audienceViews: ViewingContext[] }) { const userContext = useUserContext(); const viewsWithMyStage = audienceViews.filter((vc) => vc.stage === userContext.stage); @@ -53,6 +59,7 @@ export interface PageTitleProps { audienceViews?: ViewingContext[]; onTitleEdit?: (newTitle: string) => void; } + export const PageTitle = ({ currentPageTitle, subTitle, @@ -65,6 +72,7 @@ export const PageTitle = ({ }: PageTitleProps) => { const dispatch = useAppDispatch(); const openModal = useAppSelector((state: AppState) => Boolean(state?.activeModals?.length)); + const user = useAppSelector((state: AppState) => state?.user); // Add this const headerRef = useRef(null); useEffect(() => { @@ -73,7 +81,7 @@ export const PageTitle = ({ useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; - if (element && "followedAtLeastOneSoftLink" in window && window.followedAtLeastOneSoftLink && !openModal) { + if (element && window.followedAtLeastOneSoftLink && !openModal) { element.focus(); } }, [currentPageTitle, openModal]); @@ -97,9 +105,15 @@ export const PageTitle = ({ {boosterVideoButton ? ( From 2dff91477e2099dbec1c9de573a58e2c9866b2b3 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 2 Jan 2026 09:43:12 +0200 Subject: [PATCH 08/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index e0d1f7e59f..88aa10224c 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -109,11 +109,6 @@ export const PageTitle = ({ user?.loggedIn ? "/pages/test_page_booster_recording" : "/login?target=/pages/test_page_booster_recording" } className="primary-button text-light align-self-center ml-sm-2" - style={{ - padding: "12px 32px", - fontSize: "18px", - lineHeight: "27px", - }} > Watch booster videos From c02496a1b95a39274f7993ae6b376503199e6025 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 2 Jan 2026 11:03:02 +0200 Subject: [PATCH 09/10] 768 - Implement booster video CTA on Events page --- src/app/components/elements/PageTitle.tsx | 64 ++++++++++++++--------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 88aa10224c..476e2ce4ba 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -1,6 +1,5 @@ import React, { ReactElement, useEffect, useRef } from "react"; -import { UncontrolledTooltip } from "reactstrap"; -import { Button } from "reactstrap"; +import { UncontrolledTooltip, Button } from "reactstrap"; import { Link } from "react-router-dom"; import { AUDIENCE_DISPLAY_FIELDS, @@ -22,6 +21,9 @@ declare global { interface Window { followedAtLeastOneSoftLink?: boolean; } + + // eslint-disable-next-line no-var + var followedAtLeastOneSoftLink: boolean | undefined; } function AudienceViewer({ audienceViews }: { audienceViews: ViewingContext[] }) { @@ -72,20 +74,51 @@ export const PageTitle = ({ }: PageTitleProps) => { const dispatch = useAppDispatch(); const openModal = useAppSelector((state: AppState) => Boolean(state?.activeModals?.length)); - const user = useAppSelector((state: AppState) => state?.user); // Add this + const user = useAppSelector((state: AppState) => state?.user); const headerRef = useRef(null); useEffect(() => { dispatch(mainContentIdSlice.actions.set("main-heading")); }, [dispatch]); + useEffect(() => { document.title = currentPageTitle + " — Isaac " + SITE_SUBJECT_TITLE; const element = headerRef.current; - if (element && window.followedAtLeastOneSoftLink && !openModal) { + if (element && globalThis.followedAtLeastOneSoftLink && !openModal) { element.focus(); } }, [currentPageTitle, openModal]); + // Extract nested ternary logic + const renderHelpOrBoosterButton = () => { + if (boosterVideoButton) { + const targetPath = user?.loggedIn + ? "/pages/test_page_booster_recording" + : "/login?target=/pages/test_page_booster_recording"; + + return ( + + ); + } + + if (help) { + return ( + +
+ Help +
+ + {help} + +
+ ); + } + + return null; + }; + return (

@@ -100,28 +133,7 @@ export const PageTitle = ({ {audienceViews && } - - {/* Show booster video button OR help tooltip, but not both */} - {boosterVideoButton ? ( - - ) : help ? ( - -
- Help -
- - {help} - -
- ) : null} + {renderHelpOrBoosterButton()}

); }; From 35eec3851498b37a3440c739f06926bd2baae928 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 22 Jan 2026 23:16:25 +0200 Subject: [PATCH 10/10] Redirect link --- src/app/components/elements/PageTitle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/elements/PageTitle.tsx b/src/app/components/elements/PageTitle.tsx index 476e2ce4ba..954abbcd72 100644 --- a/src/app/components/elements/PageTitle.tsx +++ b/src/app/components/elements/PageTitle.tsx @@ -93,8 +93,8 @@ export const PageTitle = ({ const renderHelpOrBoosterButton = () => { if (boosterVideoButton) { const targetPath = user?.loggedIn - ? "/pages/test_page_booster_recording" - : "/login?target=/pages/test_page_booster_recording"; + ? "/pages/booster_video_binary_conversion_and_addition" + : "/login?target=/pages/booster_video_binary_conversion_and_addition"; return (