Skip to content

Commit 4a0735f

Browse files
committed
Use controller state for repo/track instead of transitionTo query params
- Set course route queryParams repo/track to refreshModel: false - Add activeRepositoryOverride and activeRepository getter on course controller - Add handleTryOtherLanguage, handleRetryWithSameLanguage, handleSelectRepository; set repo/track and override in controller instead of transitionTo/replaceWith - Replace "Try other language" link with button calling onTryOtherLanguage - Use controller activeRepository in course and introduction templates - Pass optional callbacks from course to sidebar to repository dropdown for try-different-language, retry-same-language, and select-repository
1 parent c9093e8 commit 4a0735f

11 files changed

Lines changed: 127 additions & 42 deletions

File tree

app/components/course-page/introduction-step/create-repository-card/index.hbs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
<:expandedSection>
1515
{{#if (eq this.expandedSection.type "SelectLanguageSection")}}
1616
<CoursePage::IntroductionStep::CreateRepositoryCard::SelectLanguageSection
17-
@preferredLanguageSlug={{@preferredLanguageSlug}}
1817
@errorMessage={{@repositoryCreationErrorMessage}}
19-
@repository={{@repository}}
2018
@onLanguageSelection={{this.handleLanguageSelection}}
19+
@onTryOtherLanguage={{@onTryOtherLanguage}}
20+
@preferredLanguageSlug={{@preferredLanguageSlug}}
21+
@repository={{@repository}}
2122
/>
2223
{{else if (eq this.expandedSection.type "SelectLanguageProficiencyLevelSection")}}
2324
<CoursePage::IntroductionStep::CreateRepositoryCard::SelectLanguageProficiencyLevelSection

app/components/course-page/introduction-step/create-repository-card/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface Signature {
1414

1515
Args: {
1616
onLanguageSelection: (language: LanguageModel) => void | Promise<boolean>;
17+
onTryOtherLanguage: () => void;
1718
preferredLanguageSlug: string | undefined;
1819
repository: RepositoryModel;
1920
repositoryCreationErrorMessage?: string;

app/components/course-page/introduction-step/create-repository-card/select-language-section.hbs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,14 @@
2929
@isSelected={{true}}
3030
/>
3131

32-
<TertiaryLinkButton
32+
<TertiaryButton
3333
{{! @glint-expect-error isNew is not typed correctly }}
3434
@isDisabled={{@repository.isNew}}
35-
@route="course.introduction"
36-
@models={{array @repository.course.slug}}
37-
@query={{hash repo="new" track=null}}
35+
{{on "click" @onTryOtherLanguage}}
36+
data-test-try-other-language-button
3837
>
3938
Try other language
40-
</TertiaryLinkButton>
39+
</TertiaryButton>
4140
{{/if}}
4241
{{else}}
4342
{{#if (and (gt this.preferredLanguages.length 0) (not this.shouldShowNonPreferredLanguages))}}

app/components/course-page/introduction-step/create-repository-card/select-language-section.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ interface Signature {
1414

1515
Args: {
1616
errorMessage?: string;
17+
onLanguageSelection: (language: LanguageModel) => void;
18+
onTryOtherLanguage: () => void;
1719
preferredLanguageSlug?: string;
1820
repository: RepositoryModel;
19-
onLanguageSelection: (language: LanguageModel) => void;
2021
};
2122
}
2223

app/components/course-page/repository-dropdown/index.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ interface Signature {
1313
Element: HTMLDivElement;
1414

1515
Args: {
16-
repositories: RepositoryModel[];
1716
activeRepository: RepositoryModel;
17+
onRetryWithSameLanguage?: (dropdownActions: { close: () => void }) => void;
18+
onSelectRepository?: (repository: RepositoryModel, dropdownActions: { close: () => void }) => void;
19+
onTryDifferentLanguage?: () => void;
20+
repositories: RepositoryModel[];
1821
};
1922

2023
Blocks: {
@@ -102,24 +105,36 @@ export default class CoursePageRepositoryDropdown extends Component<Signature> {
102105

103106
@action
104107
async handleRepositoryLinkClick(repository: RepositoryModel, dropdownActions: { close: () => void }) {
105-
// TODO: Even though we're using replaceWith, this does seem to cause a history entry to be added. Debug why?
106-
await this.router.replaceWith('course', repository.course.slug, { queryParams: { repo: repository.id } }).followRedirects();
107-
dropdownActions.close();
108+
if (this.args.onSelectRepository) {
109+
this.args.onSelectRepository(repository, dropdownActions);
110+
} else {
111+
// TODO: Even though we're using replaceWith, this does seem to cause a history entry to be added. Debug why?
112+
await this.router.replaceWith('course', repository.course.slug, { queryParams: { repo: repository.id } }).followRedirects();
113+
dropdownActions.close();
114+
}
108115
}
109116

110117
@action
111118
async handleRetryWithSameLanguageActionClick(dropdownActions: { close: () => void }) {
112-
this.router
113-
.transitionTo('course.introduction', { queryParams: { repo: 'new', track: this.args.activeRepository.language!.slug } })
114-
.followRedirects();
115-
116-
dropdownActions.close();
119+
if (this.args.onRetryWithSameLanguage) {
120+
this.args.onRetryWithSameLanguage(dropdownActions);
121+
} else {
122+
this.router
123+
.transitionTo('course.introduction', { queryParams: { repo: 'new', track: this.args.activeRepository.language!.slug } })
124+
.followRedirects();
125+
dropdownActions.close();
126+
}
117127
}
118128

119129
@action
120130
async handleTryDifferentLanguageActionClick(dropdownActions: { close: () => void }) {
121-
this.router.transitionTo('course.introduction', { queryParams: { repo: 'new', track: null } });
122-
dropdownActions.close();
131+
if (this.args.onTryDifferentLanguage) {
132+
this.args.onTryDifferentLanguage();
133+
dropdownActions.close();
134+
} else {
135+
this.router.transitionTo('course.introduction', { queryParams: { repo: 'new', track: null } });
136+
dropdownActions.close();
137+
}
123138
}
124139
}
125140

app/components/course-page/sidebar/index.hbs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
data-test-course-page-sidebar
44
...attributes
55
>
6-
<CoursePage::RepositoryDropdown @repositories={{@repositories}} @activeRepository={{@activeRepository}} />
6+
<CoursePage::RepositoryDropdown
7+
@activeRepository={{@activeRepository}}
8+
@onRetryWithSameLanguage={{@onRetryWithSameLanguage}}
9+
@onSelectRepository={{@onSelectRepository}}
10+
@onTryDifferentLanguage={{@onTryDifferentLanguage}}
11+
@repositories={{@repositories}}
12+
/>
713

814
<div class="px-3 mt-1">
915
<div class="h-px bg-gray-200 dark:bg-white/5 w-full"></div>

app/components/course-page/sidebar/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ interface Signature {
1212
Element: HTMLDivElement;
1313

1414
Args: {
15-
course: CourseModel;
16-
repositories: RepositoryModel[];
1715
activeRepository: RepositoryModel;
16+
course: CourseModel;
1817
onCollapseButtonClick: () => void;
18+
onRetryWithSameLanguage?: (dropdownActions: { close: () => void }) => void;
19+
onSelectRepository?: (repository: RepositoryModel, dropdownActions: { close: () => void }) => void;
20+
onTryDifferentLanguage?: () => void;
21+
repositories: RepositoryModel[];
1922
};
2023
}
2124

app/controllers/course.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import type RouterService from '@ember/routing/router-service';
1212
import type AnalyticsEventTrackerService from 'codecrafters-frontend/services/analytics-event-tracker';
1313
import type FeatureFlagsService from 'codecrafters-frontend/services/feature-flags';
1414
import type LanguageModel from 'codecrafters-frontend/models/language';
15+
import type RepositoryModel from 'codecrafters-frontend/models/repository';
1516
import type { ModelType } from 'codecrafters-frontend/routes/course';
1617
import type { StepDefinition } from 'codecrafters-frontend/utils/course-page-step-list';
1718
import type { StepStatus, StepType } from 'codecrafters-frontend/utils/course-page-step-list/step';
19+
import { StepListDefinition } from 'codecrafters-frontend/utils/course-page-step-list';
1820
import * as Sentry from '@sentry/ember';
1921
import { task } from 'ember-concurrency';
2022

@@ -36,6 +38,9 @@ export default class CourseController extends Controller {
3638
@tracked repo: string | undefined = undefined;
3739
@tracked track: string | undefined = undefined;
3840

41+
/** When set, used instead of model.activeRepository (avoids model refresh on QP change). */
42+
@tracked activeRepositoryOverride: RepositoryModel | null = null;
43+
3944
@tracked configureGithubIntegrationModalIsOpen = false;
4045
@tracked sidebarIsExpandedOnDesktop = true;
4146
@tracked sidebarIsExpandedOnMobile = false;
@@ -48,6 +53,10 @@ export default class CourseController extends Controller {
4853

4954
@tracked repositoryCreationErrorMessage: string | undefined;
5055

56+
get activeRepository(): RepositoryModel {
57+
return this.activeRepositoryOverride ?? this.model.activeRepository;
58+
}
59+
5160
get currentUser() {
5261
return this.authenticator.currentUser;
5362
}
@@ -83,7 +92,7 @@ export default class CourseController extends Controller {
8392
this.configureGithubIntegrationModalIsOpen = true;
8493

8594
next(() => {
86-
this.router.transitionTo({ queryParams: { action: null } }); // reset param
95+
this.action = undefined;
8796
});
8897
}
8998
}
@@ -109,20 +118,22 @@ export default class CourseController extends Controller {
109118
@action
110119
async handleLanguageSelection(language: LanguageModel): Promise<boolean> {
111120
this.repositoryCreationErrorMessage = undefined;
112-
this.model.activeRepository.language = language;
121+
this.activeRepository.language = language;
113122

114123
try {
115-
await this.model.activeRepository.save();
124+
await this.activeRepository.save();
116125
} catch (error) {
117-
this.model.activeRepository.language = undefined;
126+
this.activeRepository.language = undefined;
118127
this.repositoryCreationErrorMessage =
119128
'Failed to create repository, please try again? Contact us at hello@codecrafters.io if this error persists.';
120129
Sentry.captureException(error);
121130

122131
return false;
123132
}
124133

125-
this.router.transitionTo({ queryParams: { repo: this.model.activeRepository.id, track: null } });
134+
this.repo = this.activeRepository.id;
135+
this.track = undefined;
136+
this.activeRepositoryOverride = null;
126137

127138
return true;
128139
}
@@ -132,30 +143,71 @@ export default class CourseController extends Controller {
132143
// Nothing to do at the moment
133144
}
134145

146+
@action
147+
handleRetryWithSameLanguage(dropdownActions: { close: () => void }) {
148+
const trackSlug = this.activeRepository.language?.slug ?? undefined;
149+
this.handleTryOtherLanguage(trackSlug ?? null);
150+
dropdownActions.close();
151+
}
152+
135153
@action
136154
async handleRouteChanged() {
137155
this.sidebarIsExpandedOnMobile = false;
138156
}
139157

158+
@action
159+
handleSelectRepository(repository: RepositoryModel, dropdownActions: { close: () => void }) {
160+
this.activeRepositoryOverride = repository;
161+
this.repo = repository.id;
162+
this.track = undefined;
163+
this.coursePageState.setStepList(new StepListDefinition(repository));
164+
dropdownActions.close();
165+
}
166+
140167
@action
141168
handleToggleTestResultsBar() {
142169
this.coursePageState.testResultsBarIsExpanded = !this.coursePageState.testResultsBarIsExpanded;
143170
}
144171

172+
@action
173+
handleTryOtherLanguage(track: string | null) {
174+
const course = this.model.course;
175+
const existingNew = this.store.peekAll('repository').find((repository) => repository.isNew);
176+
177+
let newRepository: RepositoryModel;
178+
179+
if (existingNew) {
180+
(existingNew as RepositoryModel).course = course;
181+
(existingNew as RepositoryModel).user = this.authenticator.currentUser!;
182+
newRepository = existingNew as RepositoryModel;
183+
} else {
184+
newRepository = this.store.createRecord('repository', {
185+
course,
186+
user: this.authenticator.currentUser,
187+
}) as RepositoryModel;
188+
}
189+
190+
this.activeRepositoryOverride = newRepository;
191+
this.repo = 'new';
192+
this.track = track ?? undefined;
193+
this.coursePageState.setStepList(new StepListDefinition(newRepository));
194+
}
195+
145196
@action
146197
async handleWillDestroyContainer() {
147198
this.teardownRouteChangeListeners();
148199
}
149200

150201
pollRepositoryTask = task({ keepLatest: true }, async (): Promise<void> => {
151-
await new RepositoryPoller(this.model.activeRepository).doPoll();
202+
await new RepositoryPoller(this.activeRepository).doPoll();
152203
});
153204

154205
@action
155206
resetController(_controller: unknown, isExiting: boolean, _transition: unknown) {
156207
if (isExiting) {
157208
this.repo = undefined;
158209
this.track = undefined;
210+
this.activeRepositoryOverride = null;
159211
}
160212
}
161213

app/routes/course.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ export default class CourseRoute extends BaseRoute {
2929

3030
queryParams = {
3131
repo: {
32-
refreshModel: true,
32+
refreshModel: false,
3333
},
3434
track: {
35-
refreshModel: true,
35+
refreshModel: false,
3636
},
3737
};
3838

app/templates/course.hbs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,23 @@
2121
{{! @glint-expect-error on-key modifier types aren't right? }}
2222
{{on-key "cmd+KeyJ" this.handleToggleTestResultsBar}}
2323
>
24-
{{#if @model.activeRepository.id}}
24+
{{#if this.activeRepository.id}}
2525
<div
2626
class="hidden"
27-
{{on-action-cable-message this.pollRepositoryTask.perform channel="RepositoryChannel" args=(hash repository_id=@model.activeRepository.id)}}
27+
{{on-action-cable-message this.pollRepositoryTask.perform channel="RepositoryChannel" args=(hash repository_id=this.activeRepository.id)}}
2828
{{on-visibility-change this.pollRepositoryTask.perform if="visible"}}
2929
/>
3030
{{/if}}
3131

3232
{{#if this.sidebarIsExpandedOnDesktop}}
3333
<CoursePage::Sidebar
34+
@activeRepository={{this.activeRepository}}
3435
@course={{@model.course}}
35-
@repositories={{@model.repositories}}
36-
@activeRepository={{@model.activeRepository}}
3736
@onCollapseButtonClick={{this.handleCollapseSidebarButtonClick}}
37+
@onRetryWithSameLanguage={{this.handleRetryWithSameLanguage}}
38+
@onSelectRepository={{this.handleSelectRepository}}
39+
@onTryDifferentLanguage={{fn this.handleTryOtherLanguage null}}
40+
@repositories={{@model.repositories}}
3841
{{! pb-12 accounts for the test results bar at the bottom !}}
3942
class="group relative pb-12 w-full max-w-[18em] shrink-0 h-screen hidden lg:flex"
4043
/>
@@ -48,10 +51,13 @@
4851
</div>
4952

5053
<CoursePage::Sidebar
54+
@activeRepository={{this.activeRepository}}
5155
@course={{@model.course}}
52-
@repositories={{@model.repositories}}
53-
@activeRepository={{@model.activeRepository}}
5456
@onCollapseButtonClick={{this.handleCollapseSidebarButtonClick}}
57+
@onRetryWithSameLanguage={{this.handleRetryWithSameLanguage}}
58+
@onSelectRepository={{this.handleSelectRepository}}
59+
@onTryDifferentLanguage={{fn this.handleTryOtherLanguage null}}
60+
@repositories={{@model.repositories}}
5561
class="group relative w-full max-w-[18em] shrink-0 h-screen lg:hidden"
5662
/>
5763
</CoursePage::Sidebar::MobileBackdrop>
@@ -66,7 +72,7 @@
6672
/>
6773

6874
<CoursePage::Header::MainSection
69-
@activeRepository={{@model.activeRepository}}
75+
@activeRepository={{this.activeRepository}}
7076
@activeStep={{this.coursePageState.activeStep}}
7177
@course={{@model.course}}
7278
@currentStep={{this.coursePageState.currentStep}}
@@ -91,7 +97,7 @@
9197
</div>
9298

9399
<CoursePage::RightSidebar
94-
@activeRepository={{@model.activeRepository}}
100+
@activeRepository={{this.activeRepository}}
95101
@course={{@model.course}}
96102
@isExpanded={{this.leaderboardIsExpanded}}
97103
@onCollapseButtonClick={{this.handleCollapseLeaderboardButtonClick}}
@@ -106,7 +112,7 @@
106112
<CoursePage::TestResultsBar
107113
@activeStep={{this.coursePageState.activeStep}}
108114
@currentStep={{this.coursePageState.currentStep}}
109-
@repository={{@model.activeRepository}}
115+
@repository={{this.activeRepository}}
110116
class="absolute bottom-0 left-0 right-0 z-20"
111117
/>
112118
</div>
@@ -115,7 +121,7 @@
115121
<ModalBackdrop>
116122
{{! @glint-expect-error: not ts-ified yet }}
117123
<CoursePage::ConfigureGithubIntegrationModal
118-
@repository={{@model.activeRepository}}
124+
@repository={{this.activeRepository}}
119125
{{! @glint-expect-error: mut not ts-ified yet }}
120126
@onClose={{fn (mut this.configureGithubIntegrationModalIsOpen) false}}
121127
/>

0 commit comments

Comments
 (0)