Browse Source

Adding login and Register

- Login and Register  mostly working.
- Starting to work on creating communities.
main
Dessalines 4 years ago
parent
commit
2adfebe597
  1. 6
      package.json
  2. 90
      src/components/create-community.tsx
  3. 57
      src/components/create-post.tsx
  4. 57
      src/components/login.tsx
  5. 54
      src/components/navbar.tsx
  6. 7
      src/index.tsx
  7. 24
      src/interfaces.ts
  8. 5
      src/main.css
  9. 57
      src/services.ts
  10. 51
      src/services/UserService.ts
  11. 37
      src/services/WebSocketService.ts
  12. 2
      src/services/index.ts
  13. 7
      src/utils.ts
  14. 24
      yarn.lock

6
package.json

@ -15,11 +15,15 @@
},
"engineStrict": true,
"dependencies": {
"@types/js-cookie": "^2.2.1",
"classcat": "^1.1.3",
"dotenv": "^6.1.0",
"inferno": "^7.0.1",
"inferno-router": "^7.0.1",
"moment": "^2.22.2"
"js-cookie": "^2.2.0",
"jwt-decode": "^2.2.0",
"moment": "^2.22.2",
"rxjs": "^6.4.0"
},
"devDependencies": {
"fuse-box": "3.1.3",

90
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 {
}
}
}

57
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 {
}
}
}

57
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('/');
}
}
}
}

54
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();
}
}

7
src/index.tsx

@ -4,10 +4,12 @@ import { HashRouter, Route, Switch } from 'inferno-router';
import { Navbar } from './components/navbar';
import { Home } from './components/home';
import { Login } from './components/login';
import { CreatePost } from './components/create-post';
import { CreateCommunity } from './components/create-community';
import './main.css';
import { WebSocketService } from './services';
import { WebSocketService, UserService } from './services';
const container = document.getElementById('app');
@ -16,6 +18,7 @@ class Index extends Component<any, any> {
constructor(props, context) {
super(props, context);
WebSocketService.Instance;
UserService.Instance;
}
render() {
@ -26,6 +29,8 @@ class Index extends Component<any, any> {
<Switch>
<Route exact path="/" component={Home} />
<Route path={`/login`} component={Login} />
<Route path={`/create_post`} component={CreatePost} />
<Route path={`/create_community`} component={CreateCommunity} />
{/*
<Route path={`/search/:type_/:q/:page`} component={Search} />
<Route path={`/submit`} component={Submit} />

24
src/interfaces.ts

@ -1,7 +1,17 @@
export interface LoginForm {
export enum UserOperation {
Login, Register, CreateCommunity
}
export interface User {
id: number
username: string;
}
export interface LoginForm {
username_or_email: string;
password: string;
}
export interface RegisterForm {
username: string;
email?: string;
@ -9,6 +19,14 @@ export interface RegisterForm {
password_verify: string;
}
export enum UserOperation {
Login, Register
export interface CommunityForm {
name: string;
updated?: number
}
export interface PostForm {
name: string;
url: string;
attributed_to: string;
updated?: number
}

5
src/main.css

@ -0,0 +1,5 @@
.pointer {
cursor: pointer;
}

57
src/services.ts

@ -1,57 +0,0 @@
import { wsUri } from './env';
import { LoginForm, RegisterForm, UserOperation } from './interfaces';
export class WebSocketService {
private static _instance: WebSocketService;
private _ws;
private conn: WebSocket;
private constructor() {
console.log("Creating WSS");
this.connect();
console.log(wsUri);
}
public static get Instance(){
return this._instance || (this._instance = new this());
}
private connect() {
this.disconnect();
this.conn = new WebSocket(wsUri);
console.log('Connecting...');
this.conn.onopen = (() => {
console.log('Connected.');
});
this.conn.onmessage = (e => {
console.log('Received: ' + e.data);
});
this.conn.onclose = (() => {
console.log('Disconnected.');
this.conn = null;
});
}
private disconnect() {
if (this.conn != null) {
console.log('Disconnecting...');
this.conn.close();
this.conn = null;
}
}
public login(loginForm: LoginForm) {
this.conn.send(this.wsSendWrapper(UserOperation.Login, loginForm));
}
public register(registerForm: RegisterForm) {
this.conn.send(this.wsSendWrapper(UserOperation.Register, registerForm));
}
private wsSendWrapper(op: UserOperation, data: any): string {
let send = { op: UserOperation[op], data: data };
console.log(send);
return JSON.stringify(send);
}
}

51
src/services/UserService.ts

@ -0,0 +1,51 @@
import * as Cookies from 'js-cookie';
import { User } from '../interfaces';
import * as jwt_decode from 'jwt-decode';
import { Subject } from 'rxjs';
export class UserService {
private static _instance: UserService;
private user: User;
public sub: Subject<User> = new Subject<User>();
private constructor() {
let jwt = Cookies.get("jwt");
if (jwt) {
this.setUser(jwt);
} else {
console.log('No JWT cookie found.');
}
}
public login(jwt: string) {
Cookies.set("jwt", jwt);
console.log("jwt cookie set");
this.setUser(jwt);
}
public logout() {
this.user = null;
Cookies.remove("jwt");
console.log("Logged out.");
this.sub.next(null);
}
public get loggedIn(): boolean {
return this.user !== undefined;
}
public get auth(): string {
return Cookies.get("jwt");
}
private setUser(jwt: string) {
this.user = jwt_decode(jwt);
this.sub.next(this.user);
console.log(this.user.username);
}
public static get Instance(){
return this._instance || (this._instance = new this());
}
}

37
src/services/WebSocketService.ts

@ -0,0 +1,37 @@
import { wsUri } from '../env';
import { LoginForm, RegisterForm, UserOperation, CommunityForm } from '../interfaces';
import { webSocket } from 'rxjs/webSocket';
import { Subject } from 'rxjs';
import { UserService } from './';
export class WebSocketService {
private static _instance: WebSocketService;
public subject: Subject<{}>;
private constructor() {
this.subject = webSocket(wsUri);
console.log(`Connected to ${wsUri}`);
}
public static get Instance(){
return this._instance || (this._instance = new this());
}
public login(loginForm: LoginForm) {
this.subject.next(this.wsSendWrapper(UserOperation.Login, loginForm));
}
public register(registerForm: RegisterForm) {
this.subject.next(this.wsSendWrapper(UserOperation.Register, registerForm));
}
public createCommunity(communityForm: CommunityForm) {
this.subject.next(this.wsSendWrapper(UserOperation.CreateCommunity, communityForm, UserService.Instance.auth));
}
private wsSendWrapper(op: UserOperation, data: any, auth?: string) {
let send = { op: UserOperation[op], data: data, auth: auth };
console.log(send);
return send;
}
}

2
src/services/index.ts

@ -0,0 +1,2 @@
export { UserService } from './UserService';
export { WebSocketService } from './WebSocketService';

7
src/utils.ts

@ -1,2 +1,9 @@
import { UserOperation } from './interfaces';
export let repoUrl = 'https://github.com/dessalines/rust-reddit-fediverse';
export let wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/service/ws/';
export function msgOp(msg: any): UserOperation {
let opStr: string = msg.op;
return UserOperation[opStr];
}

24
yarn.lock

@ -9,6 +9,11 @@
dependencies:
regenerator-runtime "^0.12.0"
"@types/[email protected]^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.1.tgz#aa6f6d5e5aaf7d97959e9fa938ac2501cf1a76f4"
integrity sha512-VIVurImEhQ95jxtjs8baVU5qCzVfwYfuMrpXwdRykJ5MCI5iY7/jB4cDSgwBVeYqeXrhT7GfJUwoDOmN0OMVCA==
[email protected]:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@ -1456,6 +1461,11 @@ [email protected]~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
[email protected]^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb"
integrity sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s=
"[email protected]^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -1503,6 +1513,11 @@ [email protected]^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
[email protected]^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
[email protected]^3.0.2, [email protected]^3.0.3, [email protected]^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@ -2446,6 +2461,13 @@ [email protected]*, [email protected]^4.0.8:
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
integrity sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=
[email protected]^6.4.0:
version "6.4.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504"
integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw==
dependencies:
tslib "^1.9.0"
[email protected], [email protected]^5.0.1, [email protected]^5.1.2, [email protected]~5.1.0, [email protected]~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@ -2824,7 +2846,7 @@ [email protected]^4.0.2:
resolved "https://registry.yarnpkg.com/ts-transform-inferno/-/ts-transform-inferno-4.0.2.tgz#06b9be45edf874ba7a6ebfb6107ba782509c6afe"
integrity sha512-CZb4+w/2l2zikPZ/c51fi3n+qnR2HCEfAS73oGQB80aqRLffkZqm25kYYTMmqUW2+oVfs4M5AZa0z14cvxlQ5w==
[email protected]^1.8.0:
[email protected]^1.8.0, [email protected]^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==

Loading…
Cancel
Save