Skip to content

Commit 80f2808

Browse files
Adds functionality to enable student self-enrollment in courses
Necessary UI elements are added to the interface to allow students to enroll in new courses from the sharing code provided by teachers. In addition, the home page of the app is modified to show a customization in case students access a URL provided by a teacher to invite new people to a course.
1 parent 2157925 commit 80f2808

File tree

13 files changed

+166
-74
lines changed

13 files changed

+166
-74
lines changed

vscode4teaching-webapp/src/app/app-routing.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { isLoggedIn, isTeacher } from "./services/auth/guards/guards.service";
1010

1111
const routes: Routes = [
1212
// private/common
13-
{ path: "dashboard", component: DashboardComponent, data: {}, canActivate: [isLoggedIn] },
13+
{ path: "dashboard", component: DashboardComponent, data: { showAside: false }, canActivate: [isLoggedIn] },
1414

1515
// private/student
1616
{

vscode4teaching-webapp/src/app/components/private/common/dashboard/dashboard.component.html

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="dashboard">
1+
<div class="dashboard px-5">
22
<h3>My Courses</h3>
33

44
<ng-container *ngIf="!this.coursesLoaded">
@@ -8,13 +8,13 @@ <h3>My Courses</h3>
88
</ng-container>
99

1010
<ng-container *ngIf="this.coursesLoaded && this.userCourses.length === 0">
11-
<div class="alert alert-v4t">
11+
<div class="alert alert-v4t my-2">
1212
<i class="fas fa-exclamation-triangle"></i> No courses for this user.
1313
</div>
1414
</ng-container>
1515

16-
<ng-container *ngIf="this.coursesLoaded && this.userCourses.length > 0">
17-
<div class="courseGrid">
16+
<div class="courseGrid">
17+
<ng-container *ngIf="this.coursesLoaded && this.userCourses.length > 0">
1818
<div class="course" *ngFor="let course of this.userCourses">
1919
<div class="icon">
2020
<i class="fas fa-book"></i>
@@ -33,6 +33,23 @@ <h3>My Courses</h3>
3333
</ng-container>
3434
</div>
3535
</div>
36+
</ng-container>
37+
38+
<div class="course" style="justify-content: center">
39+
<div class="name">
40+
Join a new course with a sharing code:
41+
</div>
42+
<div class="input-group">
43+
<input type="text" class="form-control form-control-v4t" [(ngModel)]="this.joinSharingCode" placeholder="Sharing code provided by teacher…">
44+
<button class="btn btn-v4t" (click)="this.joinCourse()" [disabled]="this.joinStatus === 'IN_PROGRESS'">
45+
<div *ngIf="this.joinStatus === 'NOT_STARTED' || this.joinStatus === 'ERROR'"><i class="fas fa-right-to-bracket"></i> Join course</div>
46+
<div *ngIf="this.joinStatus === 'IN_PROGRESS'"><i class="fas fa-circle-notch fa-spin"></i> Joining…</div>
47+
<div *ngIf="this.joinStatus === 'FINISHED'"><i class="fas fa-check"></i> Joined!</div>
48+
</button>
49+
</div>
50+
<div class="alert alert-v4t mt-2 text-center" *ngIf="this.joinStatus === 'ERROR'">
51+
<i class="fa fa-info-circle"></i> There was an error joining the course: sharing code is not valid or you are already in the course.
52+
</div>
3653
</div>
37-
</ng-container>
54+
</div>
3855
</div>
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Component, OnInit } from '@angular/core';
2+
import { ExerciseUserInfoStatus } from "../../../../model/exercise-user-info.model";
23
import { CourseService } from "../../../../services/rest-api/model-entities/course/course.service";
34
import { CurrentUserService } from "../../../../services/auth/current-user/current-user.service";
45
import { Course } from "../../../../model/course.model";
56
import { User } from "../../../../model/user.model";
67
import { supported as fileSystemAccessApiSupported } from "browser-fs-access";
7-
import { AsideService } from "../../../../services/aside/aside.service";
88

99
@Component({
1010
selector: 'app-dashboard',
@@ -19,21 +19,39 @@ export class DashboardComponent implements OnInit {
1919

2020
fsaApiSupported: boolean;
2121

22-
constructor(private asideService: AsideService,
23-
private courseService: CourseService,
22+
public joinSharingCode!: string;
23+
public joinStatus: ExerciseUserInfoStatus | "ERROR";
24+
25+
constructor(private courseService: CourseService,
2426
private curUserService: CurrentUserService) {
2527
this.coursesLoaded = false;
2628
this.fsaApiSupported = fileSystemAccessApiSupported;
29+
this.joinStatus = "NOT_STARTED";
2730
}
2831

29-
async ngOnInit(): Promise<void> {
32+
public async ngOnInit(): Promise<void> {
3033
const currentUser = await this.curUserService.currentUser;
3134
if (currentUser !== undefined) this.curUser = currentUser;
3235

33-
// TODO PENDIENTE REFACTORIZAR
34-
// this.asideService.lanzarBusquedaInfoAside();
35-
3636
this.userCourses = await this.courseService.getCoursesByUser(this.curUser);
3737
this.coursesLoaded = true;
3838
}
39+
40+
public async joinCourse(): Promise<void> {
41+
if (this.joinSharingCode !== undefined) {
42+
this.joinStatus = "IN_PROGRESS";
43+
try {
44+
await this.courseService.joinCourseBySharingCode(this.joinSharingCode);
45+
46+
this.joinStatus = "FINISHED";
47+
this.joinSharingCode = "";
48+
setTimeout(() => this.joinStatus = "NOT_STARTED", 3000);
49+
50+
await this.ngOnInit();
51+
} catch (e) {
52+
this.joinStatus = "ERROR";
53+
this.joinSharingCode = "";
54+
}
55+
}
56+
}
3957
}

vscode4teaching-webapp/src/app/components/private/student/course/exercise-status/in-progress-exercise/in-progress-exercise.component.scss

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,6 @@
44
}
55

66

7-
.progressBadges {
8-
display: flex;
9-
flex-direction: row;
10-
justify-content: space-evenly;
11-
align-items: center;
12-
gap: 1rem;
13-
14-
margin-bottom: .5rem;
15-
16-
width: 100%;
17-
18-
> div {
19-
flex: 1 1 0;
20-
}
21-
}
22-
23-
247
.syncWorkProcess {
258
display: flex;
269
align-items: center;

vscode4teaching-webapp/src/app/components/public/index/index.component.html

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@
55
<h1>VSCode4Teaching</h1>
66
<h2>Extension for Visual Studio Code</h2>
77
<hr/>
8-
<!-- <ng-container>
9-
<div class="alert alert-secondary"><i class="fas fa-circle-notch fa-spin"></i> Loading...</div>
8+
<ng-container *ngIf="this.course === null">
109
<div class="alert alert-danger"><i class="fas fa-exclamation-triangle"></i> The code you entered could not be related to a course.</div>
11-
</ng-container> -->
10+
</ng-container>
11+
<ng-container *ngIf="this.course === undefined && this.sharingCode !== undefined">
12+
<div class="alert alert-secondary"><i class="fas fa-circle-notch fa-spin"></i> Loading...</div>
13+
</ng-container>
1214
<ng-container>
13-
<!-- <ng-container>
14-
<div class="text-big">
15-
<kbd>NAME</kbd> <kbd>SURNAME</kbd> invited you to join <kbd>COURSENAME</kbd> at VSCode4Teaching!
15+
<ng-container *ngIf="this.course !== undefined && this.course !== null">
16+
<div class="text-big" style="font-size: 1.5rem">
17+
<strong>{{ this.course.creator?.name }} {{ this.course.creator?.lastName }} invited you to join {{ this.course.name }} at VSCode4Teaching!</strong>
1618
</div>
1719
<div class="text-big">
1820
If you want to join this course, please follow this steps:
1921
</div>
20-
</ng-container> -->
21-
<ng-container>
22+
</ng-container>
23+
<ng-container *ngIf="this.course === undefined || this.course === null">
2224
<div class="text-big">
2325
Bring the programming exercises of a course directly to the student's editor, so that the teacher of that course can check the progress of the students and help them.
2426
</div>
@@ -54,16 +56,19 @@ <h2>Extension for Visual Studio Code</h2>
5456
<img class="stepImage" src="/assets/img/gif2_login.gif" alt="Animated image of demo student logging in">
5557
</div>
5658
<div class="step" id="step5">
57-
<ng-container>
59+
<ng-container *ngIf="!this.course">
5860
<div class="stepDescription">Join a course (the teacher will give you the code!):</div>
5961
</ng-container>
60-
<!-- <ng-container>
62+
<ng-container *ngIf="!!this.course">
6163
<div class="stepDescription">Join the course using this code:</div>
6264
<div class="codeBlock">
63-
<input id="givenCodeInput" [value]="this.code" readonly #codeCopyInput />
64-
<button type="button" id="codeCopyBtn" #codeCopyBtn>Copy</button>
65+
<input id="givenCodeInput" [value]="this.sharingCode" readonly />
66+
<button type="button" id="codeCopyBtn" (click)="this.copySharingCode()">
67+
<div *ngIf="this.sharingCodeCopied"><i class="fa fa-check"></i> Copied!</div>
68+
<div *ngIf="!this.sharingCodeCopied">Copy</div>
69+
</button>
6570
</div>
66-
</ng-container> -->
71+
</ng-container>
6772
<img class="stepImage" src="/assets/img/gif3_joincourse.gif" alt="Animated image of demo student enrolling in a course using an enrollment code" />
6873
</div>
6974
<div class="step" id="step6">

vscode4teaching-webapp/src/app/components/public/index/index.component.scss

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,28 +105,30 @@
105105

106106
.codeBlock {
107107
display: flex;
108-
width: 40%;
108+
width: 50%;
109109
margin: 0 auto 0.5rem auto;
110-
}
111110

112-
#givenCodeInput {
113-
border: 2px solid #f44a3e;
114-
padding: 12px 40px;
115-
flex-grow: 9;
116-
transition: all 0.25s;
117111

118-
&:focus {
119-
outline: none;
120-
box-shadow: 0 0 20px #f38078;
112+
> #givenCodeInput {
113+
border: 2px solid #f44a3e;
114+
padding: 12px 40px;
115+
flex-grow: 9;
121116
transition: all 0.25s;
117+
text-align: center;
118+
119+
&:focus {
120+
outline: none;
121+
box-shadow: 0 0 20px #f38078;
122+
transition: all 0.25s;
123+
}
122124
}
123-
}
124125

125-
#codeCopyBtn {
126-
background-color: #f44a3e;
127-
padding: 12px 40px;
128-
color: white;
129-
border: 0;
130-
flex-grow: 1;
131-
cursor: pointer;
126+
#codeCopyBtn {
127+
background-color: #f44a3e;
128+
padding: 12px 40px;
129+
color: white;
130+
border: 0;
131+
flex-grow: 1;
132+
cursor: pointer;
133+
}
132134
}
Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,53 @@
1-
import {Component} from '@angular/core';
1+
import { Component, OnDestroy, OnInit } from '@angular/core';
2+
import { ActivatedRoute, Params } from "@angular/router";
3+
import { Subscription } from "rxjs";
4+
import { Course } from "../../../model/course.model";
5+
import { CourseService } from "../../../services/rest-api/model-entities/course/course.service";
26

37
@Component({
48
selector: 'app-index',
59
templateUrl: './index.component.html',
610
styleUrls: ['./index.component.scss']
711
})
8-
export class IndexComponent {
12+
export class IndexComponent implements OnInit, OnDestroy {
913

10-
constructor() {
14+
public course?: Course | null;
15+
public sharingCode?: string;
16+
public sharingCodeCopied: boolean;
17+
18+
public paramsSubscription!: Subscription;
19+
20+
constructor(private courseService: CourseService,
21+
private activatedRoute: ActivatedRoute,
22+
) {
23+
this.sharingCodeCopied = false;
24+
}
25+
26+
27+
public ngOnInit() {
28+
this.paramsSubscription = this.activatedRoute.queryParams.subscribe(async (params: Params) => {
29+
this.sharingCode = params['code'];
30+
if (this.sharingCode) {
31+
try {
32+
this.course = await this.courseService.getCourseBySharingCode(this.sharingCode);
33+
} catch (e) {
34+
this.course = null;
35+
}
36+
} else {
37+
this.course = undefined;
38+
}
39+
});
40+
}
41+
42+
public async copySharingCode(): Promise<void> {
43+
if (this.sharingCode) {
44+
await navigator.clipboard.writeText(this.sharingCode);
45+
this.sharingCodeCopied = true;
46+
setTimeout(() => this.sharingCodeCopied = false, 1500);
47+
}
1148
}
1249

50+
public ngOnDestroy(): void {
51+
this.paramsSubscription.unsubscribe();
52+
}
1353
}

vscode4teaching-webapp/src/app/model/course.model.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class Course {
1010
#exercises: Exercise[] | undefined;
1111

1212
constructor(dto: CourseDTO) {
13-
this.#id = parseInt(dto.id);
13+
this.#id = dto.id as number;
1414
this.#name = dto.name;
1515
this.#creator = dto.creator ? new User(dto.creator) : undefined;
1616
this.#exercises = dto.exercises?.map((dto: ExerciseDTO) => new Exercise(dto)) ?? undefined;
@@ -40,7 +40,7 @@ export class Course {
4040

4141
public toDTO = (): CourseDTO => {
4242
return {
43-
id: this.id.toString(),
43+
id: this.id,
4444
name: this.name,
4545
creator: this.creator?.toDTO(),
4646
exercises: this.exercises?.map((exercise: Exercise) => exercise.toDTO())

vscode4teaching-webapp/src/app/model/exercise.model.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import { ExerciseDTO } from "./rest-api/exercise.dto";
44
export class Exercise {
55
readonly #id: number;
66
readonly #name: string;
7-
readonly #course: Course;
7+
readonly #course?: Course;
88
readonly #includesTeacherSolution: boolean;
99
#solutionIsPublic: boolean;
1010
#allowEditionAfterSolutionDownloaded: boolean;
1111

1212

1313
constructor(dto: ExerciseDTO) {
14-
this.#id = dto.id;
14+
this.#id = dto.id as number;
1515
this.#name = dto.name;
16-
this.#course = new Course(dto.course);
16+
if (dto.course) this.#course = new Course(dto.course);
1717
this.#includesTeacherSolution = dto.includesTeacherSolution;
1818
this.#solutionIsPublic = dto.solutionIsPublic;
1919
this.#allowEditionAfterSolutionDownloaded = dto.allowEditionAfterSolutionDownloaded;
@@ -28,7 +28,7 @@ export class Exercise {
2828
return this.#name;
2929
}
3030

31-
get course(): Course {
31+
get course(): Course | undefined {
3232
return this.#course;
3333
}
3434

@@ -56,7 +56,7 @@ export class Exercise {
5656
return {
5757
id: this.id,
5858
name: this.name,
59-
course: this.course.toDTO(),
59+
...(this.course ? { course: this.course.toDTO() } : {}),
6060
includesTeacherSolution: this.includesTeacherSolution,
6161
solutionIsPublic: this.solutionIsPublic,
6262
allowEditionAfterSolutionDownloaded: this.allowEditionAfterSolutionDownloaded

vscode4teaching-webapp/src/app/model/rest-api/course.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ExerciseDTO } from "./exercise.dto";
22
import { UserDTO } from "./user.dto";
33

44
export interface CourseDTO {
5-
id: string;
5+
id: number;
66
name: string;
77
creator?: UserDTO;
88
exercises?: ExerciseDTO[];

0 commit comments

Comments
 (0)