diff --git a/src/app/app-router.module.ts b/src/app/app-router.module.ts
index 43d8625..c05a30d 100644
--- a/src/app/app-router.module.ts
+++ b/src/app/app-router.module.ts
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { CallbackComponent } from './header/header-popup/callback/callback.component';
+import {ProjectsComponent} from "./projects/projects.component";
const routes: Routes = [
{
@@ -14,6 +15,10 @@ const routes: Routes = [
path: 'home',
component: HomeComponent,
},
+ {
+ path: 'projects',
+ component: ProjectsComponent,
+ },
{
path: 'callback',
component: CallbackComponent,
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 9d85a39..98c88c1 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -11,6 +11,7 @@ import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import { FooterComponent } from './footer/footer.component';
import {HomeModule} from "./home/home.module";
+import {ProjectsModule} from "./projects/projects.module";
@NgModule({
declarations: [AppComponent, FooterComponent],
@@ -18,6 +19,7 @@ import {HomeModule} from "./home/home.module";
BrowserModule,
HeaderModule,
HomeModule,
+ ProjectsModule,
AppRouterModule,
AppServiceWorkerModule,
SharedModule,
diff --git a/src/app/header/header.component.ts b/src/app/header/header.component.ts
index 4f75f3f..c13684f 100644
--- a/src/app/header/header.component.ts
+++ b/src/app/header/header.component.ts
@@ -25,7 +25,7 @@ import { MyProfileComponent } from './header-popup/my-profile/my-profile.compone
export class HeaderComponent implements OnInit, OnDestroy {
pages: { name: string; route: string }[] = [
{ name: 'Home', route: '/home' },
- { name: 'Projects', route: '/home' },
+ { name: 'Projects', route: '/projects' },
{ name: 'Contact', route: '/home' },
];
@@ -38,13 +38,13 @@ export class HeaderComponent implements OnInit, OnDestroy {
userSliderStatus: boolean = false;
@ViewChild('profileBtn')
- profileBtnElementRef!: ElementRef;
+ profileBtnElementRef!: ElementRef;
@ViewChild('profileDropdown')
- profileDropdownElementRef!: ElementRef;
+ profileDropdownElementRef!: ElementRef;
@ViewChild('user')
- userElementRef!: ElementRef;
+ userElementRef!: ElementRef;
loggedUser!: User | null;
diff --git a/src/app/projects/project-card/project-card.component.css b/src/app/projects/project-card/project-card.component.css
new file mode 100644
index 0000000..12d44ca
--- /dev/null
+++ b/src/app/projects/project-card/project-card.component.css
@@ -0,0 +1,74 @@
+.card .inverse-card-image {
+ flex-direction: row-reverse;
+}
+
+.card .card-content {
+ height: 350px;
+ width: 100%;
+ padding: 40px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+.card .card-content > * {
+ margin: 10px;
+}
+
+.card .card-content .card-content-h {
+ width: 80%;
+}
+
+.card .card-content .card-title {
+ font-size: 2rem;
+ font-weight: 500;
+ justify-content: left;
+}
+.card .card-content .card-text {
+ font-size: 1.2rem;
+ font-weight: 300;
+}
+
+
+.card .card-content .card-content-f {
+ display: flex;
+ justify-content: center;
+ flex-direction: row;
+}
+.card .card-content .card-content-f .card-info {
+ display: flex;
+ flex-direction: column;
+}
+.card .card-content .card-content-f .card-stats {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+
+.stat-item {
+ margin: 5px 0px 5px 0px;
+ display: flex;
+ flex-direction: row;
+ align-items: start;
+ align-content: start;
+}
+.stat-item .stat-icon {
+ margin: 0px 15px 0px 0px;
+ width: 20px;
+ height: 20px;
+ font-size: 1rem;
+
+ display: flex;
+}
+.stat-item span {
+ font-size: 1rem;
+ font-weight: 300;
+ margin: 0;
+ justify-content: left;
+}
+
+.stats-inline {
+ margin: 0;
+ width: 100%;
+ flex-direction: row !important;
+}
diff --git a/src/app/projects/project-card/project-card.component.html b/src/app/projects/project-card/project-card.component.html
new file mode 100644
index 0000000..02af6f7
--- /dev/null
+++ b/src/app/projects/project-card/project-card.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
{{project.description}}
+
+
+
+
Language: {{project.languages}}
+
+
+
+
+
+
+
{{project.license}}
+
+
+
+
+
+
{{project.stars}}
+
+
+
+
+
+
{{project.forks}}
+
+
+
+
+
+
{{project.watchers}}
+
+
+
+
+
diff --git a/src/app/projects/project-card/project-card.component.spec.ts b/src/app/projects/project-card/project-card.component.spec.ts
new file mode 100644
index 0000000..74de6b1
--- /dev/null
+++ b/src/app/projects/project-card/project-card.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProjectCardComponent } from './project-card.component';
+
+describe('ProjectCardComponent', () => {
+ let component: ProjectCardComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [ProjectCardComponent]
+ });
+ fixture = TestBed.createComponent(ProjectCardComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/projects/project-card/project-card.component.ts b/src/app/projects/project-card/project-card.component.ts
new file mode 100644
index 0000000..eb7f79d
--- /dev/null
+++ b/src/app/projects/project-card/project-card.component.ts
@@ -0,0 +1,33 @@
+import {Component, Input} from '@angular/core';
+import { faCodeFork, faEye, faStar } from '@fortawesome/free-solid-svg-icons';
+import {Project} from "../../shared/model/project/project.model";
+import {faScaleBalanced} from "@fortawesome/free-solid-svg-icons/faScaleBalanced";
+
+@Component({
+ selector: 'app-project-card',
+ templateUrl: './project-card.component.html',
+ styleUrls: ['./project-card.component.css']
+})
+export class ProjectCardComponent {
+ @Input() inverted: boolean = false;
+
+ @Input() project!: Project;
+
+ // Stats Icons Definitions
+ faLicense = faScaleBalanced;
+
+ faStars = faStar;
+
+ faCodeFork = faCodeFork;
+
+ faEye = faEye;
+
+ get hasLicense(): boolean {
+ return this.project.license !== undefined;
+ }
+
+ get hasLanguage(): boolean {
+ return this.project.languages !== undefined &&
+ this.project.languages?.length > 0;
+ }
+}
diff --git a/src/app/projects/projects.component.css b/src/app/projects/projects.component.css
new file mode 100644
index 0000000..fc35c46
--- /dev/null
+++ b/src/app/projects/projects.component.css
@@ -0,0 +1,8 @@
+app-project-card {
+ display: flex;
+ flex-direction: column;
+ margin-top: 25px;
+}
+app-project-card:last-child {
+ margin-bottom: 25px;
+}
diff --git a/src/app/projects/projects.component.html b/src/app/projects/projects.component.html
new file mode 100644
index 0000000..6604b95
--- /dev/null
+++ b/src/app/projects/projects.component.html
@@ -0,0 +1,6 @@
+
diff --git a/src/app/projects/projects.component.spec.ts b/src/app/projects/projects.component.spec.ts
new file mode 100644
index 0000000..d396b2d
--- /dev/null
+++ b/src/app/projects/projects.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProjectsComponent } from './projects.component';
+
+describe('ProjectsComponent', () => {
+ let component: ProjectsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [ProjectsComponent]
+ });
+ fixture = TestBed.createComponent(ProjectsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts
new file mode 100644
index 0000000..23da837
--- /dev/null
+++ b/src/app/projects/projects.component.ts
@@ -0,0 +1,27 @@
+import {Component, OnInit} from '@angular/core';
+import {GithubService} from "../shared/github-service/github.service";
+import {Project} from "../shared/model/project/project.model";
+
+@Component({
+ selector: 'app-projects',
+ templateUrl: './projects.component.html',
+ styleUrls: ['./projects.component.css']
+})
+export class ProjectsComponent implements OnInit {
+ projects!: Project[];
+
+ constructor(private githubService: GithubService) {
+ }
+
+ ngOnInit(): void {
+ this.projects = [];
+ this.githubService.getProjects().subscribe((project: Project) => {
+ this.projects.push(project);
+ });
+ }
+
+ identifyProject(index: number, project: Project) {
+ return project.name;
+ }
+
+}
diff --git a/src/app/projects/projects.module.ts b/src/app/projects/projects.module.ts
new file mode 100644
index 0000000..665f97d
--- /dev/null
+++ b/src/app/projects/projects.module.ts
@@ -0,0 +1,21 @@
+import {NgModule} from "@angular/core";
+import {ProjectsComponent} from "./projects.component";
+import {CommonModule, NgOptimizedImage} from "@angular/common";
+import { ProjectCardComponent } from './project-card/project-card.component';
+import {MatIconModule} from "@angular/material/icon";
+import {FontAwesomeModule} from "@fortawesome/angular-fontawesome";
+
+@NgModule({
+ declarations: [
+ ProjectsComponent,
+ ProjectCardComponent
+ ],
+ imports: [
+ CommonModule,
+ NgOptimizedImage,
+ MatIconModule,
+ FontAwesomeModule
+ ],
+ exports: []
+})
+export class ProjectsModule { }
diff --git a/src/app/shared/auth/auth.service.ts b/src/app/shared/auth/auth.service.ts
index b3aedae..678376b 100644
--- a/src/app/shared/auth/auth.service.ts
+++ b/src/app/shared/auth/auth.service.ts
@@ -2,19 +2,15 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
first,
- firstValueFrom,
map,
Observable,
of,
Subject,
- take,
- tap,
} from 'rxjs';
import { catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { HttpError } from '../model/httpError/httpError.model';
import { User } from '../model/user/user.model';
-import * as http from 'http';
@Injectable({
providedIn: 'root',
diff --git a/src/app/shared/github-service/github.service.spec.ts b/src/app/shared/github-service/github.service.spec.ts
new file mode 100644
index 0000000..bcef5d4
--- /dev/null
+++ b/src/app/shared/github-service/github.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { GithubServiceService } from './github.service';
+
+describe('GithubServiceService', () => {
+ let service: GithubServiceService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(GithubServiceService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/github-service/github.service.ts b/src/app/shared/github-service/github.service.ts
new file mode 100644
index 0000000..5905cea
--- /dev/null
+++ b/src/app/shared/github-service/github.service.ts
@@ -0,0 +1,78 @@
+import { Injectable } from '@angular/core';
+import {Language, Project} from "../model/project/project.model";
+import {HttpClient} from "@angular/common/http";
+import {
+ map, mergeMap,
+ Observable,
+ pipe, switchMap, take
+} from 'rxjs';
+import { environment } from 'src/environments/environment';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class GithubService {
+
+ GITHUB_API_URL = 'https://api.github.com';
+
+ GITHUB_USER = environment.githubUser;
+
+ constructor(private http: HttpClient) { }
+
+ getProjects(): Observable {
+ return this.http.get(this.apiReposString()).pipe(
+ map((projects: any) => {
+ return projects.map((project: any) => {
+ return {
+ name: project.name,
+ description: project.description,
+ license: project.license?.key,
+ link: project.html_url,
+
+ stars: project.stargazers_count,
+ forks: project.forks_count,
+ watchers: project.watchers_count
+ } as Project;
+ }).filter((project: Project) => {
+ return project.name !== this.GITHUB_USER;
+ });
+ }),
+ switchMap((projects: Project[]) => {
+ return new Observable((observer) => {
+ projects.forEach((project: Project, index: number) => {
+ this.getProjectLanguage(project).subscribe((languages: Language[]) => {
+ project.languages = languages;
+ observer.next(project);
+ });
+ });
+ });
+ })
+ )
+ }
+
+ private getProjectLanguage(project: Project): Observable {
+ return this.http.get(this.apiRepoLanguagesString(project.name)).pipe(
+ map((languages: any) => {
+ let totalBytes = 0;
+ Object.keys(languages).forEach((language: string) => {
+ totalBytes += languages[language];
+ });
+
+ return Object.keys(languages).map((language: string) => {
+ return {
+ name: language,
+ percentage: (languages[language]/totalBytes)*100
+ } as Language;
+ });
+ })
+ );
+ }
+
+ private apiReposString() {
+ return `${this.GITHUB_API_URL}/users/${this.GITHUB_USER}/repos`;
+ }
+
+ private apiRepoLanguagesString(repoName: string) {
+ return `${this.GITHUB_API_URL}/repos/${this.GITHUB_USER}/${repoName}/languages`;
+ }
+}
diff --git a/src/app/shared/model/project/project.model.ts b/src/app/shared/model/project/project.model.ts
new file mode 100644
index 0000000..8fd9c7b
--- /dev/null
+++ b/src/app/shared/model/project/project.model.ts
@@ -0,0 +1,17 @@
+export type Language = {
+ name: string;
+ percentage: number;
+}
+
+export type Project = {
+ name: string;
+ description: string;
+ link: string;
+
+ license?: string;
+ languages?: Language[];
+
+ stars: number;
+ forks: number;
+ watchers: number;
+}
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index d1643c6..adff1c3 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -2,4 +2,5 @@ export const environment = {
production: true,
backendPath: (window)['env']['BACKEND_URL'],
backendOAuthPath: (window)['env']['BACKEND_OAUTH_URL'],
+ githubUser: (window)['env']['GITHUB_USER'],
};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index 6e0d313..bdf5548 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -6,6 +6,7 @@ export const environment = {
production: false,
backendPath: 'http://localhost:8070',
backendOAuthPath: 'http://localhost:8070',
+ githubUser: 'HideyoshiNakazone',
};
/*