Skip to content

Commit 6be4e06

Browse files
OpenStaxClaudeclaude
authored andcommitted
Port remaining book details page modules from JS to TS
Converted all remaining JavaScript files in src/app/pages/details to TypeScript: Desktop view modules: - desktop-view.js → desktop-view.tsx - details-tab.js → details-tab.tsx - videos-tab.js → videos-tab.tsx - partners.js → partners.tsx - import-*.js → import-*.ts Phone view modules: - phone-view.js → phone-view.tsx - details-pane.js → details-pane.tsx - student-resources-pane.js → student-resources-pane.tsx - instructor-resources-pane.js → instructor-resources-pane.tsx All conversions follow TypeScript best practices: - Avoided using 'any' type - Preferred 'type' to 'interface' - Used type inference where possible - Inline type definitions for function parameters - Line lengths kept under 120 characters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Port remaining book details page modules from JS to TS Converted all remaining JavaScript files in src/app/pages/details to TypeScript: Desktop view modules: - desktop-view.js → desktop-view.tsx - details-tab.js → details-tab.tsx - videos-tab.js → videos-tab.tsx - partners.js → partners.tsx - import-*.js → import-*.ts Phone view modules: - phone-view.js → phone-view.tsx - details-pane.js → details-pane.tsx - student-resources-pane.js → student-resources-pane.tsx - instructor-resources-pane.js → instructor-resources-pane.tsx All conversions follow TypeScript best practices: - Avoided using 'any' type - Preferred 'type' to 'interface' - Used type inference where possible - Inline type definitions for function parameters - Line lengths kept under 120 characters 🤖 Generated with [Claude Code](https://claude.com/claude-code) Revert to JS (loaders) Co-Authored-By: Claude <[email protected]>
1 parent 5155545 commit 6be4e06

File tree

15 files changed

+416
-249
lines changed

15 files changed

+416
-249
lines changed

src/app/pages/details/context.tsx renamed to src/app/pages/details/context.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {useState} from 'react';
22
import buildContext from '~/components/jsx-helpers/build-context';
33
import type {ResourceData} from './common/resource-box/resource-box-utils';
44
import type {PromoteData} from './desktop-view/promo';
5+
import type {Model as GetThisTitleModel} from './common/get-this-title';
56

67
export type LocaleType = {
78
locale: string;
@@ -24,7 +25,7 @@ type StuffContent = {
2425
heading: string;
2526
};
2627
};
27-
type VideoContent = {
28+
export type VideoContent = {
2829
title: string;
2930
description: string;
3031
embed: string;
@@ -37,13 +38,14 @@ type Author = {
3738

3839
export type IsbnType = 'print' | 'printSoftcover' | 'digital' | 'assignable';
3940

40-
export type ContextValues = {
41+
export type ContextValues = GetThisTitleModel & {
4142
id: number;
4243
slug: string;
4344
translations: Array<TranslationType>;
4445
bookState: string;
4546
comingSoon: boolean;
4647
coverColor: string;
48+
description: string;
4749
meta: LocaleType;
4850
reverseGradient: boolean;
4951
title: string;
@@ -59,7 +61,7 @@ export type ContextValues = {
5961
webinarContent?: WebinarContent;
6062
freeStuffStudent: StuffContent;
6163
freeStuffInstructor: StuffContent;
62-
videos: VideoContent[];
64+
videos: [VideoContent[]] | never[];
6365
setUseCardBackground?: React.Dispatch<React.SetStateAction<boolean>>;
6466
authors: Author[];
6567
created: string;

src/app/pages/details/desktop-view/desktop-view.js renamed to src/app/pages/details/desktop-view/desktop-view.tsx

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,87 +3,105 @@ import TabGroup from '~/components/tab-group/tab-group';
33
import ContentGroup from '~/components/content-group/content-group';
44
import {useIntl} from 'react-intl';
55
import VideoTab from './videos-tab/videos-tab';
6-
import useDetailsContext from '../context';
6+
import useDetailsContext, {type ContextValues} from '../context';
77
import {GiveLink} from '../common/common';
88
import {useNavigate} from 'react-router-dom';
99
import $ from '~/helpers/$';
1010
import JITLoad from '~/helpers/jit-load';
1111
import {findSelectedTab, replaceSearchTerm} from '../common/tab-utils';
1212
import './desktop-view.scss';
1313

14-
const importDetailsTab = () => import('./details-tab/details-tab.js');
15-
const importInstructorTab = () => import('./instructor-resource-tab/import-instructor-resource-tab.js');
16-
const importStudentTab = () => import('./student-resource-tab/import-student-resource-tab.js');
14+
const importDetailsTab = () => import('./details-tab/details-tab');
15+
const importInstructorTab = () =>
16+
import('./instructor-resource-tab/import-instructor-resource-tab');
17+
const importStudentTab = () =>
18+
import('./student-resource-tab/import-student-resource-tab');
1719

1820
// eslint-disable-next-line complexity
19-
function useLabelsFromModel(model, polish) {
21+
function useLabelsFromModel(model: ContextValues, polish: boolean) {
2022
const intl = useIntl();
21-
const tabLabels = [polish ? 'Szczegóły książki' : intl.formatMessage({
22-
id: 'tabs.bookDetails'
23-
})];
23+
const tabLabels = [
24+
polish
25+
? 'Szczegóły książki'
26+
: intl.formatMessage({
27+
id: 'tabs.bookDetails'
28+
})
29+
];
2430

2531
if (!polish && model.freeStuffInstructor.content) {
26-
tabLabels.push(intl.formatMessage({
27-
id: 'tabs.instructorResources'
28-
}));
32+
tabLabels.push(
33+
intl.formatMessage({
34+
id: 'tabs.instructorResources'
35+
})
36+
);
2937
}
3038
if (!polish && model.freeStuffStudent.content) {
31-
tabLabels.push(intl.formatMessage({
32-
id: 'tabs.studentResources'
33-
}));
39+
tabLabels.push(
40+
intl.formatMessage({
41+
id: 'tabs.studentResources'
42+
})
43+
);
3444
}
3545
if (model.videos.length) {
36-
tabLabels.push(intl.formatMessage({
37-
id: 'tabs.videos'
38-
}));
46+
tabLabels.push(
47+
intl.formatMessage({
48+
id: 'tabs.videos'
49+
})
50+
);
3951
}
4052

4153
return tabLabels;
4254
}
4355

44-
function useSelectedLabelTiedToSearchString(labels) {
56+
function useSelectedLabelTiedToSearchString(labels: string[]) {
4557
const navigate = useNavigate();
4658
const selectedTab = findSelectedTab(labels);
4759
const updateSelectedLabel = React.useCallback(
48-
(newValue) => {
60+
(newValue: string) => {
4961
const newSearchString = replaceSearchTerm(labels, newValue);
5062

5163
navigate(newSearchString, {replace: true});
5264
},
5365
[labels, navigate]
5466
);
5567

56-
return [selectedTab, updateSelectedLabel];
68+
return [selectedTab, updateSelectedLabel] as const;
5769
}
5870

59-
function StubUntilSeen({active, ...JLParams}) {
71+
type StubUntilSeenProps = {
72+
active: boolean;
73+
importFn: () => Promise<{default: React.ComponentType<unknown>}>;
74+
polish?: boolean;
75+
model?: ContextValues;
76+
};
77+
78+
function StubUntilSeen({active, ...JLParams}: StubUntilSeenProps) {
6079
const [seen, setSeen] = useState(false);
6180

62-
useEffect(
63-
() => {
64-
if (active) {
65-
setSeen(true);
66-
}
67-
},
68-
[active]
69-
);
81+
useEffect(() => {
82+
if (active) {
83+
setSeen(true);
84+
}
85+
}, [active]);
7086

71-
return (
72-
seen ? <JITLoad {...JLParams} /> : <div />
73-
);
87+
return seen ? <JITLoad {...JLParams} /> : <div />;
7488
}
7589

7690
export default function DesktopView() {
7791
const model = useDetailsContext();
7892
const polish = $.isPolish(model.title);
7993
const labels = useLabelsFromModel(model, polish);
80-
const [selectedLabel, setSelectedLabel] = useSelectedLabelTiedToSearchString(labels);
94+
const [selectedLabel, setSelectedLabel] =
95+
useSelectedLabelTiedToSearchString(labels);
8196
const TabTag = 'h2';
8297
const activeIndex = labels.indexOf(selectedLabel);
8398
const isShowingCards = selectedLabel !== 'Book details';
8499
const setUseCardBackground = model.setUseCardBackground;
85100

86-
useEffect(() => setUseCardBackground(isShowingCards), [isShowingCards, setUseCardBackground]);
101+
useEffect(
102+
() => setUseCardBackground?.(isShowingCards),
103+
[isShowingCards, setUseCardBackground]
104+
);
87105

88106
return (
89107
<React.Fragment>
@@ -98,9 +116,21 @@ export default function DesktopView() {
98116
</div>
99117
<div className="tab-content">
100118
<ContentGroup activeIndex={activeIndex} labels={labels}>
101-
<StubUntilSeen active={activeIndex===0} importFn={importDetailsTab} polish={polish} model={model} />
102-
<StubUntilSeen active={activeIndex===1} importFn={importInstructorTab} model={model} />
103-
<StubUntilSeen active={activeIndex===2} importFn={importStudentTab} />
119+
<StubUntilSeen
120+
active={activeIndex === 0}
121+
importFn={importDetailsTab}
122+
polish={polish}
123+
model={model}
124+
/>
125+
<StubUntilSeen
126+
active={activeIndex === 1}
127+
importFn={importInstructorTab}
128+
model={model}
129+
/>
130+
<StubUntilSeen
131+
active={activeIndex === 2}
132+
importFn={importStudentTab}
133+
/>
104134
{model.videos[0] && <VideoTab videos={model.videos[0]} />}
105135
</ContentGroup>
106136
</div>

src/app/pages/details/desktop-view/details-tab/details-tab.js renamed to src/app/pages/details/desktop-view/details-tab/details-tab.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import GetThisTitle from '../../common/get-this-title';
66
import LetUsKnow from '../../common/let-us-know/let-us-know';
77
import SavingsBlurb from '../../common/savings-blurb';
88
import Promo from '../promo';
9+
import type {ContextValues} from '../../context';
910
import './details-tab.scss';
1011

11-
function PolishTab({model}) {
12+
function PolishTab({model}: {model: ContextValues}) {
1213
return (
1314
<div className="details-tab">
1415
<div className="sidebar">
@@ -33,13 +34,16 @@ function PolishTab({model}) {
3334
);
3435
}
3536

36-
function EnglishTab({model}) {
37+
function EnglishTab({model}: {model: ContextValues}) {
3738
return (
3839
<div className="details-tab">
3940
<div className="sidebar">
4041
<div>
4142
<h3>
42-
<FormattedMessage id="getTheBook" defaultMessage="Get the book" />
43+
<FormattedMessage
44+
id="getTheBook"
45+
defaultMessage="Get the book"
46+
/>
4347
</h3>
4448
<GetThisTitle model={model} />
4549
</div>
@@ -51,7 +55,10 @@ function EnglishTab({model}) {
5155
<Promo promoteSnippet={model.promoteSnippet} />
5256
<div className="loc-summary-text">
5357
<h3>
54-
<FormattedMessage id="summary" defaultMessage="Summary" />
58+
<FormattedMessage
59+
id="summary"
60+
defaultMessage="Summary"
61+
/>
5562
</h3>
5663
<RawHTML html={model.description} />
5764
</div>
@@ -66,8 +73,14 @@ function EnglishTab({model}) {
6673
);
6774
}
6875

69-
export default function DetailsTab({model, polish}) {
76+
export default function DetailsTab({
77+
model,
78+
polish
79+
}: {
80+
model: ContextValues;
81+
polish: boolean;
82+
}) {
7083
const Child = polish ? PolishTab : EnglishTab;
7184

72-
return (<Child model={model} />);
85+
return <Child model={model} />;
7386
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import DetailsTab from './details-tab';
2+
3+
export default DetailsTab;

src/app/pages/details/desktop-view/instructor-resource-tab/partners/partners.js renamed to src/app/pages/details/desktop-view/instructor-resource-tab/partners/partners.tsx

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,41 @@ import {useNavigate} from 'react-router-dom';
33
import PartnerCard from '~/components/partner-card/partner-card';
44
import './partners.scss';
55

6-
export default function Partners({bookAbbreviation, model}) {
6+
type PartnerBlurb = {
7+
url: string;
8+
name: string;
9+
type: string;
10+
image: string;
11+
cost?: string;
12+
verifiedFeatures: string[];
13+
};
14+
15+
type PartnersModel = {
16+
title: string;
17+
seeMoreText: string;
18+
blurbs: PartnerBlurb[];
19+
badgeImage?: string;
20+
};
21+
22+
export default function Partners({
23+
bookAbbreviation,
24+
model
25+
}: {
26+
bookAbbreviation: string;
27+
model: PartnersModel;
28+
}) {
729
const {title, seeMoreText, blurbs, badgeImage} = model;
830
const navigate = useNavigate();
931
const onClick = React.useCallback(
10-
(event) => {
11-
const destUrl = event.target.getAttribute('href');
32+
(event: React.MouseEvent<HTMLAnchorElement>) => {
33+
const destUrl = event.currentTarget.getAttribute('href');
1234

13-
navigate(destUrl, {
14-
book: bookAbbreviation,
15-
redirect: true
16-
});
35+
if (destUrl) {
36+
navigate(destUrl, {
37+
book: bookAbbreviation,
38+
redirect: true
39+
} as never);
40+
}
1741
event.preventDefault();
1842
},
1943
[navigate, bookAbbreviation]
@@ -34,7 +58,10 @@ export default function Partners({bookAbbreviation, model}) {
3458
{seeMoreText}
3559
</a>
3660
</div>
37-
<div className="blurb-scroller" data-analytics-content-list={title}>
61+
<div
62+
className="blurb-scroller"
63+
data-analytics-content-list={title}
64+
>
3865
<ul className="blurbs">
3966
{blurbs.map((blurb) => (
4067
<li key={blurb.url}>
@@ -43,11 +70,13 @@ export default function Partners({bookAbbreviation, model}) {
4370
title={blurb.name}
4471
href={blurb.url}
4572
logoUrl={blurb.image}
46-
tags={[blurb.cost, blurb.type].filter((x) => x)}
73+
tags={[blurb.cost, blurb.type].filter(
74+
(x) => x
75+
)}
4776
onClick={onClick}
4877
badgeImage={badgeImage}
4978
verifiedFeatures={blurb.verifiedFeatures}
50-
analyticsContentType='Partner Profile'
79+
analyticsContentType="Partner Profile"
5180
/>
5281
</li>
5382
))}

src/app/pages/details/desktop-view/videos-tab/videos-tab.js

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
import RawHTML from '~/components/jsx-helpers/raw-html';
3+
import './videos-tab.scss';
4+
5+
type Video = {
6+
title: string;
7+
description: string;
8+
embed: string;
9+
};
10+
11+
export default function VideoTab({videos}: {videos: Video[]}) {
12+
return (
13+
<div className="videos-tab">
14+
{videos.map(({title, description, embed}) => (
15+
<div className="video-block" key={title}>
16+
<h2 className="title">{title}</h2>
17+
<RawHTML
18+
html={description}
19+
className="description"
20+
/>
21+
<RawHTML html={embed} className="embed" embed />
22+
</div>
23+
))}
24+
</div>
25+
);
26+
}

0 commit comments

Comments
 (0)