diff options
| author | Tyler Ferrari <69283684+Sowgro@users.noreply.github.com> | 2025-03-30 14:12:41 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-30 14:12:41 -0400 | 
| commit | 0c793d302c5065085ff7982a68f7ed449d84d6dc (patch) | |
| tree | fbbf2c808300c395caddccb0a929c7500453be84 | |
| parent | ddbd1cc688aa98fb275ad72a750fbaaf53e6c0ae (diff) | |
| parent | 3c9a9004780c0b91772fd7f868c642bdadb60348 (diff) | |
| download | JellySolutions-0c793d302c5065085ff7982a68f7ed449d84d6dc.tar.gz JellySolutions-0c793d302c5065085ff7982a68f7ed449d84d6dc.tar.bz2 JellySolutions-0c793d302c5065085ff7982a68f7ed449d84d6dc.zip  | |
Merge pull request #19 from RIT-SWEN-261-02/signup-page
signup-page merge
7 files changed, 245 insertions, 3 deletions
diff --git a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java index 55ee457..bbfd3f6 100644 --- a/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java +++ b/ufund-api/src/main/java/com/ufund/api/ufundapi/controller/CupboardController.java @@ -182,7 +182,7 @@ public class CupboardController {      /**       * Checks out a need by checkoutAmount       *  -     * @param data JSON object with paramters needID and amount +     * @param data JSON object with parameters needID and amount       * @param key  Key used to authenticate user       * @return OK if successful, other statuses if failure       */ 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.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/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/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', +};  | 
