diff options
Diffstat (limited to 'ufund-ui/src/app/components')
11 files changed, 353 insertions, 156 deletions
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', +};  | 
