diff options
Diffstat (limited to 'ufund-ui/src/app/components')
4 files changed, 239 insertions, 2 deletions
diff --git a/ufund-ui/src/app/components/login/login.component.html b/ufund-ui/src/app/components/login/login.component.html index e04ec23..743b1b3 100644 --- a/ufund-ui/src/app/components/login/login.component.html +++ b/ufund-ui/src/app/components/login/login.component.html @@ -5,8 +5,7 @@ <input placeholder="Password" type="password" #password> <button type="button" (click)="login(username.value, password.value)">Login</button> <div> - New? <a href="/">Create an account</a> + New? <a routerLink="/signup">Create an account</a> </div> -<!-- <button type="button" (click)="signup(username.value, password.value)">Create Account</button>--> <span *ngIf="statusText">{{statusText | async}}</span> </div> 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', +}; |