Skip to content

Commit 2256fe8

Browse files
authored
Merge pull request #2746 from codecrafters-io/CC-1654-completions
Add dynamic completion messaging and TypeScript support to CourseCompletedCard
2 parents a9bd433 + ed5e5fd commit 2256fe8

File tree

7 files changed

+93
-41
lines changed

7 files changed

+93
-41
lines changed

app/components/course-page/course-completed-card.hbs

+38-27
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,49 @@
77
</:header>
88
<:content>
99
<div class="prose dark:prose-invert" data-test-instructions-text>
10-
<p>
11-
Congratulations are in order. Only ~{{@repository.course.roundedCompletionPercentage}}% of users that attempt this challenge end up completing
12-
all stages, and you're one of them!
13-
</p>
14-
<p>
15-
Here's what you can do next:
16-
</p>
17-
<ul>
18-
<li>
19-
<strong>Polish your code. </strong>Perhaps you took a shortcut when approaching the challenge the first time. Now is a great time to clean
20-
up your code. You can simply push new commits to the existing repo, and we'll run tests just like before.
21-
</li>
22-
<li>
23-
<strong>Publish to GitHub. </strong>Share your work with the world. With one click, you can publish your CodeCrafters project to GitHub.
24-
<span role="button" class="underline" {{on "click" (fn (mut this.configureGithubIntegrationModalIsOpen) true)}}><strong>Click here</strong></span>
25-
to get started.
26-
</li>
27-
<li>
28-
<strong>Try a different approach. </strong>You can re-approach the same challenge with a new programming language, a new constraint, or a
29-
new style. To launch the challenge again, use the dropdown on the top left.
30-
</li>
31-
</ul>
32-
<p>
33-
If you've got any feedback or feature requests, feel free to let us know at
34-
<a href="mailto:[email protected]">[email protected]</a>. We respond to every single email.
35-
</p>
10+
{{! If there's no completion_message set, the backend returns null }}
11+
{{#if (eq @repository.course.completionMessageMarkdown null)}}
12+
<p>
13+
Congratulations are in order. Only ~{{@repository.course.roundedCompletionPercentage}}% of users that attempt this challenge end up
14+
completing all stages, and you're one of them!
15+
</p>
16+
<p>
17+
Here's what you can do next:
18+
</p>
19+
<ul>
20+
<li>
21+
<strong>Polish your code. </strong>Perhaps you took a shortcut when approaching the challenge the first time. Now is a great time to clean
22+
up your code. You can simply push new commits to the existing repo, and we'll run tests just like before.
23+
</li>
24+
<li>
25+
<strong>Publish to GitHub. </strong>Share your work with the world. With one click, you can publish your CodeCrafters project to GitHub.
26+
<span role="button" class="underline" {{on "click" (fn (mut this.configureGithubIntegrationModalIsOpen) true)}}><strong>Click here</strong></span>
27+
to get started.
28+
</li>
29+
<li>
30+
<strong>Try a different approach. </strong>You can re-approach the same challenge with a new programming language, a new constraint, or a
31+
new style. To launch the challenge again, use the dropdown on the top left.
32+
</li>
33+
</ul>
34+
<p>
35+
If you've got any feedback or feature requests, feel free to let us know at
36+
<a href="mailto:[email protected]">[email protected]</a>. We respond to every single email.
37+
</p>
38+
{{else}}
39+
{{! @glint-expect-error the else condition is guarded by a null check }}
40+
{{markdown-to-html @repository.course.completionMessageMarkdown}}
41+
{{/if}}
3642
</div>
3743
</:content>
3844
</CoursePage::InstructionsCard>
3945

4046
{{#if this.configureGithubIntegrationModalIsOpen}}
4147
<ModalBackdrop>
42-
<CoursePage::ConfigureGithubIntegrationModal @repository={{@repository}} @onClose={{fn (mut this.configureGithubIntegrationModalIsOpen) false}} />
48+
{{! @glint-expect-error: not ts-ified yet }}
49+
<CoursePage::ConfigureGithubIntegrationModal
50+
@repository={{@repository}}
51+
{{! @glint-expect-error: mut not ts-ified yet }}
52+
@onClose={{fn (mut this.configureGithubIntegrationModalIsOpen) false}}
53+
/>
4354
</ModalBackdrop>
4455
{{/if}}

app/components/course-page/course-completed-card.js

-9
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { tracked } from '@glimmer/tracking';
2+
import Component from '@glimmer/component';
3+
import congratulationsImage from '/assets/images/icons/congratulations.png';
4+
import type RepositoryModel from 'codecrafters-frontend/models/repository';
5+
6+
interface Signature {
7+
Element: HTMLDivElement;
8+
9+
Args: {
10+
repository: RepositoryModel;
11+
};
12+
}
13+
14+
export default class CourseCompletedCardComponent extends Component<Signature> {
15+
congratulationsImage = congratulationsImage;
16+
17+
@tracked configureGithubIntegrationModalIsOpen = false;
18+
}
19+
20+
declare module '@glint/environment-ember-loose/registry' {
21+
export default interface Registry {
22+
'CoursePage::CourseCompletedCard': typeof CourseCompletedCardComponent;
23+
}
24+
}

app/models/course.ts

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type SyncBuildpacksResponse = { error: string } | { success: boolean };
3030

3131
export default class CourseModel extends Model {
3232
@attr('date') declare buildpacksLastSyncedAt: Date;
33+
@attr('string') declare completionMessageMarkdown: string | null;
3334
@attr('number') declare completionPercentage: number;
3435
@attr() declare conceptSlugs: string[];
3536
@attr('string') declare definitionRepositoryFullName: string;

mirage/course-fixtures/dummy.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default {
66
"description_md": "Add a description for your course here.",
77
"short_description_md": "Add a short description for your course here.",
88
"completion_percentage": 15,
9+
"completion_message_markdown": "Congratulations! You've completed all stages of this course. 🎉\nWe recommend you to take a break and celebrate your achievement!",
910
"languages": [
1011
{
1112
"slug": "go"

mirage/utils/create-course-from-data.js

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function createLanguageConfigurations(server, course, courseData) {
7373

7474
function courseAttributesFromData(courseData) {
7575
return {
76+
completionMessageMarkdown: courseData.completion_message_markdown || null,
7677
completionPercentage: courseData.completion_percentage,
7778
descriptionMarkdown: courseData.description_md,
7879
difficulty: courseData.marketing.difficulty,

tests/acceptance/course-page/complete-challenge-test.js

+28-5
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@ module('Acceptance | course-page | complete-challenge-test', function (hooks) {
1818

1919
const currentUser = this.server.schema.users.first();
2020
const python = this.server.schema.languages.findBy({ name: 'Python' });
21-
const docker = this.server.schema.courses.findBy({ slug: 'docker' });
21+
const grep = this.server.schema.courses.findBy({ slug: 'grep' });
2222

2323
this.server.create('repository', 'withAllStagesCompleted', {
24-
course: docker,
24+
course: grep,
2525
language: python,
2626
user: currentUser,
2727
});
2828

2929
await catalogPage.visit();
30-
await catalogPage.clickOnCourse('Build your own Docker');
31-
assert.strictEqual(currentURL(), '/courses/docker/completed', 'URL is /courses/docker/completed');
32-
30+
await catalogPage.clickOnCourse('Build your own grep');
31+
assert.strictEqual(currentURL(), '/courses/grep/completed', 'URL is /courses/redis/completed');
3332
assert.contains(coursePage.courseCompletedCard.instructionsText, 'Congratulations are in order. Only ~30% of users');
3433

3534
await percySnapshot('Course Completed Page');
@@ -38,6 +37,30 @@ module('Acceptance | course-page | complete-challenge-test', function (hooks) {
3837
assert.ok(coursePage.configureGithubIntegrationModal.isOpen, 'configure github integration modal is open');
3938
});
4039

40+
test('custom course completion message is displayed', async function (assert) {
41+
testScenario(this.server, ['dummy']);
42+
signIn(this.owner, this.server);
43+
44+
const currentUser = this.server.schema.users.first();
45+
const python = this.server.schema.languages.findBy({ name: 'Python' });
46+
const dummy = this.server.schema.courses.findBy({ slug: 'dummy' });
47+
dummy.update('releaseStatus', 'live');
48+
49+
this.server.create('repository', 'withAllStagesCompleted', {
50+
course: dummy,
51+
language: python,
52+
user: currentUser,
53+
});
54+
55+
await catalogPage.visit();
56+
57+
// This opens in the extension completed page
58+
await catalogPage.clickOnCourse('Build your own Dummy');
59+
await visit('/courses/dummy/completed');
60+
61+
assert.contains(coursePage.courseCompletedCard.instructionsText, 'Congratulations!');
62+
});
63+
4164
test('visiting /completed route without completing course redirects to correct stage', async function (assert) {
4265
testScenario(this.server);
4366
signIn(this.owner, this.server);

0 commit comments

Comments
 (0)