-
Notifications
You must be signed in to change notification settings - Fork 28
feat(course-page): add dynamic CTA and loading state to leaderboard rank #3535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,13 +7,20 @@ import type Owner from '@ember/owner'; | |
| import type RouterService from '@ember/routing/router-service'; | ||
| import { action } from '@ember/object'; | ||
| import { service } from '@ember/service'; | ||
| import { task } from 'ember-concurrency'; | ||
| import { task, timeout } from 'ember-concurrency'; | ||
| import { tracked } from '@glimmer/tracking'; | ||
| import type CourseStageModel from 'codecrafters-frontend/models/course-stage'; | ||
| import type RepositoryModel from 'codecrafters-frontend/models/repository'; | ||
| import computeLeaderboardCTA from 'codecrafters-frontend/utils/compute-leaderboard-cta'; | ||
|
|
||
| interface Signature { | ||
| Element: HTMLDivElement; | ||
|
|
||
| Args: { | ||
| currentCourseStage: CourseStageModel; | ||
| language: LanguageModel; | ||
| repository: RepositoryModel; | ||
| shouldShowCTA: boolean; | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -23,12 +30,28 @@ export default class LanguageLeaderboardRankSection extends Component<Signature> | |
| @service declare leaderboardEntriesCacheRegistry: LeaderboardEntriesCacheRegistryService; | ||
| @service declare router: RouterService; | ||
|
|
||
| @tracked isLoadingRank: boolean = true; | ||
| @tracked ctaText: string = 'Loading...'; | ||
|
|
||
| constructor(owner: Owner, args: Signature['Args']) { | ||
| super(owner, args); | ||
|
|
||
| this.refreshRankTask.perform(); | ||
| } | ||
|
|
||
| get computedCtaText(): string | null { | ||
| if (!this.leaderboardEntriesCache.userEntry || !this.leaderboardEntriesCache.userRankCalculation) { | ||
| return null; | ||
| } | ||
|
|
||
| return computeLeaderboardCTA( | ||
| this.leaderboardEntriesCache.userEntry, | ||
| this.leaderboardEntriesCache.userRankCalculation, | ||
| this.args.repository, | ||
| this.args.currentCourseStage, | ||
| ); | ||
| } | ||
|
|
||
| get leaderboardEntriesCache(): LeaderboardEntriesCache { | ||
| return this.leaderboardEntriesCacheRegistry.getOrCreate(this.args.language.leaderboard!); | ||
| } | ||
|
|
@@ -43,7 +66,22 @@ export default class LanguageLeaderboardRankSection extends Component<Signature> | |
| } | ||
|
|
||
| refreshRankTask = task({ restartable: true }, async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
| await this.leaderboardEntriesCacheRegistry.getOrCreate(this.args.language.leaderboard!).loadOrRefresh(); | ||
|
|
||
| this.isLoadingRank = false; | ||
| this.ctaText = ' '; // No break space | ||
|
|
||
| await timeout(500); | ||
| this.ctaText = 'Nice work!'; | ||
|
|
||
| if (this.computedCtaText) { | ||
| await timeout(2500); | ||
| this.ctaText = ' '; // No break space | ||
|
|
||
| await timeout(500); | ||
| this.ctaText = this.computedCtaText; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loading indicator stuck forever if data fetch failsThe original code used |
||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition between CTA check and assignment
The
computedCtaTextgetter is called twice inrefreshRankTask- once to check if it's truthy, then again 3000ms later for assignment. If the underlying leaderboard data changes during this window, the getter could returnnullon the second call even though the condition passed. This would assignnulltoctaText(which is typed asstring), causing the CTA to unexpectedly disappear. The value returned from the first check could be captured and reused instead.