Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions angular-ngrx-scss/src/app/home/home.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
<header class="nav">
<app-nav-bar></app-nav-bar>
</header>
<main class="main">
<div>Main content!</div>
</main>
<ng-container *ngIf="user$ | async as user">
<main class="main">
<aside>
<app-user-gists></app-user-gists>
</aside>
<app-top-repositories></app-top-repositories>
</main>
</ng-container>
</div>
9 changes: 8 additions & 1 deletion angular-ngrx-scss/src/app/home/home.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
grid-template-rows: auto 1fr;
grid-template-columns: 30% 1fr;
width: 100%;
height: 100%;
height: 100vh;
}

.nav {
Expand All @@ -14,7 +14,14 @@
}

.main {
display: grid;
grid-template-columns: 24rem 1fr;
grid-row: 2 / 3;
grid-column: 1 / 3;
background: variables.$gray100;
}

aside {
background: variables.$white;
padding: 2rem;
}
3 changes: 3 additions & 0 deletions angular-ngrx-scss/src/app/home/home.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectUserLoginName } from '../state/user';
import { fetchUserData } from '../state/user/user.actions';

@Component({
Expand All @@ -8,6 +9,8 @@ import { fetchUserData } from '../state/user/user.actions';
styleUrls: ['./home.component.scss'],
})
export class HomeComponent implements OnInit {
user$ = this.store.select(selectUserLoginName);

constructor(private store: Store) {}

ngOnInit() {
Expand Down
4 changes: 4 additions & 0 deletions angular-ngrx-scss/src/app/home/home.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ProfileAboutComponent } from './profile/profile-about/profile-about.com
import { ProfileReposComponent } from './profile/profile-repos/profile-repos.component';
import { RelativeTimePipe } from '../shared/pipes/relative-time.pipe';
import { RepoCardComponent } from '../shared/components/repo-card/repo-card.component';
import { UserGistsComponent } from './user-gists/user-gists.component';
import { TopRepositoriesComponent } from './top-repositories/top-repositories.component';

@NgModule({
declarations: [
Expand All @@ -22,6 +24,8 @@ import { RepoCardComponent } from '../shared/components/repo-card/repo-card.comp
ProfileReposComponent,
RepoCardComponent,
RelativeTimePipe,
UserGistsComponent,
TopRepositoriesComponent,
],
imports: [CommonModule, HomeRoutingModule],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div class="top-repositories-container">
<h2>Top Repositories</h2>
<div class="repo-container">
<ng-container *ngIf="repos$ | async as repos">
<ng-container *ngFor="let repo of repos; last as isLast">
<div class="repo-section">
<app-repo-card [repo]="repo"></app-repo-card>
</div>
<div class="view-all-link" *ngIf="isLast">
<ng-container *ngIf="username$ | async as username">
<a routerLink="/{{ username }}" [class.disabled]="!username"
>View all repositories</a
>
</ng-container>
</div>
</ng-container>
</ng-container>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@use '../../shared/styles/variables';

.top-repositories-container {
padding: 3rem;
h2 {
font-size: 18px;
line-height: 28px;
font-weight: 500;
margin-bottom: 16px;
}
.repo-container {
background-color: variables.$white;
border-radius: 0.5rem;
border-width: 1px;
}
.repo-section {
padding: 2rem 0;
padding-left: 2rem;
border-bottom: 1px solid variables.$gray300;
}
.view-all-link {
padding: 1.25rem;
background-color: variables.$gray200;
display: grid;
place-items: center;
a {
color: inherit;
font-weight: 600;
&.disabled {
color: variables.$gray300;
cursor: not-allowed;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectTopRepos, selectUserLoginName } from 'src/app/state/user';

@Component({
selector: 'app-top-repositories',
templateUrl: './top-repositories.component.html',
styleUrls: ['./top-repositories.component.scss'],
})
export class TopRepositoriesComponent {
username$ = this.store.select(selectUserLoginName);
repos$ = this.store.select(selectTopRepos);

constructor(private store: Store) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="container">
<h3>Gists</h3>
<ng-container *ngIf="userGists$ | async as gists">
<ng-container *ngIf="gists.length > 0">
<div class="list-container">
<ng-container *ngFor="let gist of gists">
<a class="link" [href]="gist.url" target="_blank">{{
gist.fileName
}}</a>
</ng-container>
</div>
</ng-container>
</ng-container>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@use '../../shared/styles/variables';

.container {
padding: 2rem 0;
border-top: 1px solid variables.$gray300;
border-bottom: 1px solid variables.$gray300;
h3 {
font-weight: 600;
}
a.link {
color: variables.$black;
&:hover {
color: variables.$blue300;
}
}
}
14 changes: 14 additions & 0 deletions angular-ngrx-scss/src/app/home/user-gists/user-gists.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectGists } from 'src/app/state/user';

@Component({
selector: 'app-user-gists',
templateUrl: './user-gists.component.html',
styleUrls: ['./user-gists.component.scss'],
})
export class UserGistsComponent {
userGists$ = this.store.select(selectGists);

constructor(private store: Store) {}
}
4 changes: 4 additions & 0 deletions angular-ngrx-scss/src/app/shared/styles/variables.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// colors
$white: rgb(255 255 255);
$gray100: rgb(243 244 246);
$gray200: rgb(249, 250, 251);
$gray300: rgb(229, 231, 235);
$gray500: rgb(107 114 128);
$gray700: rgb(75 85 99);
$gray900: rgb(17, 24, 39);
$black: rgb(0 0 0);
$blue300: rgb(59, 130, 246);
$blue600: rgb(37 99 235);

// breakpoints
Expand Down
28 changes: 28 additions & 0 deletions angular-ngrx-scss/src/app/state/profile/profile.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,36 @@ export interface ProfileState {
user?: UserState;
orgs?: UserOrgsState[];
repos?: UserReposState[];
gists?: UserGistsState[];
}

export interface UserGistsState {
url: string;
fileName: string;
}

interface Files {
[key: string]: { filename: string };
}
export interface UserGist {
comments: number;
comments_url: string;
commits_url: string;
created_at: string;
forks_url: string;
git_pull_url: string;
git_push_url: string;
html_url: string;
id: string;
node_id: string;
public: boolean;
truncated: false;
updated_at: string;
url: string;
files: Files;
}

export type UserGistsApiResponse = UserGist[];
export interface UserOrgsState {
id: number;
login: string;
Expand Down
21 changes: 21 additions & 0 deletions angular-ngrx-scss/src/app/state/user/user.actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createAction, props } from '@ngrx/store';
import { UserGistsState, UserReposState } from '../profile/profile.state';
import { UserState } from './user.state';

export const fetchUserData = createAction('[User API] User data requested');
Expand All @@ -12,3 +13,23 @@ export const fetchUserDataError = createAction(
'[User API] User Data fetch unsuccessful',
props<{ error: object }>(),
);

export const fetchUserTopReposSuccess = createAction(
'[User API] User top repos successfully received',
props<{ topRepos: UserReposState[] }>(),
);

export const fetchUserTopReposError = createAction(
'[User API] User top repos fetch unsuccessful',
props<{ error: object }>(),
);

export const fetchUserGistsSuccess = createAction(
'[User API] User gists successfully received',
props<{ gists: UserGistsState[] }>(),
);

export const fetchUserGistsError = createAction(
'[User API] User gists fetch unsuccessful',
props<{ error: object }>(),
);
101 changes: 100 additions & 1 deletion angular-ngrx-scss/src/app/state/user/user.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,30 @@ import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Action } from '@ngrx/store';
import { Observable, of } from 'rxjs';
import { fetchUserData, fetchUserDataSuccess } from './user.actions';
import {
fetchUserData,
fetchUserDataSuccess,
fetchUserGistsSuccess,
fetchUserTopReposSuccess,
} from './user.actions';
import { UserService } from '../../user/services/user.service';
import { UserEffects } from './user.effects';
import { UserState } from './user.state';
import { UserGistsState, UserReposState } from '../profile/profile.state';

const USER_STATE_MOCK: UserState = {
username: 'thisdot',
avatar: '',
bio: '',
blog: '',
company: '',
email: '',
followers: 0,
following: 0,
location: '',
name: '',
twitter_username: '',
};

describe('UserEffects', () => {
let actions$: Observable<Action>;
Expand All @@ -17,6 +37,12 @@ describe('UserEffects', () => {
getAuthenticatedUserInfo: () => {
return of();
},
getUserGists: () => {
return of();
},
getUserTopRepos: () => {
return of();
},
});
TestBed.configureTestingModule({
imports: [],
Expand Down Expand Up @@ -64,4 +90,77 @@ describe('UserEffects', () => {
done();
});
});

it('should get the user gists from the Github API', (done) => {
actions$ = of(fetchUserDataSuccess({ userData: USER_STATE_MOCK }));
const expectedUserData: UserGistsState[] = [
{
url: 'github.com/gists',
fileName: 'textfile1.txt',
},
];

userServiceMock.getUserGists.and.returnValue(of(expectedUserData));

effects.loadUserGists$.subscribe((action) => {
expect(action).toEqual(
fetchUserGistsSuccess({ gists: expectedUserData }),
);
done();
});
});

it('should get the top repositories from the Github API', (done) => {
actions$ = of(fetchUserDataSuccess({ userData: USER_STATE_MOCK }));
const expectedUserData: UserReposState[] = [
{
name: 'Repo-test',
description: 'This is a repo test',
language: 'TypeScript',
stargazers_count: 0,
forks_count: 0,
private: false,
updated_at: '2022-06-17T09:54:38Z',
license: null,
owner: {
login: 'thisdot',
},
},
{
name: 'Repo-test-2',
description: 'This is a repo test 2',
language: 'Javascript',
stargazers_count: 0,
forks_count: 0,
private: false,
updated_at: '2022-06-17T09:54:38Z',
license: null,
owner: {
login: 'thisdot',
},
},
{
name: 'Repo-test-3',
description: 'This is a repo test 2',
language: 'Javascript',
stargazers_count: 0,
forks_count: 0,
private: false,
updated_at: '2022-06-17T09:54:38Z',
license: null,
owner: {
login: 'thisdot',
},
},
];

userServiceMock.getUserTopRepos.and.returnValue(of(expectedUserData));

effects.loadUserTopRepos$.subscribe((action) => {
expect(action).toEqual(
fetchUserTopReposSuccess({ topRepos: expectedUserData }),
);
done();
});
});
});
Loading