diff options
| author | transtrike <transtrike@gmail.com> | 2021-01-16 23:26:28 +0200 |
|---|---|---|
| committer | transtrike <transtrike@gmail.com> | 2021-01-16 23:26:28 +0200 |
| commit | 1237e7c521a5500fa7631d604596b4e49d3a3cdc (patch) | |
| tree | 614718101ae614f0380cd67952b7b5fdf8d53c19 | |
| parent | 9e615d882aa1e29a1c21c0a5de6ae78eb6a27f25 (diff) | |
| parent | cd9dcf39967ce81113a9549d8789e20760be877c (diff) | |
| download | DevHive-1237e7c521a5500fa7631d604596b4e49d3a3cdc.tar DevHive-1237e7c521a5500fa7631d604596b4e49d3a3cdc.tar.gz DevHive-1237e7c521a5500fa7631d604596b4e49d3a3cdc.zip | |
Merge branch 'dev' of github.com:Team-Kaleidoscope/DevHive into dev
29 files changed, 650 insertions, 294 deletions
diff --git a/src/DevHive.Angular/src/app/app-constants.module.ts b/src/DevHive.Angular/src/app/app-constants.module.ts new file mode 100644 index 0000000..df676da --- /dev/null +++ b/src/DevHive.Angular/src/app/app-constants.module.ts @@ -0,0 +1,8 @@ +export class AppConstants { + public static BASE_API_URL = 'http://localhost:5000/api'; + public static API_USER_URL = AppConstants.BASE_API_URL + '/User'; + public static API_USER_LOGIN_URL = AppConstants.API_USER_URL + '/login'; + public static API_USER_REGISTER_URL = AppConstants.API_USER_URL + '/register'; + + public static FETCH_TIMEOUT = 500; +} diff --git a/src/DevHive.Angular/src/app/app-routing.module.ts b/src/DevHive.Angular/src/app/app-routing.module.ts index b733655..7817dcb 100644 --- a/src/DevHive.Angular/src/app/app-routing.module.ts +++ b/src/DevHive.Angular/src/app/app-routing.module.ts @@ -3,11 +3,15 @@ import { Routes, RouterModule } from '@angular/router'; import { FeedComponent } from './components/feed/feed.component'; import { LoginComponent } from './components/login/login.component'; import { RegisterComponent } from './components/register/register.component'; +import { ProfileComponent } from './components/profile/profile.component'; +import {ProfileSettingsComponent} from './components/profile-settings/profile-settings.component'; const routes: Routes = [ { path: '', component: FeedComponent }, { path: 'login', component: LoginComponent }, - { path: 'register', component: RegisterComponent } + { path: 'register', component: RegisterComponent }, + { path: 'profile/:username', component: ProfileComponent }, + { path: 'profile/:username/settings', component: ProfileSettingsComponent } ]; @NgModule({ diff --git a/src/DevHive.Angular/src/app/app.module.ts b/src/DevHive.Angular/src/app/app.module.ts index 6270561..7158941 100644 --- a/src/DevHive.Angular/src/app/app.module.ts +++ b/src/DevHive.Angular/src/app/app.module.ts @@ -12,6 +12,8 @@ import { LoginComponent } from './components/login/login.component'; import { RegisterComponent } from './components/register/register.component'; import { FeedComponent } from './components/feed/feed.component'; import { PostComponent } from './components/post/post.component'; +import { ProfileComponent } from './components/profile/profile.component'; +import { ProfileSettingsComponent } from './components/profile-settings/profile-settings.component'; @NgModule({ declarations: [ @@ -19,7 +21,9 @@ import { PostComponent } from './components/post/post.component'; LoginComponent, RegisterComponent, FeedComponent, - PostComponent + PostComponent, + ProfileComponent, + ProfileSettingsComponent ], imports: [ BrowserModule, diff --git a/src/DevHive.Angular/src/app/components/feed/feed.component.css b/src/DevHive.Angular/src/app/components/feed/feed.component.css index 07667c2..aa3c392 100644 --- a/src/DevHive.Angular/src/app/components/feed/feed.component.css +++ b/src/DevHive.Angular/src/app/components/feed/feed.component.css @@ -1,7 +1,7 @@ #feed-page { max-width: 40em; margin: 0 auto; - border: .5em solid white; + border: .5em solid var(--bg-color); border-bottom: 0; box-sizing: border-box; display: flex; @@ -31,10 +31,21 @@ } #posts { - max-height: calc(100vh - 3em); + max-height: calc(100vh - 3.5em); overflow-y: auto; } + /* Hide scrollbar for Chrome, Safari and Opera */ +#posts::-webkit-scrollbar { + display: none; +} + + /* Hide scrollbar for IE, Edge and Firefox */ +#posts { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + /* Profile bar */ #profile-bar { @@ -43,9 +54,14 @@ display: flex; flex-direction: column; align-items: center; + box-sizing: border-box; + background-color: var(--bg-color); + margin-right: .5em; padding-bottom: 1em; + padding: .5em; + border-radius: .6em; } #profile-bar-profile-pic { @@ -63,12 +79,11 @@ #top-bar { display: flex; + width: 95%; + margin: 0 auto; + margin-bottom: .5em; } -#top-bar img { - height: 1.6em; - width: 1.6em; -} #top-bar-profile-pic { height: 2.5em; @@ -84,9 +99,38 @@ #top-bar-create-post { flex: 1; font-size: inherit; + width: 100%; + margin: 0 auto; + box-sizing: border-box; + border: 2px solid var(--bg-color); + padding: .5em; + border-radius: .6em; + box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; } #top-bar-open-chat { /* Until implemented */ display: none; } + +#profile-bar, +#top-bar > * { + box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px; +} + +/* Elements, that act as buttons */ + +#profile-bar:hover, +#top-bar-profile-pic:hover { + cursor: pointer; +} + +/* Can't copy text from */ + +#profile-bar, +.vote { + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ +} diff --git a/src/DevHive.Angular/src/app/components/feed/feed.component.html b/src/DevHive.Angular/src/app/components/feed/feed.component.html index ffbad14..5d7c86a 100644 --- a/src/DevHive.Angular/src/app/components/feed/feed.component.html +++ b/src/DevHive.Angular/src/app/components/feed/feed.component.html @@ -1,5 +1,10 @@ -<div id="feed-page"> - <nav id="profile-bar" class="rounded-border"> +<!-- TODO: replace with loading component --> +<div id="content" *ngIf="!dataArrived"> + Loading... +</div> + +<div id="feed-page" *ngIf="dataArrived"> + <nav id="profile-bar" (click)="goToProfile()"> <img id="profile-bar-profile-pic" class="round-image" src="assets/images/feed/profile-pic.png" alt=""/> <div id="profile-bar-name"> {{ user.firstName }} {{ user.lastName }} @@ -10,10 +15,8 @@ </nav> <div id="feed-content"> <nav id="top-bar"> - <a id="top-bar-profile-pic" class="round-image" href=""> - <img class="round-image" src="assets/images/feed/profile-pic.png" alt=""/> - </a> - <input id="top-bar-create-post" class="rounded-border" type="text" placeholder="What's on your mind?"/> + <img id="top-bar-profile-pic" class="round-image" src="assets/images/feed/profile-pic.png" alt="" (click)="goToProfile()"> + <input id="top-bar-create-post" type="text" placeholder="What's on your mind?"/> <a id="top-bar-open-chat" href=""> <img src="assets/images/feed/chat-pic.png" alt=""/> </a> diff --git a/src/DevHive.Angular/src/app/components/feed/feed.component.ts b/src/DevHive.Angular/src/app/components/feed/feed.component.ts index 2889e7f..7d37c9a 100644 --- a/src/DevHive.Angular/src/app/components/feed/feed.component.ts +++ b/src/DevHive.Angular/src/app/components/feed/feed.component.ts @@ -1,13 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; -import { Guid } from 'guid-typescript'; -import jwt_decode from 'jwt-decode'; -import { FeedService } from 'src/app/services/feed.service'; import { User } from 'src/models/identity/user'; -import { IJWTPayload } from 'src/interfaces/jwt-payload'; -import { IUserCredentials } from 'src/interfaces/user-credentials'; import { PostComponent } from '../post/post.component'; +import { UserService } from '../../services/user.service'; +import { AppConstants } from 'src/app/app-constants.module'; @Component({ selector: 'app-feed', @@ -16,17 +13,15 @@ import { PostComponent } from '../post/post.component'; }) export class FeedComponent implements OnInit { private _title = 'Feed'; + public dataArrived = false; public user: User; public posts: PostComponent[]; - constructor(private titleService: Title, private service: FeedService, private router: Router) { - this.titleService.setTitle(this._title); + constructor(private _titleService: Title, private _router: Router, private _userService: UserService) { + this._titleService.setTitle(this._title); } ngOnInit(): void { - // Default values, so it doesn't give an error while initializing - this.user = new User(Guid.createEmpty(), '', '', '', ''); - this.posts = [ new PostComponent(), new PostComponent(), @@ -35,21 +30,16 @@ export class FeedComponent implements OnInit { ]; if (sessionStorage.getItem('UserCred')) { - const jwt: IJWTPayload = JSON.parse(sessionStorage.getItem('UserCred') ?? ''); - const userCred = jwt_decode<IUserCredentials>(jwt.token); - this.saveUserData(userCred.ID, jwt.token); + // Workaround for waiting the fetch response + // TODO: properly wait for it, before loading the page contents + setTimeout(() => { this.user = this._userService.fetchUserFromSessionStorage(); }, AppConstants.FETCH_TIMEOUT); + setTimeout(() => { this.dataArrived = true; }, AppConstants.FETCH_TIMEOUT + 100); } else { - this.router.navigate(['/login']); + this._router.navigate(['/login']); } } - saveUserData(userId: Guid, authToken: string): void { - fetch(`http://localhost:5000/api/User?Id=${userId}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + authToken - } - }).then(response => response.json()).then(data => Object.assign(this.user, data)); + goToProfile(): void { + this._router.navigate(['/profile/' + this.user.userName]); } } diff --git a/src/DevHive.Angular/src/app/components/login/login.component.ts b/src/DevHive.Angular/src/app/components/login/login.component.ts index b3698e2..a9206f2 100644 --- a/src/DevHive.Angular/src/app/components/login/login.component.ts +++ b/src/DevHive.Angular/src/app/components/login/login.component.ts @@ -1,7 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; +import { FormGroup, FormBuilder, Validators, FormControl, AbstractControl } from '@angular/forms'; import { Router } from '@angular/router'; import { Title } from '@angular/platform-browser'; +import { UserService } from 'src/app/services/user.service'; @Component({ selector: 'app-login', @@ -9,16 +10,15 @@ import { Title } from '@angular/platform-browser'; styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { - loginUserFormGroup: FormGroup; - private _title = 'Login'; + loginUserFormGroup: FormGroup; - constructor(private titleService: Title, private fb: FormBuilder, private router: Router) { - titleService.setTitle(this._title); + constructor(private _titleService: Title, private _fb: FormBuilder, private _router: Router, private _userService: UserService) { + this._titleService.setTitle(this._title); } ngOnInit(): void { - this.loginUserFormGroup = this.fb.group({ + this.loginUserFormGroup = this._fb.group({ username: new FormControl('', [ Validators.required ]), @@ -29,31 +29,19 @@ export class LoginComponent implements OnInit { } async onSubmit(): Promise<void> { - const response = await fetch('http://localhost:5000/api/User/login', { - method: 'POST', - body: JSON.stringify({ - UserName: this.loginUserFormGroup.get('username')?.value, - Password: this.loginUserFormGroup.get('password')?.value - }), - headers: { - 'Content-Type': 'application/json' - } - }); - const userCred: string = await response.json(); - - sessionStorage.setItem('UserCred', JSON.stringify(userCred)); - this.router.navigate(['/']); + this._userService.loginUser(this.loginUserFormGroup); + this._router.navigate(['/']); } onRedirectRegister(): void { - this.router.navigate(['/register']); + this._router.navigate(['/register']); } - get username() { + get username(): AbstractControl | null { return this.loginUserFormGroup.get('username'); } - get password() { + get password(): AbstractControl | null { return this.loginUserFormGroup.get('password'); } } diff --git a/src/DevHive.Angular/src/app/components/post/post.component.css b/src/DevHive.Angular/src/app/components/post/post.component.css index e53eb2c..4769191 100644 --- a/src/DevHive.Angular/src/app/components/post/post.component.css +++ b/src/DevHive.Angular/src/app/components/post/post.component.css @@ -1,7 +1,26 @@ .post { display: flex; - width: inherit; - margin: .5em 0; + width: 95%; + + margin: .5em auto; + box-sizing: border-box; + border: 2px solid var(--card-bg); + padding: .5em; + background-color: var(--card-bg); + border-radius: .6em; +box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; +} + + /* So the bottom shadow of the last element + * doesn't get cut off + */ +.post:nth-last-child(1) { + margin-bottom: 1.1em; +} + +hr { + border: 1px solid black; + width: 90%; } /* Author */ @@ -43,22 +62,32 @@ display: flex; flex-direction: column; align-items: center; - margin-right: -.2em; + margin-right: -.1em; +} + +.score { + flex: 1; + display: flex; + align-items: center; } + .vote { + display: flex; + align-items: center; flex: 1; - background: white; + + background: var(--card-bg); font-size: 1em; + + border: 1px solid var(--card-bg); box-sizing: border-box; - border: 2px solid white; - border-radius: .3em; -} + border-radius: .2em; -.vote:hover { - background: lightgray; -} + } -.vote:active { - border-color: black; +.vote:hover { + border: 1px solid var(--focus-color); + color: var(--focus-color); + cursor: pointer; } diff --git a/src/DevHive.Angular/src/app/components/post/post.component.html b/src/DevHive.Angular/src/app/components/post/post.component.html index b6fab55..041158f 100644 --- a/src/DevHive.Angular/src/app/components/post/post.component.html +++ b/src/DevHive.Angular/src/app/components/post/post.component.html @@ -1,4 +1,4 @@ -<div class="post rounded-border"> +<div class="post"> <div class="content"> <div class="author"> <img class="round-image" src="assets/images/feed/profile-pic.png"> diff --git a/src/DevHive.Angular/src/app/components/post/post.component.ts b/src/DevHive.Angular/src/app/components/post/post.component.ts index 72c7aec..b4bb2f0 100644 --- a/src/DevHive.Angular/src/app/components/post/post.component.ts +++ b/src/DevHive.Angular/src/app/components/post/post.component.ts @@ -3,26 +3,26 @@ import { Guid } from 'guid-typescript'; import { User } from 'src/models/identity/user'; @Component({ - selector: 'app-post', - templateUrl: './post.component.html', - styleUrls: ['./post.component.css'], + selector: 'app-post', + templateUrl: './post.component.html', + styleUrls: ['./post.component.css'], }) export class PostComponent implements OnInit { - public user: User; - public votesNumber: number; + public user: User; + public votesNumber: number; - constructor() {} + constructor() {} - ngOnInit(): void { - //Fetch data in post service - this.user = new User( - Guid.create(), - 'gosho_trapov', - 'Gosho', - 'Trapov', - 'assets/images/feed/profile-pic.png' - ); + ngOnInit(): void { + // Fetch data in post service + this.user = new User( + Guid.create(), + 'gosho_trapov', + 'Gosho', + 'Trapov', + 'assets/images/feed/profile-pic.png' + ); - this.votesNumber = 23; - } + this.votesNumber = 23; + } } diff --git a/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.css b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.css diff --git a/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.html b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.html new file mode 100644 index 0000000..0cc443e --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.html @@ -0,0 +1 @@ +<p>profile-settings works!</p> diff --git a/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.ts b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.ts new file mode 100644 index 0000000..9226aae --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile-settings/profile-settings.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-profile-settings', + templateUrl: './profile-settings.component.html', + styleUrls: ['./profile-settings.component.css'] +}) +export class ProfileSettingsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/DevHive.Angular/src/app/components/profile/profile.component.css b/src/DevHive.Angular/src/app/components/profile/profile.component.css new file mode 100644 index 0000000..6954b22 --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile/profile.component.css @@ -0,0 +1,88 @@ +* { + box-sizing: border-box; +} + +#content { + max-width: 22em; + justify-content: start; +} + +hr { + width: calc(100% - 1em); + color: black; + border: 1px solid black; +} + +/* Navigation bar (for loggedin user) */ + +#navigation { + display: flex; + width: 100%; +} + +.submit-btn { + flex: 1; + margin-top: 0; + margin-left: .5em; + font-size: inherit; +} + +#navigation > .submit-btn:nth-of-type(1) { + margin-left: 0; +} + +/* Top card */ + +#main-info { + display: flex; + width: 100%; + margin-bottom: .25em +} + +#main-info > img { + width: 5em; + height: 5em; +} + +#other-main-info { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; +} + +#other-main-info > * { + font-size: 1.4em; +} + +#other-main-info *:nth-child(1) { + margin-top: auto; +} + +#other-main-info *:nth-last-child(1) { + margin-bottom: auto; +} + +/* Languages and technologies */ + +.secondary-info { + margin-top: .25em; + margin-bottom: .25em; + width: 100%; + display: flex; + align-items: center; +} + +.user-language, .user-technology { + border-radius: .4em; + background-color: lightgrey; + padding: .2em; + margin: 0 .2em; +} + +/* Posts */ + +#posts { + width: 100%; +} + diff --git a/src/DevHive.Angular/src/app/components/profile/profile.component.html b/src/DevHive.Angular/src/app/components/profile/profile.component.html new file mode 100644 index 0000000..5303db3 --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile/profile.component.html @@ -0,0 +1,45 @@ +<!-- TODO: replace with loading component --> +<div id="content" style="justify-content: center;" *ngIf="!dataArrived"> + Loading... +</div> + +<div id="content" *ngIf="dataArrived"> + <nav id="navigation"> + <button class="submit-btn" type="submit" (click)="goBack()">ᐊ Back</button> + <button class="submit-btn" type="submit" (click)="navigateToSettings()" *ngIf="loggedInUser">Settings</button> + </nav> + <hr> + <div id="main-info" class="rounded-border"> + <img class="round-image" src="assets/images/feed/profile-pic.png" alt=""/> + <div id="other-main-info"> + <div id="name"> + {{ user.firstName }} {{ user.lastName }} + </div> + <div id="username"> + @{{ user.userName }} + </div> + </div> + </div> + <div class="secondary-info rounded-border"> + Languages: + <div class="user-language"> + C# + </div> + <div class="user-language"> + TypeScript + </div> + </div> + <div class="secondary-info rounded-border"> + Technologies: + <div class="user-technology"> + .NET 5 + </div> + <div class="user-technology"> + Angular + </div> + </div> + <div id="posts"> + <hr> + Posts go here + </div> +</div> diff --git a/src/DevHive.Angular/src/app/components/profile/profile.component.ts b/src/DevHive.Angular/src/app/components/profile/profile.component.ts new file mode 100644 index 0000000..16bb053 --- /dev/null +++ b/src/DevHive.Angular/src/app/components/profile/profile.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { UserService } from 'src/app/services/user.service'; +import { User } from 'src/models/identity/user'; +import { AppConstants } from 'src/app/app-constants.module'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.css'] +}) +export class ProfileComponent implements OnInit { + public loggedInUser = false; + public dataArrived = false; + public user: User; + + constructor(private _router: Router, private _userService: UserService) + { } + + private setDefaultUser(): void { + this.user = this._userService.getDefaultUser(); + } + + ngOnInit(): void { + const username = this._router.url.substring(9); + console.log(username); + + if (sessionStorage.getItem('UserCred')) { + // Workaround for waiting the fetch response + // TODO: properly wait for it, before loading the page contents + setTimeout(() => { + this.user = this._userService.fetchUserFromSessionStorage(); + }, AppConstants.FETCH_TIMEOUT); + + // After getting the user, check if we're on the profile page of the logged in user + setTimeout(() => { + if (this.user.userName !== username) { + this.setDefaultUser(); + } else { + this.loggedInUser = true; + } + }, AppConstants.FETCH_TIMEOUT + 50); + } + else { + this.setDefaultUser(); + } + + setTimeout(() => { + this.dataArrived = true; + }, AppConstants.FETCH_TIMEOUT + 100); + } + + goBack(): void { + this._router.navigate(['/']); + } + + navigateToSettings(): void { + this._router.navigate([this._router.url + '/settings']); + } +} diff --git a/src/DevHive.Angular/src/app/components/register/register.component.ts b/src/DevHive.Angular/src/app/components/register/register.component.ts index 7304589..3fdfbcd 100644 --- a/src/DevHive.Angular/src/app/components/register/register.component.ts +++ b/src/DevHive.Angular/src/app/components/register/register.component.ts @@ -1,7 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; +import { UserService } from 'src/app/services/user.service'; @Component({ selector: 'app-register', @@ -9,16 +10,15 @@ import { Router } from '@angular/router'; styleUrls: ['./register.component.css'] }) export class RegisterComponent implements OnInit { - public registerUserFormGroup: FormGroup; - private _title = 'Register'; + public registerUserFormGroup: FormGroup; - constructor(private titleService: Title, private fb: FormBuilder, private router: Router) { - titleService.setTitle(this._title); + constructor(private _titleService: Title, private _fb: FormBuilder, private _router: Router, private _userService: UserService) { + this._titleService.setTitle(this._title); } ngOnInit(): void { - this.registerUserFormGroup = this.fb.group({ + this.registerUserFormGroup = this._fb.group({ firstName: new FormControl('', [ Validators.required, Validators.minLength(3) @@ -45,50 +45,32 @@ export class RegisterComponent implements OnInit { this.registerUserFormGroup.valueChanges.subscribe(console.log); } - async onSubmit(): Promise<void> { - // TODO: add a check for form data validity - - const response = await fetch('http://localhost:5000/api/User/register', { - method: 'POST', - body: JSON.stringify({ - UserName: this.registerUserFormGroup.get('username')?.value, - Email: this.registerUserFormGroup.get('email')?.value, - FirstName: this.registerUserFormGroup.get('firstName')?.value, - LastName: this.registerUserFormGroup.get('lastName')?.value, - Password: this.registerUserFormGroup.get('password')?.value - }), - headers: { - 'Content-Type': 'application/json' - } - }); - const userCred: string = await response.json(); - - // TODO: don't redirect if there is an error response - sessionStorage.setItem('UserCred', JSON.stringify(userCred)); - this.router.navigate(['/']); + onSubmit(): void { + this._userService.registerUser(this.registerUserFormGroup); + this._router.navigate(['/']); } onRedirectRegister(): void { - this.router.navigate(['/login']); + this._router.navigate(['/login']); } - get firstName() { + get firstName(): AbstractControl | null { return this.registerUserFormGroup.get('firstName'); } - get lastName() { + get lastName(): AbstractControl | null { return this.registerUserFormGroup.get('lastName'); } - get username() { + get username(): AbstractControl | null { return this.registerUserFormGroup.get('username'); } - get email() { + get email(): AbstractControl | null { return this.registerUserFormGroup.get('email'); } - get password() { + get password(): AbstractControl | null { return this.registerUserFormGroup.get('password'); } } diff --git a/src/DevHive.Angular/src/app/services/feed.service.spec.ts b/src/DevHive.Angular/src/app/services/feed.service.spec.ts deleted file mode 100644 index 656b960..0000000 --- a/src/DevHive.Angular/src/app/services/feed.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { FeedService } from './feed.service'; - -describe('FeedService', () => { - let service: FeedService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(FeedService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/DevHive.Angular/src/app/services/feed.service.ts b/src/DevHive.Angular/src/app/services/feed.service.ts deleted file mode 100644 index 23284b9..0000000 --- a/src/DevHive.Angular/src/app/services/feed.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class FeedService { - - constructor() { } -} diff --git a/src/DevHive.Angular/src/app/services/login.service.spec.ts b/src/DevHive.Angular/src/app/services/login.service.spec.ts deleted file mode 100644 index 299b0d5..0000000 --- a/src/DevHive.Angular/src/app/services/login.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { LoginService } from './login.service'; - -describe('LoginService', () => { - let service: LoginService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(LoginService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/DevHive.Angular/src/app/services/login.service.ts b/src/DevHive.Angular/src/app/services/login.service.ts deleted file mode 100644 index 92c777e..0000000 --- a/src/DevHive.Angular/src/app/services/login.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class LoginService { - - constructor() { } -} diff --git a/src/DevHive.Angular/src/app/services/register.service.spec.ts b/src/DevHive.Angular/src/app/services/register.service.spec.ts deleted file mode 100644 index 2ba3960..0000000 --- a/src/DevHive.Angular/src/app/services/register.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { RegisterService } from './register.service'; - -describe('RegisterService', () => { - let service: RegisterService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(RegisterService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/src/DevHive.Angular/src/app/services/register.service.ts b/src/DevHive.Angular/src/app/services/register.service.ts deleted file mode 100644 index 52cb38e..0000000 --- a/src/DevHive.Angular/src/app/services/register.service.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class RegisterService { - - constructor() { } - - -} diff --git a/src/DevHive.Angular/src/app/services/user.service.ts b/src/DevHive.Angular/src/app/services/user.service.ts new file mode 100644 index 0000000..8c679d7 --- /dev/null +++ b/src/DevHive.Angular/src/app/services/user.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from '@angular/core'; +import {Guid} from 'guid-typescript'; +import { User } from '../../models/identity/user'; +import jwt_decode from 'jwt-decode'; +import { IJWTPayload } from '../../interfaces/jwt-payload'; +import { IUserCredentials } from '../../interfaces/user-credentials'; +import { FormGroup } from '@angular/forms'; +import { AppConstants } from 'src/app/app-constants.module'; + +@Injectable({ + providedIn: 'root' +}) +export class UserService { + constructor() { } + + getDefaultUser(): User { + return new User(Guid.createEmpty(), 'gosho_trapov', 'Gosho', 'Trapov', ''); + } + + fetchUserFromSessionStorage(): User { + // Get the token and userid from session storage + const jwt: IJWTPayload = JSON.parse(sessionStorage.getItem('UserCred') ?? ''); + const userCred = jwt_decode<IUserCredentials>(jwt.token); + + return this.fetchUser(userCred.ID, jwt.token); + } + + fetchUser(userId: Guid, authToken: string): User { + const fetchedUser: User = new User(Guid.createEmpty(), '', '', '', ''); + + // Fetch the data and assign it to fetchedUser + fetch(AppConstants.API_USER_URL + '?Id=' + userId, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + authToken + } + }).then(response => response.json()) + .then(data => Object.assign(fetchedUser, data)); + + return fetchedUser; + } + + // TODO: make return bool when the response is an error + loginUser(loginUserFormGroup: FormGroup): void { + // Save the fetch reponse in the sessionStorage + fetch(AppConstants.API_USER_LOGIN_URL, { + method: 'POST', + body: JSON.stringify({ + UserName: loginUserFormGroup.get('username')?.value, + Password: loginUserFormGroup.get('password')?.value + }), + headers: { + 'Content-Type': 'application/json' + } + }).then(response => response.json()) + .then(data => sessionStorage.setItem('UserCred', JSON.stringify(data))); + } + + // TODO: make return bool when the response is an error + registerUser(registerUserFormGroup: FormGroup): void { + // TODO: add a check for form data validity + + // Like in login, save the fetch reponse in the sessionStorage + fetch(AppConstants.API_USER_REGISTER_URL, { + method: 'POST', + body: JSON.stringify({ + UserName: registerUserFormGroup.get('username')?.value, + Email: registerUserFormGroup.get('email')?.value, + FirstName: registerUserFormGroup.get('firstName')?.value, + LastName: registerUserFormGroup.get('lastName')?.value, + Password: registerUserFormGroup.get('password')?.value + }), + headers: { + 'Content-Type': 'application/json' + } + }).then(response => response.json()) + .then(data => sessionStorage.setItem('UserCred', JSON.stringify(data))); + } +} diff --git a/src/DevHive.Angular/src/models/identity/register-user.ts b/src/DevHive.Angular/src/models/identity/register-user.ts deleted file mode 100644 index f96b8bb..0000000 --- a/src/DevHive.Angular/src/models/identity/register-user.ts +++ /dev/null @@ -1,37 +0,0 @@ -export class RegisterUser -{ - private _userName: string; - private _password: string; - - // constructor(userName: string, password: string) - // { - // this.userName = userName; - // this.password = password; - // } - - public get userName() - { - return this._userName; - } - - public set userName(userName: string) - { - if (userName.length <= 3) - throw new Error('Username cannot be less than 3 characters long!'); - - this._userName = userName; - } - - public get password() - { - return this._password; - } - - public set password(pass: string) - { - if (pass.length <= 5) - throw Error("Password too short!"); - - this._password = pass; - } -} diff --git a/src/DevHive.Angular/src/models/identity/user.ts b/src/DevHive.Angular/src/models/identity/user.ts index e16c908..2c5c57d 100644 --- a/src/DevHive.Angular/src/models/identity/user.ts +++ b/src/DevHive.Angular/src/models/identity/user.ts @@ -1,52 +1,52 @@ -import { Guid } from "guid-typescript"; +import { Guid } from 'guid-typescript'; export class User { - private _id : Guid; - private _lastName : string; - private _firstName : string; - private _userName : string; - private _imageUrl : string; + private _id : Guid; + private _lastName : string; + private _firstName : string; + private _userName : string; + private _imageUrl : string; - constructor(id: Guid, userName: string, firstName: string, lastName: string, imageUrl: string) { - this.id = id; - this.userName = userName; - this.firstName = firstName; - this.lastName = lastName; - } + constructor(id: Guid, userName: string, firstName: string, lastName: string, imageUrl: string) { + this.id = id; + this.userName = userName; + this.firstName = firstName; + this.lastName = lastName; + this.imageUrl = imageUrl; + } - - public get id() : Guid { - return this._id; - } - public set id(v : Guid) { - this._id = v; - } - - public get userName() : string { - return this._userName; - } - public set userName(v : string) { - this._userName = v; - } - - public get firstName() : string { - return this._firstName; - } - public set firstName(v : string) { - this._firstName = v; - } - - public get lastName() : string { - return this._lastName; - } - public set lastName(v: string) { - this._lastName = v; - } - - public get imageUrl() : string { - return this._imageUrl; - } - public set imageUrl(v : string) { - this._imageUrl = v; - } + public get id(): Guid { + return this._id; + } + public set id(v: Guid) { + this._id = v; + } + + public get userName(): string { + return this._userName; + } + public set userName(v: string) { + this._userName = v; + } + + public get firstName(): string { + return this._firstName; + } + public set firstName(v: string) { + this._firstName = v; + } + + public get lastName(): string { + return this._lastName; + } + public set lastName(v: string) { + this._lastName = v; + } + + public get imageUrl(): string { + return this._imageUrl; + } + public set imageUrl(v: string) { + this._imageUrl = v; + } } diff --git a/src/DevHive.Angular/src/styles.css b/src/DevHive.Angular/src/styles.css index 3d6d2f4..95592a1 100644 --- a/src/DevHive.Angular/src/styles.css +++ b/src/DevHive.Angular/src/styles.css @@ -4,6 +4,7 @@ :root { --bg-color: white; --focus-color: forestgreen; + --card-bg: white; } html, body { @@ -12,6 +13,7 @@ html, body { } body { font: 21px sans-serif !important; + background-color: var(--bg-color); } input:focus, button:focus { @@ -68,6 +70,7 @@ input:focus, button:focus { .input-field { width: 100%; + background-color: var(--bg-color); border: 0; border-bottom: 1px solid grey; diff --git a/src/DevHive.Data/Repositories/TechnologyRepository.cs b/src/DevHive.Data/Repositories/TechnologyRepository.cs index 1631972..73827a7 100644 --- a/src/DevHive.Data/Repositories/TechnologyRepository.cs +++ b/src/DevHive.Data/Repositories/TechnologyRepository.cs @@ -27,12 +27,6 @@ namespace DevHive.Data.Repositories return await RepositoryMethods.SaveChangesAsync(this._context); } - - public async Task<Technology> GetByNameAsync(string technologyName) - { - return await this._context.Technologies - .FirstOrDefaultAsync(x => x.Name == technologyName); - } #endregion #region Read @@ -43,6 +37,11 @@ namespace DevHive.Data.Repositories .Set<Technology>() .FindAsync(id); } + public async Task<Technology> GetByNameAsync(string technologyName) + { + return await this._context.Technologies + .FirstOrDefaultAsync(x => x.Name == technologyName); + } #endregion #region Edit diff --git a/src/DevHive.Tests/DevHive.Web.Tests/TechnologyController.Tests.cs b/src/DevHive.Tests/DevHive.Web.Tests/TechnologyController.Tests.cs index 711f66f..2709c9a 100644 --- a/src/DevHive.Tests/DevHive.Web.Tests/TechnologyController.Tests.cs +++ b/src/DevHive.Tests/DevHive.Web.Tests/TechnologyController.Tests.cs @@ -6,6 +6,7 @@ using DevHive.Web.Models.Technology; using Microsoft.AspNetCore.Mvc; using Moq; using NUnit.Framework; +using System; using System.Threading.Tasks; namespace DevHive.Web.Tests @@ -13,6 +14,7 @@ namespace DevHive.Web.Tests [TestFixture] public class TechnologyControllerTests { + const string NAME = "Gosho Trapov"; private Mock<ITechnologyService> TechnologyServiceMock { get; set; } private Mock<IMapper> MapperMock { get; set; } private TechnologyController TechnologyController { get; set; } @@ -28,16 +30,14 @@ namespace DevHive.Web.Tests #region Create [Test] public void Create_ReturnsOkObjectResult_WhenTechnologyIsSuccessfullyCreated() - { - string name = "Gosho Trapov"; - + { CreateTechnologyWebModel createTechnologyWebModel = new CreateTechnologyWebModel { - Name = name + Name = NAME }; CreateTechnologyServiceModel createTechnologyServiceModel = new CreateTechnologyServiceModel { - Name = name + Name = NAME }; this.MapperMock.Setup(p => p.Map<CreateTechnologyServiceModel>(It.IsAny<CreateTechnologyWebModel>())).Returns(createTechnologyServiceModel); @@ -48,8 +48,135 @@ namespace DevHive.Web.Tests Assert.IsInstanceOf<OkResult>(result); } + [Test] + public void Create_ReturnsBadRequestObjectResult_WhenTechnologyIsNotCreatedSuccessfully() + { + CreateTechnologyWebModel createTechnologyWebModel = new CreateTechnologyWebModel + { + Name = NAME + }; + CreateTechnologyServiceModel createTechnologyServiceModel = new CreateTechnologyServiceModel + { + Name = NAME + }; + + this.MapperMock.Setup(p => p.Map<CreateTechnologyServiceModel>(It.IsAny<CreateTechnologyWebModel>())).Returns(createTechnologyServiceModel); + this.TechnologyServiceMock.Setup(p => p.Create(It.IsAny<CreateTechnologyServiceModel>())).Returns(Task.FromResult(false)); + + IActionResult result = this.TechnologyController.Create(createTechnologyWebModel).Result; + + Assert.IsInstanceOf<BadRequestObjectResult>(result); + } + #endregion + + #region Read + [Test] + public void GetById_ReturnsTheThecnology_WhenItExists() + { + Guid id = new Guid(); + + CreateTechnologyServiceModel createTechnologyServiceModel = new CreateTechnologyServiceModel + { + Name = NAME + }; + CreateTechnologyWebModel createTechnologyWebModel = new CreateTechnologyWebModel + { + Name = NAME + }; + + this.TechnologyServiceMock.Setup(p => p.GetTechnologyById(It.IsAny<Guid>())).Returns(Task.FromResult(createTechnologyServiceModel)); + this.MapperMock.Setup(p => p.Map<CreateTechnologyWebModel>(It.IsAny<CreateTechnologyServiceModel>())).Returns(createTechnologyWebModel); + + IActionResult result = this.TechnologyController.GetById(id).Result; + + Assert.IsInstanceOf<OkObjectResult>(result); + + OkObjectResult okObjectResult = result as OkObjectResult; + CreateTechnologyWebModel resultModel = okObjectResult.Value as Models.Technology.CreateTechnologyWebModel; + + Assert.AreEqual(NAME, resultModel.Name); + } + #endregion + + #region Update + [Test] + public void Update_ShouldReturnOkResult_WhenTechnologyIsUpdatedSuccessfully() + { + Guid id = new Guid(); + UpdateTechnologyWebModel updateTechnologyWebModel = new UpdateTechnologyWebModel + { + Name = NAME + }; + UpdateTechnologyServiceModel updateTechnologyServiceModel = new UpdateTechnologyServiceModel + { + Name = NAME + }; + + this.TechnologyServiceMock.Setup(p => p.UpdateTechnology(It.IsAny<Guid>(), It.IsAny<UpdateTechnologyServiceModel>())).Returns(Task.FromResult(true)); + this.MapperMock.Setup(p => p.Map<UpdateTechnologyServiceModel>(It.IsAny<UpdateTechnologyWebModel>())).Returns(updateTechnologyServiceModel); + + IActionResult result = this.TechnologyController.Update(id, updateTechnologyWebModel).Result; + + Assert.IsInstanceOf<OkResult>(result); + } + + [Test] + public void Update_ShouldReturnOkResult_WhenTechnologyIsNotUpdatedSuccessfully () + { + Guid id = new Guid(); + string message = "Could not update Technology"; + UpdateTechnologyWebModel updateTechnologyWebModel = new UpdateTechnologyWebModel + { + Name = NAME + }; + UpdateTechnologyServiceModel updateTechnologyServiceModel = new UpdateTechnologyServiceModel + { + Name = NAME + }; + + this.TechnologyServiceMock.Setup(p => p.UpdateTechnology(It.IsAny<Guid>(), It.IsAny<UpdateTechnologyServiceModel>())).Returns(Task.FromResult(false)); + this.MapperMock.Setup(p => p.Map<UpdateTechnologyServiceModel>(It.IsAny<UpdateTechnologyWebModel>())).Returns(updateTechnologyServiceModel); + + IActionResult result = this.TechnologyController.Update(id, updateTechnologyWebModel).Result; + Assert.IsInstanceOf<BadRequestObjectResult>(result); + + BadRequestObjectResult badRequestObjectResult = result as BadRequestObjectResult; + string resultModel = badRequestObjectResult.Value.ToString(); + Assert.AreEqual(message, resultModel); + } #endregion + #region Delete + [Test] + public void Delete_ReturnsOkResult_When_TechnologyIsDeletedSuccessfully() + { + Guid id = new Guid(); + + this.TechnologyServiceMock.Setup(p => p.DeleteTechnology(It.IsAny<Guid>())).Returns(Task.FromResult(true)); + + IActionResult result = this.TechnologyController.Delete(id).Result; + + Assert.IsInstanceOf<OkResult>(result); + } + + [Test] + public void Delet_ReturnsBadRequestObjectResult_WhenTechnologyIsNotDeletedSuccessfully() + { + string message = "Could not delete Technology"; + Guid id = new Guid(); + + this.TechnologyServiceMock.Setup(p => p.DeleteTechnology(It.IsAny<Guid>())).Returns(Task.FromResult(false)); + + IActionResult result = this.TechnologyController.Delete(id).Result; + + Assert.IsInstanceOf<BadRequestObjectResult>(result); + + BadRequestObjectResult badRequestObjectResult = result as BadRequestObjectResult; + string resultModel = badRequestObjectResult.Value.ToString(); + + Assert.AreEqual(message, resultModel); + } + #endregion } } |
