diff options
Diffstat (limited to '')
19 files changed, 472 insertions, 233 deletions
diff --git a/ufund-ui/src/app/app-routing.module.ts b/ufund-ui/src/app/app-routing.module.ts index 4b76654..a6ea806 100644 --- a/ufund-ui/src/app/app-routing.module.ts +++ b/ufund-ui/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import {LoginComponent} from './components/login/login.component';  import {HomePageComponent} from './components/home-page/home-page.component';  import {FundingBasketComponent} from './components/funding-basket/funding-basket.component';  import {NeedPageComponent} from './components/need-page/need-page.component'; +import {SignupComponent} from './components/signup/signup.component';  const routes: Routes = [      {path: '', component: HomePageComponent}, @@ -13,7 +14,8 @@ const routes: Routes = [      {path: 'cupboard', component: CupboardComponent},      {path: 'dashboard', component: DashboardComponent},      {path: 'basket', component: FundingBasketComponent}, -    {path: 'need/:id', component: NeedPageComponent} +    {path: 'need/:id', component: NeedPageComponent}, +    {path: 'signup', component: SignupComponent},  ];  @NgModule({ diff --git a/ufund-ui/src/app/app.component.ts b/ufund-ui/src/app/app.component.ts index 7dc8ffb..86717c4 100644 --- a/ufund-ui/src/app/app.component.ts +++ b/ufund-ui/src/app/app.component.ts @@ -1,7 +1,7 @@  import {Component, OnInit, Inject} from '@angular/core'; -import {UsersService} from './services/users.service';  import {BehaviorSubject} from 'rxjs';  import { DOCUMENT } from '@angular/common'; +import {AuthService} from './services/auth.service';  @Component({    selector: 'app-root', @@ -14,16 +14,16 @@ export class AppComponent implements OnInit {      currentUser$: BehaviorSubject<string> = new BehaviorSubject<string>("Logged out.");      constructor( -        private userService: UsersService, +        private authService: AuthService,          @Inject(DOCUMENT) private document: Document      ) {}      reloadPage() {          this.document.defaultView?.location.reload(); -      } +    }      ngOnInit() { -        this.userService.getCurrentUserSubject().subscribe(r => { +        this.authService.getCurrentUserSubject().subscribe(r => {              this.currentUser$?.next(r                  ? "Logged in as " + r.username                  : "Logged out." diff --git a/ufund-ui/src/app/app.module.ts b/ufund-ui/src/app/app.module.ts index 9f525fe..156ef5f 100644 --- a/ufund-ui/src/app/app.module.ts +++ b/ufund-ui/src/app/app.module.ts @@ -14,6 +14,7 @@ import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';  import {DashboardComponent} from './components/dashboard/dashboard.component';  import {CommonModule} from '@angular/common';  import {LoginComponent} from './components/login/login.component'; +import { SignupComponent } from './components/signup/signup.component';  @NgModule({      declarations: [ @@ -25,6 +26,7 @@ import {LoginComponent} from './components/login/login.component';          NeedListComponent,          DashboardComponent,          LoginComponent, +        SignupComponent,      ],      imports: [          BrowserModule, diff --git a/ufund-ui/src/app/components/cupboard/cupboard.component.ts b/ufund-ui/src/app/components/cupboard/cupboard.component.ts index ad68c38..85ffd17 100644 --- a/ufund-ui/src/app/components/cupboard/cupboard.component.ts +++ b/ufund-ui/src/app/components/cupboard/cupboard.component.ts @@ -1,10 +1,10 @@  import {Component, Input, OnInit, ViewChild} from '@angular/core';  import { CupboardService } from '../../services/cupboard.service'; -import { UsersService } from '../../services/users.service';  import { Need, GoalType } from '../../models/Need';  import { userType } from '../../models/User';  import { BehaviorSubject, catchError, of } from 'rxjs';  import { NeedListComponent } from '../need-list/need-list.component'; +import {AuthService} from '../../services/auth.service';  @Component({      selector: 'app-cupboard', @@ -18,8 +18,12 @@ export class CupboardComponent implements OnInit {      protected statusText = new BehaviorSubject("")      selectedForm = "create";      needs: any; -    @ViewChild("needList") needList?: NeedListComponent; -    constructor(private cupboardService: CupboardService, private usersService: UsersService) { } +    @ViewChild("needList") needList?: NeedListComponent + +    constructor( +        private cupboardService: CupboardService, +        private authService: AuthService +    ) {}      ngOnInit(): void {          this.cupboardService.getNeeds().subscribe(n => this.needs = n); @@ -73,7 +77,7 @@ export class CupboardComponent implements OnInit {      }      isManager() { -        const type = this.usersService.getCurrentUser()?.type; +        const type = this.authService.getCurrentUser()?.type;          return type === ("MANAGER" as unknown as userType);      } @@ -89,9 +93,9 @@ export class CupboardComponent implements OnInit {              filterAttributes: [],              current: 0          }; -        +          this.cupboardService.updateNeed(need.id, need) -            .pipe(catchError((ex, r) => { +            .pipe(catchError((ex, _) => {                  if (ex.status == 500) {                      this.statusText.next("Fields cannot be blank");                  } else if (ex.status == 400) { @@ -128,7 +132,7 @@ export class CupboardComponent implements OnInit {          console.log("need:", need);          console.log("form submitted. creating need: ", need);          this.cupboardService.createNeed(need) -            .pipe(catchError((ex, r) => { +            .pipe(catchError((ex, _) => {                  if (ex.status == 500) {                      this.statusText.next("Fields cannot be blank");                  } else if (ex.status == 400) { @@ -155,48 +159,3 @@ export class CupboardComponent implements OnInit {      }  } - -let friendlyHttpStatus: { [key: number]: string } = { -    200: 'OK', -    201: 'Created', -    202: 'Accepted', -    203: 'Non-Authoritative Information', -    204: 'No Content', -    205: 'Reset Content', -    206: 'Partial Content', -    300: 'Multiple Choices', -    301: 'Moved Permanently', -    302: 'Found', -    303: 'See Other', -    304: 'Not Modified', -    305: 'Use Proxy', -    306: 'Unused', -    307: 'Temporary Redirect', -    400: 'Bad Request', -    401: 'Unauthorized', -    402: 'Payment Required', -    403: 'Forbidden', -    404: 'Not Found', -    405: 'Method Not Allowed', -    406: 'Not Acceptable', -    407: 'Proxy Authentication Required', -    408: 'Request Timeout', -    409: 'Conflict', -    410: 'Gone', -    411: 'Length Required', -    412: 'Precondition Required', -    413: 'Request Entry Too Large', -    414: 'Request-URI Too Long', -    415: 'Unsupported Media Type', -    416: 'Requested Range Not Satisfiable', -    417: 'Expectation Failed', -    418: 'I\'m a teapot', -    422: 'Unprocessable Entity', -    429: 'Too Many Requests', -    500: 'Internal Server Error', -    501: 'Not Implemented', -    502: 'Bad Gateway', -    503: 'Service Unavailable', -    504: 'Gateway Timeout', -    505: 'HTTP Version Not Supported', -}; diff --git a/ufund-ui/src/app/components/dashboard/dashboard.component.ts b/ufund-ui/src/app/components/dashboard/dashboard.component.ts index b9faefa..a0ad566 100644 --- a/ufund-ui/src/app/components/dashboard/dashboard.component.ts +++ b/ufund-ui/src/app/components/dashboard/dashboard.component.ts @@ -1,21 +1,21 @@ -import { Component } from '@angular/core'; -import { UsersService } from '../../services/users.service'; -import { userType } from '../../models/User'; +import {Component} from '@angular/core'; +import {userType} from '../../models/User'; +import {AuthService} from '../../services/auth.service';  @Component({ -  selector: 'app-dashboard', -  standalone: false, -  templateUrl: './dashboard.component.html', -  styleUrl: './dashboard.component.css' +    selector: 'app-dashboard', +    standalone: false, +    templateUrl: './dashboard.component.html', +    styleUrl: './dashboard.component.css'  })  export class DashboardComponent {      constructor( -      protected usersService: UsersService, +        protected authService: AuthService,      ) {}      isManager() { -        const type = this.usersService.getCurrentUser()?.type; +        const type = this.authService.getCurrentUser()?.type;          return type === ("MANAGER" as unknown as userType); -      } +    }  } diff --git a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts index e654711..faa7e0b 100644 --- a/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts +++ b/ufund-ui/src/app/components/funding-basket/funding-basket.component.ts @@ -1,11 +1,9 @@  import {Component, Input, OnInit, ViewChild} from '@angular/core'; -import {User} from '../../models/User'; -import { UsersService } from '../../services/users.service'; -import { Need } from '../../models/Need'; -import { NeedListComponent } from '../need-list/need-list.component'; -import { Router } from '@angular/router'; -import { CupboardService } from '../../services/cupboard.service'; -import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs'; +import {UsersService} from '../../services/users.service'; +import {Router} from '@angular/router'; +import {CupboardService} from '../../services/cupboard.service'; +import {catchError, firstValueFrom, Observable} from 'rxjs'; +import {AuthService} from '../../services/auth.service';  @Component({      selector: 'app-funding-basket', @@ -14,67 +12,67 @@ import { BehaviorSubject, catchError, firstValueFrom, Observable } from 'rxjs';      styleUrl: './funding-basket.component.css'  })  export class FundingBasketComponent implements OnInit { -  statusText: any; +    statusText: any; -  constructor( -    private router: Router, -    protected cupboardService: CupboardService,  -    protected usersService: UsersService -  ) {} +    constructor( +        private router: Router, +        protected cupboardService: CupboardService, +        protected usersService: UsersService, +        private authService: AuthService +    ) {} -  @ViewChild("contribution") contribution?: Input; -  @Input() isValid: boolean = true; +    @ViewChild("contribution") contribution?: Input; +    @Input() isValid: boolean = true; -  // this is for login rerouting -  ngOnInit(): void { -    if (!this.usersService.getCurrentUser()) { -      this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); -      return; -    } - -    this.usersService.refreshBasket(); -    // this.usersService.removeNeed(); <- call this to remove -  } +    // this is for login rerouting +    ngOnInit(): void { +        if (!this.authService.getCurrentUser()) { +            this.router.navigate(['/login'], {queryParams: {redir: this.router.url}}); +            return; +        } -  async checkout() { -    this.isValid = true; -    for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { -      let contribution = c as HTMLInputElement; -      contribution.setAttribute("style",""); -      if ( contribution.value == '' || contribution.valueAsNumber <= 0) { -        this.isValid = false; -        contribution.setAttribute("style","color: #ff0000"); -      } +        this.usersService.refreshBasket(); +        // this.usersService.removeNeed(); <- call this to remove      } -    if (this.isValid) { -      for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { -        let contribution = c as HTMLInputElement; -        let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); -        need.current +=+ contribution.value; -        this.usersService.removeNeed(+need.id); -        this.cupboardService.updateNeed(need.id, need) -            .pipe(catchError((ex, r) => { -                if (ex.status == 500) { -                    this.statusText.next('Fields cannot be blank'); -                } else if (ex.status == 400) { -                    this.statusText.next('Goal must be greater than 0'); -                } else { -                    this.statusText.next('Error on creating need'); -                } -                return new Observable<string>(); -            })) -            .subscribe((result) => { -                if (result) { -                    console.log('need updated successfully'); -                    //this.needList?.refresh() -                } else { -                    console.log('need update failed'); -                } -            }); -      } -    } -  } +    async checkout() { +        this.isValid = true; +        for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { +            let contribution = c as HTMLInputElement; +            contribution.setAttribute("style", ""); +            if (contribution.value == '' || contribution.valueAsNumber <= 0) { +                this.isValid = false; +                contribution.setAttribute("style", "color: #ff0000"); +            } +        } +        if (this.isValid) { +            for (let c of document.getElementById("funding-basket")?.querySelectorAll('.contribution')!) { +                let contribution = c as HTMLInputElement; +                let need = await firstValueFrom(this.cupboardService.getNeed(+contribution.id)); +                need.current += +contribution.value; +                this.usersService.removeNeed(+need.id); +                this.cupboardService.checkoutNeed(need.id, +contribution.value) +                    .pipe(catchError((ex, _) => { +                        if (ex.status == 500) { +                            this.statusText.next('Fields cannot be blank'); +                        } else if (ex.status == 400) { +                            this.statusText.next('Goal must be greater than 0'); +                        } else { +                            this.statusText.next('Error on creating need'); +                        } +                        return new Observable<string>(); +                    })) +                    .subscribe((result) => { +                        if (result) { +                            console.log('need updated successfully'); +                            //this.needList?.refresh() +                        } else { +                            console.log('need update failed'); +                        } +                    }); +            } +        } +    }  } diff --git a/ufund-ui/src/app/components/home-page/home-page.component.ts b/ufund-ui/src/app/components/home-page/home-page.component.ts index 5b2277c..95e8962 100644 --- a/ufund-ui/src/app/components/home-page/home-page.component.ts +++ b/ufund-ui/src/app/components/home-page/home-page.component.ts @@ -1,10 +1,10 @@ -import { Component } from '@angular/core'; +import {Component} from '@angular/core';  @Component({ -  selector: 'app-home-page', -  standalone: false, -  templateUrl: './home-page.component.html', -  styleUrl: './home-page.component.css' +    selector: 'app-home-page', +    standalone: false, +    templateUrl: './home-page.component.html', +    styleUrl: './home-page.component.css'  })  export class HomePageComponent { diff --git a/ufund-ui/src/app/components/login/login.component.html b/ufund-ui/src/app/components/login/login.component.html index 2cdb6d0..a6441f4 100644 --- a/ufund-ui/src/app/components/login/login.component.html +++ b/ufund-ui/src/app/components/login/login.component.html @@ -3,5 +3,5 @@  <input placeholder="Username" type="text" #username>  <input placeholder="Password" type="password" #password>  <button type="button" (click)="login(username.value, password.value)">Login</button> -<button type="button" (click)="signup(username.value, password.value)">Create Account</button> +<button type="button" routerLink="/signup">Create Account...</button>  <span *ngIf="statusText">{{statusText | async}}</span> diff --git a/ufund-ui/src/app/components/login/login.component.ts b/ufund-ui/src/app/components/login/login.component.ts index 9d806f5..f6a2996 100644 --- a/ufund-ui/src/app/components/login/login.component.ts +++ b/ufund-ui/src/app/components/login/login.component.ts @@ -2,12 +2,13 @@ import {Component, OnInit} from '@angular/core'  import {UsersService} from '../../services/users.service';  import {ActivatedRoute, Router} from '@angular/router';  import {BehaviorSubject} from 'rxjs'; +import {AuthService} from '../../services/auth.service';  @Component({ -  selector: 'app-login', -  standalone: false, -  templateUrl: './login.component.html', -  styleUrl: './login.component.css' +    selector: 'app-login', +    standalone: false, +    templateUrl: './login.component.html', +    styleUrl: './login.component.css'  })  export class LoginComponent implements OnInit { @@ -17,7 +18,8 @@ export class LoginComponent implements OnInit {      constructor(          protected usersService: UsersService,          protected router: Router, -        private route: ActivatedRoute +        private route: ActivatedRoute, +        private authService: AuthService      ) {}      ngOnInit() { @@ -31,7 +33,7 @@ export class LoginComponent implements OnInit {              return;          } -        this.usersService.login(username, password).then(() => { +        this.authService.login(username, password).then(() => {              this.router.navigate([next]);          }).catch(ex => {              this.statusText.next("Unable to login: " + friendlyHttpStatus[ex.status]) diff --git a/ufund-ui/src/app/components/need-list/need-list.component.ts b/ufund-ui/src/app/components/need-list/need-list.component.ts index 17bb3e6..2bbacb0 100644 --- a/ufund-ui/src/app/components/need-list/need-list.component.ts +++ b/ufund-ui/src/app/components/need-list/need-list.component.ts @@ -128,7 +128,7 @@ export class NeedListComponent {              } else {                this.searchResults = n.sort(this.currentSortAlgo).reverse();              } -             +              console.log(currentSearchValue, this.searchResults);              });            } diff --git a/ufund-ui/src/app/components/need-page/need-page.component.ts b/ufund-ui/src/app/components/need-page/need-page.component.ts index 597d0e0..e38554c 100644 --- a/ufund-ui/src/app/components/need-page/need-page.component.ts +++ b/ufund-ui/src/app/components/need-page/need-page.component.ts @@ -2,30 +2,30 @@ import {Component, Input} from '@angular/core';  import {GoalType, Need} from '../../models/Need';  import {ActivatedRoute} from "@angular/router";  import {CupboardService} from "../../services/cupboard.service"; -import { NgFor } from '@angular/common'; +import {NgFor} from '@angular/common';  @Component({ -  selector: 'app-need-page', -  standalone: false, -  templateUrl: './need-page.component.html', -  styleUrl: './need-page.component.css' +    selector: 'app-need-page', +    standalone: false, +    templateUrl: './need-page.component.html', +    styleUrl: './need-page.component.css'  })  export class NeedPageComponent { -  constructor( -     private route: ActivatedRoute, -     private cupboardService: CupboardService, -  ) {} +    constructor( +        private route: ActivatedRoute, +        private cupboardService: CupboardService, +    ) {} -  public GoalType = GoalType; +    public GoalType = GoalType; -  @Input() need?: Need; +    @Input() need?: Need; -  ngOnInit(): void { -    const id = Number(this.route.snapshot.paramMap.get('id')); -    this.cupboardService.getNeed(id).subscribe(n => this.need = n); -  } +    ngOnInit(): void { +        const id = Number(this.route.snapshot.paramMap.get('id')); +        this.cupboardService.getNeed(id).subscribe(n => this.need = n); +    } -  back() { -    window.history.back(); -  } -}
\ No newline at end of file +    back() { +        window.history.back(); +    } +} diff --git a/ufund-ui/src/app/components/signup/signup.component.css b/ufund-ui/src/app/components/signup/signup.component.css new file mode 100644 index 0000000..f286cf9 --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.css @@ -0,0 +1,47 @@ +:host { +    display: flex; +    flex-direction: column; +    max-width: 300px; +    gap: 10px; + +    & > div { +        display: flex; +        flex-direction: column; +    } +} + +.border { +    border-style: solid; +    border-width: 1px; +    padding: 10px; +    margin: 10px; +    position: absolute; +    background-color: white; +    box-shadow: 0 0 10px 10px black; +} + +#bar { +    height: 5px; +    width: 100%; +    appearance: none; +    overflow: hidden; +    /*margin-top: -5px;*/ +} + +#bar::-webkit-progress-bar { +    background-color: lightgray; +    transition: width 0.5s ease-in-out, background-color 0.5s ease-in-out; +} + +.color0::-webkit-progress-value { background: rgba(255, 0 ,0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color1::-webkit-progress-value { background: rgba(255, 0 ,0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color2::-webkit-progress-value { background: rgba(255, 165, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color3::-webkit-progress-value { background: rgba(255, 255, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color4::-webkit-progress-value { background: rgba(173, 255, 47, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color5::-webkit-progress-value { background: rgba(50, 205, 50, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } +.color6::-webkit-progress-value { background: rgba(0, 128, 0, 1); transition: background-color 0.5s ease-in-out, width 0.5s ease-in-out; } + +#requirement2, #statusText, #passwordStatusText, #usernameStatusText { +    color: red; +} + diff --git a/ufund-ui/src/app/components/signup/signup.component.html b/ufund-ui/src/app/components/signup/signup.component.html new file mode 100644 index 0000000..ebedc2a --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.html @@ -0,0 +1,26 @@ +<p>Signup:</p> +<div> +    <input placeholder="Username" type="text" (input)="validate(username.value, confirmPass.value, password.value)" #username> +    <span *ngIf="usernameStatusText">{{usernameStatusText | async}}</span> +</div> + +<div> +    <input placeholder="Password" type="password" (input)="validate(username.value, confirmPass.value, password.value)" #password> +    <progress [ngClass]="'color' + strength.getValue()" id="bar" [value]="strength | async" max="6">  </progress> +    <span *ngIf="passwordStatusText">{{passwordStatusText | async}}</span> + +    <span *ngFor="let requirement of Object.values(passwordRequirements)"> +        <span *ngIf="passwordRequirements" [style.color]="requirement.value ? 'green' : 'red'"> {{requirement.title}} </span> +    </span> +</div> + +<div> +    <input placeholder="Confirm password" type="password" (input)="validate(username.value, confirmPass.value, password.value)" #confirmPass> +    <span *ngIf="confirmPassStatusText">{{confirmPassStatusText | async}}</span> +</div> + +<div> +    <button [disabled]="!(ableToCreateAccount | async)" (click)="signup(username.value, password.value)">Create Account</button> +    <span *ngIf="showSuccessMessage | async">Account created <a routerLink="/login">Proceed to login</a></span> +    <span *ngIf="statusText | async">{{statusText | async}}</span> +</div> diff --git a/ufund-ui/src/app/components/signup/signup.component.ts b/ufund-ui/src/app/components/signup/signup.component.ts new file mode 100644 index 0000000..3b43287 --- /dev/null +++ b/ufund-ui/src/app/components/signup/signup.component.ts @@ -0,0 +1,165 @@ +import {Component} from '@angular/core'; +import {UsersService} from '../../services/users.service'; +import {Router} from '@angular/router'; +import {BehaviorSubject} from 'rxjs'; + +class PasswordRequirements { +    sixLong:    {title: string, value: boolean} = {title: 'Is 6 characters or longer' , value: false} +    twelveLong: {title: string, value: boolean} = {title: 'Is 12 characters or longer', value: false} +    lowercase:  {title: string, value: boolean} = {title: 'Includes lowercase letter' , value: false} +    uppercase:  {title: string, value: boolean} = {title: 'Includes uppercase letter' , value: false} +    number:     {title: string, value: boolean} = {title: 'Includes number'           , value: false} +    symbol:     {title: string, value: boolean} = {title: 'Includes symbol'           , value: false} +} + +@Component({ +  selector: 'app-signup', +  standalone: false, +  templateUrl: './signup.component.html', +  styleUrl: './signup.component.css' +}) + +export class SignupComponent { + +    protected passwordStatusText = new BehaviorSubject("") +    protected confirmPassStatusText = new BehaviorSubject("") +    protected usernameStatusText = new BehaviorSubject("") +    protected showSuccessMessage = new BehaviorSubject(false) +    protected passwordStrongEnough = new BehaviorSubject(false) +    protected ableToCreateAccount = new BehaviorSubject(false) +    protected passwordRequirements: PasswordRequirements = new PasswordRequirements() +    protected strength = new BehaviorSubject(0) +    protected statusText = new BehaviorSubject(""); + +    constructor( +        protected usersService: UsersService, +        protected router: Router, +    ) {} + +    signup(username: string | null, password: string | null) { +        console.log(`attempting to sign up with ${username} ${password}`) +        if (!username || !password) { +            return; +        } + +        this.usersService.createUser(username, password).then(() => { +            this.showSuccessMessage.next(true); +        }).catch(ex => { +            this.statusText.next("Unable to create account: " + friendlyHttpStatus[ex.status]) +            console.log(ex) +        }) +    } + +    validate(username: string, passConfirm:string, password: string) { +        this.confirmPassStatusText.next("") +        this.usernameStatusText.next("") +        this.checkPasswordStrength(password); + +        if (username === "") { +            this.usernameStatusText.next("Username field can't be blank") +        } + +        if (!(password === passConfirm) && !!passConfirm) { +            this.confirmPassStatusText.next("Passwords don't match") +        } +        this.ableToCreateAccount.next(this.passwordStrongEnough.getValue() && password === passConfirm && !!username) +    } + +    checkPasswordStrength(password: string) { +        this.strength.next(0) +        this.passwordRequirements = new PasswordRequirements() +        this.passwordStrongEnough.next(false) + +        if (password.match(/[^!-~]/g)) { +            this.passwordStatusText.next("Invalid characters") + +            return +        } + +        if (password.length > 6) { +            this.passwordRequirements.sixLong.value = true +        } +        if (password.length > 12) { +            this.passwordRequirements.twelveLong.value = true +        } +        if (password.match(/[a-z]/g)) { +            this.passwordRequirements.lowercase.value = true +        } +        if (password.match(/[A-Z]/g)) { +            this.passwordRequirements.uppercase.value = true +        } +        if (password.match(/[0-9]/g)) { +            this.passwordRequirements.number.value = true +        } +        if (password.match(/[^A-Za-z0-9]/g)) { +            this.passwordRequirements.symbol.value = true +        } + +        let strength = 0 +        Object.values(this.passwordRequirements).forEach(k => { +            k.value && strength++ +        }) + +        if (strength >= 5) { +            this.passwordStrongEnough.next(true) +            this.passwordStatusText.next("") +        } else if (strength == 0) { +            this.passwordStatusText.next("") +        } else { +            this.passwordStatusText.next("Password does not meet requirements") +        } + +        this.strength.next(strength) +    } + +    getColor() { +        return `rgba(${(this.strength.getValue()/7) * 255}, ${255 - (this.strength.getValue()/7) * 255}, 0)` +    } + +    protected readonly length = length; +    protected readonly Object = Object; +} + +let friendlyHttpStatus: {[key: number]: string} = { +    200: 'OK', +    201: 'Created', +    202: 'Accepted', +    203: 'Non-Authoritative Information', +    204: 'No Content', +    205: 'Reset Content', +    206: 'Partial Content', +    300: 'Multiple Choices', +    301: 'Moved Permanently', +    302: 'Found', +    303: 'See Other', +    304: 'Not Modified', +    305: 'Use Proxy', +    306: 'Unused', +    307: 'Temporary Redirect', +    400: 'Bad Request', +    401: 'Unauthorized', +    402: 'Payment Required', +    403: 'Forbidden', +    404: 'Not Found', +    405: 'Method Not Allowed', +    406: 'Not Acceptable', +    407: 'Proxy Authentication Required', +    408: 'Request Timeout', +    409: 'Conflict', +    410: 'Gone', +    411: 'Length Required', +    412: 'Precondition Required', +    413: 'Request Entry Too Large', +    414: 'Request-URI Too Long', +    415: 'Unsupported Media Type', +    416: 'Requested Range Not Satisfiable', +    417: 'Expectation Failed', +    418: 'I\'m a teapot', +    429: 'Too Many Requests', +    500: 'Internal Server Error', +    501: 'Not Implemented', +    502: 'Bad Gateway', +    503: 'Service Unavailable', +    504: 'Gateway Timeout', +    505: 'HTTP Version Not Supported', +}; diff --git a/ufund-ui/src/app/models/Need.ts b/ufund-ui/src/app/models/Need.ts index de6504a..1451cad 100644 --- a/ufund-ui/src/app/models/Need.ts +++ b/ufund-ui/src/app/models/Need.ts @@ -1,15 +1,15 @@  export interface Need { -  name: string, -  id: number, -  filterAttributes: string[], -  location: string; -  type: GoalType; -  maxGoal: number; -  current: number; -  urgent: boolean; +    name: string, +    id: number, +    filterAttributes: string[], +    location: string; +    type: GoalType; +    maxGoal: number; +    current: number; +    urgent: boolean;  }  export enum GoalType { -  MONETARY, -  PHYSICAL +    MONETARY, +    PHYSICAL  } diff --git a/ufund-ui/src/app/models/User.ts b/ufund-ui/src/app/models/User.ts index f4396f6..e6848fa 100644 --- a/ufund-ui/src/app/models/User.ts +++ b/ufund-ui/src/app/models/User.ts @@ -1,5 +1,3 @@ -import {Need} from './Need'; -  export enum userType {      HELPER,      MANAGER diff --git a/ufund-ui/src/app/services/auth.service.ts b/ufund-ui/src/app/services/auth.service.ts new file mode 100644 index 0000000..6bc7145 --- /dev/null +++ b/ufund-ui/src/app/services/auth.service.ts @@ -0,0 +1,57 @@ +import {Injectable} from '@angular/core'; +import {BehaviorSubject, firstValueFrom} from 'rxjs'; +import {User} from '../models/User'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; + +@Injectable({ +    providedIn: 'root' +}) +export class AuthService { + +    private authUrl = "http://localhost:8080/auth" +    private userUrl = "http://localhost:8080/users" + +    private currentUser : BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null); +    private apiKey: string = ""; + +    httpOptions2 = () => ({ +        headers: new HttpHeaders({ +            'Content-Type': 'application/json', +            "jelly-api-key": this.apiKey +        }), +        responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue... +        // see the relevant angular bug report https://github.com/angular/angular/issues/18586 +    }); + +    constructor( +        private http: HttpClient +    ) {} + +    async login(username: string, password: string) { +        let res = this.http.post<string>(this.authUrl, {username: username, password: password}, this.httpOptions2()); +        this.apiKey = await firstValueFrom(res); +        console.log("apikey: "+this.apiKey) +        let res2 = this.http.get<User>(`${this.userUrl}/${username}`, { +            headers: new HttpHeaders({ +                'Content-Type': 'application/json', +                "jelly-api-key": this.apiKey +            }) +        }) +        let currentU = await firstValueFrom(res2); +        this.currentUser.next(currentU); +        // this.currentUser.subscribe(r => console.log("currentUser: "+r.username)) +    } + +    getCurrentUserSubject() { +        return this.currentUser; +    } + +    getCurrentUser() { +        return this.currentUser.getValue() +    } + +    getApiKey() { +        return this.apiKey; +    } + +} diff --git a/ufund-ui/src/app/services/cupboard.service.ts b/ufund-ui/src/app/services/cupboard.service.ts index 9e14106..9232c0c 100644 --- a/ufund-ui/src/app/services/cupboard.service.ts +++ b/ufund-ui/src/app/services/cupboard.service.ts @@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';  import {HttpClient, HttpHeaders} from '@angular/common/http';  import {Need} from '../models/Need';  import {Observable} from 'rxjs'; +import {AuthService} from './auth.service';  @Injectable({      providedIn: 'root' @@ -9,35 +10,44 @@ import {Observable} from 'rxjs';  export class CupboardService {      private url = "http://localhost:8080/cupboard" -    private httpOptions = { -        headers: new HttpHeaders({'Content-Type': 'application/json'}) -    }; + +    httpOptions = () => ({ +        headers: new HttpHeaders({ +            'Content-Type': 'application/json', +            "jelly-api-key": this.authService.getApiKey() +        }) +    });      constructor( -        private http: HttpClient +        private http: HttpClient, +        private authService: AuthService      ) {}      createNeed(need: Need): Observable<boolean> { -        return this.http.post<boolean>(this.url, need, this.httpOptions) +        return this.http.post<boolean>(this.url, need, this.httpOptions())      }      getNeeds(): Observable<Need[]> { -        return this.http.get<Need[]>(this.url, this.httpOptions) +        return this.http.get<Need[]>(this.url, this.httpOptions())      }      searchNeeds(name: String): Observable<Need[]> { -        return this.http.get<Need[]>(`${this.url}/?name=${name}`, this.httpOptions) +        return this.http.get<Need[]>(`${this.url}/?name=${name}`, this.httpOptions())      }      getNeed(id: number): Observable<Need> { -        return this.http.get<Need>(`${this.url}/${id}`, this.httpOptions) +        return this.http.get<Need>(`${this.url}/${id}`, this.httpOptions())      }      updateNeed(id: number, data: Need): Observable<boolean> { -        return this.http.put<boolean>(`${this.url}/${id}`, data, this.httpOptions) +        return this.http.put<boolean>(`${this.url}/${id}`, data, this.httpOptions())      }      deleteNeed(id: number): Observable<boolean> { -        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions) +        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions()) +    } + +    checkoutNeed(id: number, quantity: number) { +        return this.http.put(`${this.url}/checkout`, {needID: id, amount: quantity}, this.httpOptions())      }  } diff --git a/ufund-ui/src/app/services/users.service.ts b/ufund-ui/src/app/services/users.service.ts index dba8185..4080ebf 100644 --- a/ufund-ui/src/app/services/users.service.ts +++ b/ufund-ui/src/app/services/users.service.ts @@ -1,96 +1,69 @@  import { Injectable } from '@angular/core';  import {HttpClient, HttpHeaders} from '@angular/common/http'; -import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs'; +import {BehaviorSubject, catchError, firstValueFrom, Observable, of} from 'rxjs';  import {User} from '../models/User';  import { Need } from '../models/Need';  import { CupboardService } from './cupboard.service'; +import {AuthService} from './auth.service';  @Injectable({    providedIn: 'root'  })  export class UsersService { -    private currentUser : BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null); -    private apiKey: string = "";      private basket = new BehaviorSubject<Need[]>([]); -      private url = "http://localhost:8080/users" -    private authUrl = "http://localhost:8080/auth" -    private httpOptions = { + +    httpOptions = () => ({          headers: new HttpHeaders({              'Content-Type': 'application/json', -            "jelly-api-key": this.apiKey +            "jelly-api-key": this.authService.getApiKey()          }) -    }; -    private httpOptions2 = { -        headers: new HttpHeaders({ -            'Content-Type': 'application/json', -            "jelly-api-key": this.apiKey -        }), -        responseType: "text" as "json" // don't ask me how or why this works, bc i have no clue... -        // see the relevant angular bug report https://github.com/angular/angular/issues/18586 -    }; +    });      constructor(          private http: HttpClient,          private cupboardService: CupboardService, +        private authService: AuthService      ) {}      async createUser(username:string, password:string) { -        await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions)) +        await firstValueFrom(this.http.post<User>(this.url, {username: username, password: password}, this.httpOptions()))      }      getUser(id: string): Observable<User> { -        return this.http.get<User>(`${this.url}/${id}`, this.httpOptions) +        return this.http.get<User>(`${this.url}/${id}`, this.httpOptions())      }      updateUser(user: User): Observable<User> { -        return this.http.put<User>(`${this.url}/${user.username}`,user, this.httpOptions2) +        console.log(`${this.url}/${user.username}`, user, this.httpOptions) +        return this.http.put<User>(`${this.url}/${user.username}`, user, this.httpOptions())      }      deleteUser(id: number): Observable<boolean> { -        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions) -    } - -    getCurrentUserSubject() { -        return this.currentUser; -    } - -    getCurrentUser() { -        return this.currentUser.getValue() -    } - -    async login(username: string, password: string) { -        let res = this.http.post<string>(this.authUrl, {username: username, password: password}, this.httpOptions2); -        this.apiKey = await firstValueFrom(res); -        console.log("apikey: "+this.apiKey) -        let res2 = this.http.get<User>(`${this.url}/${username}`, { -            headers: new HttpHeaders({ -                'Content-Type': 'application/json', -                "jelly-api-key": this.apiKey -            }) -        }) -        let currentU = await firstValueFrom(res2); -        this.currentUser.next(currentU); -        // this.currentUser.subscribe(r => console.log("currentUser: "+r.username)) +        return this.http.delete<boolean>(`${this.url}/${id}`, this.httpOptions())      }      refreshBasket() { -        let promiseArr = this.getCurrentUser()!.basket.map(async needID => { +        let promiseArr = this.authService.getCurrentUser()!.basket.map(async needID => {              return await firstValueFrom(this.cupboardService.getNeed(needID));          })          Promise.all(promiseArr).then(r => this.basket.next(r));      } -     +      removeNeed(id: number) {          let newArr = this.basket.getValue().filter(v => v.id != id);          this.basket.next(newArr); -        this.getCurrentUser()!.basket = newArr.map(need => need.id); -        this.updateUser(this.getCurrentUser()!).subscribe(() => { +        this.authService.getCurrentUser()!.basket = newArr.map(need => need.id); +        this.updateUser(this.authService.getCurrentUser()!) +            .pipe( +                catchError((err: any, _) => { +                    console.error(err); +                    return of(); +                }) +            ) +            .subscribe(() => {              this.refreshBasket(); -            error: (err: any) => { -              console.error(err); -            }            });      }  | 
