diff options
Diffstat (limited to 'ui/src/components')
-rw-r--r-- | ui/src/components/create-community.tsx | 90 | ||||
-rw-r--r-- | ui/src/components/create-post.tsx | 57 | ||||
-rw-r--r-- | ui/src/components/login.tsx | 57 | ||||
-rw-r--r-- | ui/src/components/navbar.tsx | 54 |
4 files changed, 230 insertions, 28 deletions
diff --git a/ui/src/components/create-community.tsx b/ui/src/components/create-community.tsx new file mode 100644 index 00000000..dbacd18d --- /dev/null +++ b/ui/src/components/create-community.tsx @@ -0,0 +1,90 @@ +import { Component, linkEvent } from 'inferno'; +import { Subscription } from "rxjs"; +import { retryWhen, delay, take } from 'rxjs/operators'; +import { CommunityForm, UserOperation } from '../interfaces'; +import { WebSocketService, UserService } from '../services'; +import { msgOp } from '../utils'; + +interface State { + communityForm: CommunityForm; +} + +let emptyState: State = { + communityForm: { + name: null, + } +} + +export class CreateCommunity extends Component<any, State> { + private subscription: Subscription; + + constructor(props, context) { + super(props, context); + + this.state = emptyState; + + this.subscription = WebSocketService.Instance.subject + .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) + .subscribe( + (msg) => this.parseMessage(msg), + (err) => console.error(err), + ); + } + + componentWillUnmount() { + this.subscription.unsubscribe(); + } + + render() { + return ( + <div class="container"> + <div class="row"> + <div class="col-12 col-lg-6 mb-4"> + {this.communityForm()} + </div> + </div> + </div> + ) + } + + communityForm() { + return ( + <div> + <form onSubmit={linkEvent(this, this.handleCreateCommunitySubmit)}> + <h3>Create Forum</h3> + <div class="form-group row"> + <label class="col-sm-2 col-form-label">Name</label> + <div class="col-sm-10"> + <input type="text" class="form-control" value={this.state.communityForm.name} onInput={linkEvent(this, this.handleCommunityNameChange)} required minLength={3} /> + </div> + </div> + <div class="form-group row"> + <div class="col-sm-10"> + <button type="submit" class="btn btn-secondary">Create</button> + </div> + </div> + </form> + </div> + ); + } + + handleCreateCommunitySubmit(i: CreateCommunity, event) { + event.preventDefault(); + WebSocketService.Instance.createCommunity(i.state.communityForm); + } + + handleCommunityNameChange(i: CreateCommunity, event) { + i.state.communityForm.name = event.target.value; + i.setState(i.state); + } + + parseMessage(msg: any) { + let op: UserOperation = msgOp(msg); + if (msg.error) { + alert(msg.error); + return; + } else { + } + } + +} diff --git a/ui/src/components/create-post.tsx b/ui/src/components/create-post.tsx new file mode 100644 index 00000000..bb6e60e2 --- /dev/null +++ b/ui/src/components/create-post.tsx @@ -0,0 +1,57 @@ +import { Component, linkEvent } from 'inferno'; + +import { LoginForm, PostForm, UserOperation } from '../interfaces'; +import { WebSocketService, UserService } from '../services'; +import { msgOp } from '../utils'; + +interface State { + postForm: PostForm; +} + +let emptyState: State = { + postForm: { + name: null, + url: null, + attributed_to: null + } +} + +export class CreatePost extends Component<any, State> { + + constructor(props, context) { + super(props, context); + + this.state = emptyState; + + WebSocketService.Instance.subject.subscribe( + (msg) => this.parseMessage(msg), + (err) => console.error(err), + () => console.log('complete') + ); + } + + + render() { + return ( + <div class="container"> + <div class="row"> + <div class="col-12 col-lg-6 mb-4"> + create post + {/* {this.postForm()} */} + </div> + </div> + </div> + ) + } + + parseMessage(msg: any) { + console.log(msg); + let op: UserOperation = msgOp(msg); + if (msg.error) { + alert(msg.error); + return; + } else { + } + } + +} diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index fd6f5045..372b1557 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -1,7 +1,9 @@ import { Component, linkEvent } from 'inferno'; - -import { LoginForm, RegisterForm } from '../interfaces'; -import { WebSocketService } from '../services'; +import { Subscription } from "rxjs"; +import { retryWhen, delay, take } from 'rxjs/operators'; +import { LoginForm, RegisterForm, UserOperation } from '../interfaces'; +import { WebSocketService, UserService } from '../services'; +import { msgOp } from '../utils'; interface State { loginForm: LoginForm; @@ -10,24 +12,36 @@ interface State { let emptyState: State = { loginForm: { - username: null, - password: null + username_or_email: undefined, + password: undefined }, registerForm: { - username: null, - password: null, - password_verify: null + username: undefined, + password: undefined, + password_verify: undefined } } export class Login extends Component<any, State> { + private subscription: Subscription; constructor(props, context) { super(props, context); this.state = emptyState; + this.subscription = WebSocketService.Instance.subject + .pipe(retryWhen(errors => errors.pipe(delay(3000), take(10)))) + .subscribe( + (msg) => this.parseMessage(msg), + (err) => console.error(err), + ); + } + + componentWillUnmount() { + this.subscription.unsubscribe(); } + render() { return ( <div class="container"> @@ -51,7 +65,7 @@ export class Login extends Component<any, State> { <div class="form-group row"> <label class="col-sm-2 col-form-label">Email or Username</label> <div class="col-sm-10"> - <input type="text" class="form-control" value={this.state.loginForm.username} onInput={linkEvent(this, this.handleLoginUsernameChange)} required minLength={3} /> + <input type="text" class="form-control" value={this.state.loginForm.username_or_email} onInput={linkEvent(this, this.handleLoginUsernameChange)} required minLength={3} /> </div> </div> <div class="form-group row"> @@ -108,38 +122,55 @@ export class Login extends Component<any, State> { } handleLoginSubmit(i: Login, event) { - console.log(i.state); event.preventDefault(); WebSocketService.Instance.login(i.state.loginForm); } handleLoginUsernameChange(i: Login, event) { - i.state.loginForm.username = event.target.value; + i.state.loginForm.username_or_email = event.target.value; + i.setState(i.state); } handleLoginPasswordChange(i: Login, event) { i.state.loginForm.password = event.target.value; + i.setState(i.state); } handleRegisterSubmit(i: Login, event) { - console.log(i.state); event.preventDefault(); WebSocketService.Instance.register(i.state.registerForm); } handleRegisterUsernameChange(i: Login, event) { i.state.registerForm.username = event.target.value; + i.setState(i.state); } handleRegisterEmailChange(i: Login, event) { i.state.registerForm.email = event.target.value; + i.setState(i.state); } handleRegisterPasswordChange(i: Login, event) { i.state.registerForm.password = event.target.value; + i.setState(i.state); } - + handleRegisterPasswordVerifyChange(i: Login, event) { i.state.registerForm.password_verify = event.target.value; + i.setState(i.state); + } + + parseMessage(msg: any) { + let op: UserOperation = msgOp(msg); + if (msg.error) { + alert(msg.error); + return; + } else { + if (op == UserOperation.Register || op == UserOperation.Login) { + UserService.Instance.login(msg.jwt); + this.props.history.push('/'); + } + } } } diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx index 86d5d1d2..4cf6d6d2 100644 --- a/ui/src/components/navbar.tsx +++ b/ui/src/components/navbar.tsx @@ -1,38 +1,62 @@ import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { repoUrl } from '../utils'; +import { UserService } from '../services'; export class Navbar extends Component<any, any> { constructor(props, context) { super(props, context); + this.state = {isLoggedIn: UserService.Instance.loggedIn}; + + // Subscribe to user changes + UserService.Instance.sub.subscribe(user => { + let loggedIn: boolean = user !== null; + this.setState({isLoggedIn: loggedIn}); + }); } render() { return ( - <div class="sticky-top">{this.navbar()}</div> + <div>{this.navbar()}</div> ) } // TODO class active corresponding to current page + // TODO toggle css collapse navbar() { return ( - <nav class="navbar navbar-light bg-light p-0 px-3 shadow"> - <a class="navbar-brand mx-1" href="#"> - rrf - </a> - <ul class="navbar-nav mr-auto"> - <li class="nav-item"> - <a class="nav-item nav-link" href={repoUrl}>github</a> - </li> - </ul> - <ul class="navbar-nav ml-auto mr-2"> - <li class="nav-item"> - <Link class="nav-item nav-link" to="/login">Login</Link> - </li> - </ul> + <nav class="navbar navbar-expand-sm navbar-light bg-light p-0 px-3 shadow"> + <a class="navbar-brand" href="#">rrf</a> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse"> + <ul class="navbar-nav mr-auto"> + <li class="nav-item"> + <a class="nav-link" href={repoUrl}>github</a> + </li> + <li class="nav-item"> + <Link class="nav-link" to="/create_post">Create Post</Link> + </li> + <li class="nav-item"> + <Link class="nav-link" to="/create_community">Create Forum</Link> + </li> + </ul> + <ul class="navbar-nav ml-auto mr-2"> + <li class="nav-item"> + {this.state.isLoggedIn ? + <a role="button" class="nav-link pointer" onClick={ linkEvent(this, this.handleLogoutClick) }>Logout</a> : + <Link class="nav-link" to="/login">Login</Link> + } + </li> + </ul> + </div> </nav> ); } + handleLogoutClick(i: Navbar, event) { + UserService.Instance.logout(); + } } |