diff --git a/src/app/app.html b/src/app/app.html index c11e7c0..aec7219 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,3 +1,5 @@ + +
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index dc39edb..a091537 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,3 +1,24 @@ import { Routes } from '@angular/router'; +import {HomeView} from './home/views/home-view/home-view'; +import {OnboardingView} from './users/views/onboarding-view/onboarding-view'; +import {UserProfileView} from './users/views/user-profile-view/user-profile-view'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: 'home', + component: HomeView + }, + { + path: 'onboarding', + component: OnboardingView + }, + { + path: 'user/:userId', + component: UserProfileView + }, + { + path: '', + pathMatch: 'full', + redirectTo: '/home' + } +]; diff --git a/src/app/app.ts b/src/app/app.ts index f73df9a..7da287b 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,9 +1,10 @@ import { Component, signal } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import {Navbar} from './shared/components/navbar/navbar'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, Navbar], templateUrl: './app.html', styleUrl: './app.css' }) diff --git a/src/app/home/views/home-view/home-view.css b/src/app/home/views/home-view/home-view.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/home/views/home-view/home-view.html b/src/app/home/views/home-view/home-view.html new file mode 100644 index 0000000..d975b27 --- /dev/null +++ b/src/app/home/views/home-view/home-view.html @@ -0,0 +1 @@ +

home-view works!

diff --git a/src/app/home/views/home-view/home-view.ts b/src/app/home/views/home-view/home-view.ts new file mode 100644 index 0000000..31a964f --- /dev/null +++ b/src/app/home/views/home-view/home-view.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home-view', + imports: [], + templateUrl: './home-view.html', + styleUrl: './home-view.css', +}) +export class HomeView { + +} diff --git a/src/app/shared/components/navbar/navbar.css b/src/app/shared/components/navbar/navbar.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/navbar/navbar.html b/src/app/shared/components/navbar/navbar.html new file mode 100644 index 0000000..e3275ce --- /dev/null +++ b/src/app/shared/components/navbar/navbar.html @@ -0,0 +1,32 @@ + diff --git a/src/app/shared/components/navbar/navbar.ts b/src/app/shared/components/navbar/navbar.ts new file mode 100644 index 0000000..60d6f32 --- /dev/null +++ b/src/app/shared/components/navbar/navbar.ts @@ -0,0 +1,26 @@ +import {Component, inject, OnInit} from '@angular/core'; + +import Keycloak from 'keycloak-js'; + +import {SharedStore} from '../../stores/shared.store'; + +@Component({ + selector: 'app-navbar', + imports: [], + templateUrl: './navbar.html', + styleUrl: './navbar.css', +}) +export class Navbar implements OnInit{ + + readonly sharedStore = inject(SharedStore) + keycloak = inject(Keycloak) + + ngOnInit() { + this.sharedStore.loadCurrentUser() + } + + logout() { + this.keycloak.logout() + } + +} diff --git a/src/app/shared/stores/shared.store.ts b/src/app/shared/stores/shared.store.ts new file mode 100644 index 0000000..6950da4 --- /dev/null +++ b/src/app/shared/stores/shared.store.ts @@ -0,0 +1,93 @@ +import {inject} from '@angular/core'; +import {Router} from '@angular/router'; + +import {patchState, signalStore, withComputed, withMethods, withState} from '@ngrx/signals'; + +import {OnboardRequest, User} from '../../users/models/users.models'; +import {UserService} from '../../users/services/user'; + +interface SharedState { + isCurrentUserLoaded: boolean + isOnboarded: boolean + currentUserId: string + currentUser: User | null +} + +const initialState: SharedState = { + isCurrentUserLoaded: false, + isOnboarded: false, + currentUser: null, + currentUserId: '' +} + +export const SharedStore = signalStore( + {providedIn: 'root'}, + withState(initialState), + withMethods((store) => { + let userService = inject(UserService) + let router: Router = inject(Router) + return { + loadCurrentUser() { + console.log('Loading current user...') + userService.getCurrentUser().subscribe({ + next: result => { + console.log(result) + patchState(store, { + isCurrentUserLoaded: true, + currentUserId: result.id, + currentUser: result, + isOnboarded: result.onboarded + }) + // If user is not onboarded yet, go to onboard page + if (!result.onboarded) { + console.log('Go to onboard page...') + router.navigateByUrl('/onboarding') + } + }, + error: (err) => { + console.log(err) + patchState(store, { + isCurrentUserLoaded: false, + currentUser: null, + currentUserId: '', + isOnboarded: false + }) + // Todo Go to error page + } + }) + }, + onboardCurrentUser(payload: OnboardRequest) { + console.log('Handle onboarding...') + userService.onboardUser(payload).subscribe({ + next: result => { + patchState(store, { + currentUser: result, + isOnboarded: true, + isCurrentUserLoaded: true, + currentUserId: result.id + }) + console.log('Navigate to user view page...') + router.navigate(['/user', result.id]) + }, + error: err => { + console.log(err) + } + }) + } + } + }), + withComputed((store) => { + return { + getCurrentUserName (): string { + if (store.isCurrentUserLoaded()) { + return `${store.currentUser()!.firstName} ${store.currentUser()!.lastName.substring(0,1)}` + } else { + return 'FirstName LastName' + } + }, + getCurrentUserAvatarUrl(): string { + return 'avatar' + } + } + }) +) diff --git a/src/app/users/models/users.models.ts b/src/app/users/models/users.models.ts new file mode 100644 index 0000000..220e1b1 --- /dev/null +++ b/src/app/users/models/users.models.ts @@ -0,0 +1,16 @@ +export interface User { + id: string + firstName: string + lastName: string + headline: string + avatarUrl: string + onboarded: boolean + active: boolean + emailVerified: boolean +} + +export interface OnboardRequest { + organizationName: string + description: string + student: boolean +} diff --git a/src/app/users/services/user.ts b/src/app/users/services/user.ts new file mode 100644 index 0000000..5cef5a0 --- /dev/null +++ b/src/app/users/services/user.ts @@ -0,0 +1,25 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {OnboardRequest, User} from '../models/users.models'; + +@Injectable({ + providedIn: 'root', +}) +export class UserService { + + httpClient: HttpClient = inject(HttpClient) + + getCurrentUser(): Observable { + return this.httpClient.get(`http://localhost:8000/users/current`) + } + + getUserById(userId: string): Observable { + return this.httpClient.get(`http://localhost:8000/users/profile/${userId}`) + } + + onboardUser(payload: OnboardRequest): Observable{ + return this.httpClient.post(`http://localhost:8000/users/onboarding`, payload) + } + +} diff --git a/src/app/users/views/onboarding-view/onboarding-view.css b/src/app/users/views/onboarding-view/onboarding-view.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/users/views/onboarding-view/onboarding-view.html b/src/app/users/views/onboarding-view/onboarding-view.html new file mode 100644 index 0000000..4a261c5 --- /dev/null +++ b/src/app/users/views/onboarding-view/onboarding-view.html @@ -0,0 +1,54 @@ +
+
+
+ +
+

Tell more about you

+
+ +
+ + + + + @if (isStudent()){ + School name + } @else { + Employer + } + + + @if (form.get('organizationName')?.touched && form.get('organizationName')?.invalid ){ + This field cannot be blank + } + + + + + + @if (isStudent()){ + Field of study + } @else { + Position name + } + + + @if (form.get('description')?.touched && form.get('description')?.invalid ){ + This field cannot be blank + } + + + +
+ I am student +
+ +
+ +
+ +
+ +
+
+
diff --git a/src/app/users/views/onboarding-view/onboarding-view.ts b/src/app/users/views/onboarding-view/onboarding-view.ts new file mode 100644 index 0000000..7af2c1d --- /dev/null +++ b/src/app/users/views/onboarding-view/onboarding-view.ts @@ -0,0 +1,48 @@ +import {Component, inject, signal} from '@angular/core'; +import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {MatInputModule} from '@angular/material/input'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatButtonModule} from '@angular/material/button'; +import {OnboardRequest} from '../../models/users.models'; +import {SharedStore} from '../../../shared/stores/shared.store'; + +@Component({ + selector: 'app-onboarding-view', + imports: [ + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatCheckboxModule, + MatButtonModule + ], + templateUrl: './onboarding-view.html', + styleUrl: './onboarding-view.css', +}) +export class OnboardingView { + + sharedStore = inject(SharedStore) + formBuilder: FormBuilder = inject(FormBuilder) + + form: FormGroup = this.formBuilder.group({ + organizationName: ['', [Validators.required, Validators.maxLength(255)]], + description: ['', [Validators.required, Validators.maxLength(255)]], + }) + + isStudent = signal(false) + + updateIsStudent(value: boolean){ + this.isStudent.set(value) + } + + submit() { + const payload: OnboardRequest = { + organizationName: this.form.get('organizationName')?.value, + description: this.form.get('description')?.value, + student: this.isStudent() + } + + this.sharedStore.onboardCurrentUser(payload) + } + +} diff --git a/src/app/users/views/user-profile-view/user-profile-view.css b/src/app/users/views/user-profile-view/user-profile-view.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/users/views/user-profile-view/user-profile-view.html b/src/app/users/views/user-profile-view/user-profile-view.html new file mode 100644 index 0000000..2eeb553 --- /dev/null +++ b/src/app/users/views/user-profile-view/user-profile-view.html @@ -0,0 +1 @@ +

user-profile-view works!

diff --git a/src/app/users/views/user-profile-view/user-profile-view.ts b/src/app/users/views/user-profile-view/user-profile-view.ts new file mode 100644 index 0000000..2e1b210 --- /dev/null +++ b/src/app/users/views/user-profile-view/user-profile-view.ts @@ -0,0 +1,19 @@ +import {Component, effect, input} from '@angular/core'; + +@Component({ + selector: 'app-user-profile-view', + imports: [], + templateUrl: './user-profile-view.html', + styleUrl: './user-profile-view.css', +}) +export class UserProfileView { + + userId = input.required() + + constructor() { + effect(() => { + console.log('User id: ' + this.userId()) + }) + } + +} diff --git a/src/styles.css b/src/styles.css index ec6bedf..4f479a8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,7 @@ @import 'bulma/css/versions/bulma-no-dark-mode.css'; + +html, body { min-height: 100%; } + +mat-form-field { + width: 100%; +}