mirror of
https://github.com/usatiuk/writer.git
synced 2025-10-29 00:17:48 +01:00
singup
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Route, Switch } from "react-router";
|
||||
import { Login } from "~Auth/Login";
|
||||
import { Signup } from "~Auth/Signup";
|
||||
import { Home } from "~Home";
|
||||
|
||||
export function App() {
|
||||
@@ -9,6 +10,7 @@ export function App() {
|
||||
<Switch>
|
||||
<Route exact={true} path="/" component={Home} />
|
||||
<Route path="/login" component={Login} />
|
||||
<Route path="/signup" component={Signup} />
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,10 +9,16 @@
|
||||
h2 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.buttons {
|
||||
.header,
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
button.change {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
.footer {
|
||||
#error {
|
||||
height: 1rem;
|
||||
color: $pt-intent-danger;
|
||||
|
||||
@@ -3,11 +3,12 @@ import "./Auth.scss";
|
||||
import { Button, Card, FormGroup, H2, InputGroup } from "@blueprintjs/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps, withRouter } from "react-router-dom";
|
||||
import { Dispatch } from "redux";
|
||||
import { authStart } from "~redux/auth/actions";
|
||||
import { IAppState } from "~redux/reducers";
|
||||
|
||||
interface ILoginComponentProps {
|
||||
interface ILoginComponentProps extends RouteComponentProps {
|
||||
inProgress: boolean;
|
||||
error: string;
|
||||
spinner: boolean;
|
||||
@@ -26,10 +27,15 @@ export class LoginComponent extends React.PureComponent<
|
||||
constructor(props: ILoginComponentProps) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.change = this.change.bind(this);
|
||||
this.updateFields = this.updateFields.bind(this);
|
||||
this.state = { username: "", password: "" };
|
||||
}
|
||||
|
||||
public change() {
|
||||
this.props.history.push("/signup");
|
||||
}
|
||||
|
||||
public submit() {
|
||||
const { username, password } = this.state;
|
||||
if (!this.props.inProgress) {
|
||||
@@ -47,7 +53,17 @@ export class LoginComponent extends React.PureComponent<
|
||||
<>
|
||||
<Card className="AuthForm" elevation={2}>
|
||||
<form>
|
||||
<H2>Login</H2>
|
||||
<div className="header">
|
||||
<H2>Login</H2>
|
||||
<Button
|
||||
icon="new-person"
|
||||
minimal={true}
|
||||
onClick={this.change}
|
||||
className="change"
|
||||
>
|
||||
Signup
|
||||
</Button>
|
||||
</div>
|
||||
<FormGroup label="Username">
|
||||
<InputGroup
|
||||
name="username"
|
||||
@@ -65,12 +81,13 @@ export class LoginComponent extends React.PureComponent<
|
||||
leftIcon="key"
|
||||
/>
|
||||
</FormGroup>
|
||||
<div className="buttons">
|
||||
<div className="footer">
|
||||
<div id="error">{this.props.error}</div>
|
||||
<Button
|
||||
loading={this.props.spinner}
|
||||
className="submit"
|
||||
intent="primary"
|
||||
icon="log-in"
|
||||
onClick={this.submit}
|
||||
disabled={this.props.spinner}
|
||||
>
|
||||
@@ -99,7 +116,9 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
||||
};
|
||||
}
|
||||
|
||||
export const Login = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(LoginComponent);
|
||||
export const Login = withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(LoginComponent),
|
||||
);
|
||||
|
||||
135
frontend/src/Auth/Signup.tsx
Normal file
135
frontend/src/Auth/Signup.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import "./Auth.scss";
|
||||
|
||||
import { Button, Card, FormGroup, H2, InputGroup } from "@blueprintjs/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps, withRouter } from "react-router";
|
||||
import { Dispatch } from "redux";
|
||||
import { signupStart } from "~redux/auth/actions";
|
||||
import { IAppState } from "~redux/reducers";
|
||||
|
||||
interface ISignupComponentProps extends RouteComponentProps {
|
||||
inProgress: boolean;
|
||||
error: string;
|
||||
spinner: boolean;
|
||||
signup: (username: string, password: string, email: string) => void;
|
||||
}
|
||||
|
||||
interface ISignupComponentState {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class SignupComponent extends React.PureComponent<
|
||||
ISignupComponentProps,
|
||||
ISignupComponentState
|
||||
> {
|
||||
constructor(props: ISignupComponentProps) {
|
||||
super(props);
|
||||
this.submit = this.submit.bind(this);
|
||||
this.change = this.change.bind(this);
|
||||
this.updateFields = this.updateFields.bind(this);
|
||||
this.state = { username: "", password: "", email: "" };
|
||||
}
|
||||
|
||||
public change() {
|
||||
this.props.history.push("/login");
|
||||
}
|
||||
|
||||
public submit() {
|
||||
const { username, password, email } = this.state;
|
||||
if (!this.props.inProgress) {
|
||||
this.props.signup(username, password, email);
|
||||
}
|
||||
}
|
||||
|
||||
public updateFields(e: React.FormEvent<HTMLInputElement>) {
|
||||
const { value, name } = e.currentTarget;
|
||||
this.setState({ ...this.state, [name]: value });
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Card className="AuthForm" elevation={2}>
|
||||
<form>
|
||||
<div className="header">
|
||||
<H2>Signup</H2>
|
||||
<Button
|
||||
icon="log-in"
|
||||
minimal={true}
|
||||
onClick={this.change}
|
||||
className="change"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
<FormGroup label="Username">
|
||||
<InputGroup
|
||||
name="username"
|
||||
value={this.state.username}
|
||||
onChange={this.updateFields}
|
||||
leftIcon="person"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Email">
|
||||
<InputGroup
|
||||
name="email"
|
||||
value={this.state.email}
|
||||
onChange={this.updateFields}
|
||||
type="email"
|
||||
leftIcon="envelope"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Password">
|
||||
<InputGroup
|
||||
name="password"
|
||||
value={this.state.password}
|
||||
onChange={this.updateFields}
|
||||
type="password"
|
||||
leftIcon="key"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<div className="footer">
|
||||
<div id="error">{this.props.error}</div>
|
||||
<Button
|
||||
loading={this.props.spinner}
|
||||
icon="new-person"
|
||||
className="submit"
|
||||
intent="primary"
|
||||
onClick={this.submit}
|
||||
disabled={this.props.spinner}
|
||||
>
|
||||
Signup
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IAppState) {
|
||||
return {
|
||||
inProgress: state.auth.inProgress,
|
||||
error: state.auth.formError,
|
||||
spinner: state.auth.formSpinner,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
signup: (username: string, password: string, email: string) =>
|
||||
dispatch(signupStart(username, password, email)),
|
||||
};
|
||||
}
|
||||
|
||||
export const Signup = withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(SignupComponent),
|
||||
);
|
||||
@@ -6,3 +6,15 @@ export async function login(username: string, password: string) {
|
||||
password,
|
||||
});
|
||||
}
|
||||
|
||||
export async function signup(
|
||||
username: string,
|
||||
password: string,
|
||||
email: string,
|
||||
) {
|
||||
return fetchJSON("/users/signup", "POST", {
|
||||
username,
|
||||
password,
|
||||
email,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Action } from "redux";
|
||||
|
||||
export enum AuthTypes {
|
||||
AUTH_START = "AUTH_START",
|
||||
SIGNUP_START = "SIGNUP_START",
|
||||
AUTH_SUCCESS = "AUTH_SUCCESS",
|
||||
AUTH_FAIL = "AUTH_FAIL",
|
||||
AUTH_START_FORM_SPINNER = "AUTH_START_FORM_SPINNER",
|
||||
@@ -15,6 +16,15 @@ export interface IAuthStartActionAction extends Action {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ISignupStartActionAction extends Action {
|
||||
type: AuthTypes.SIGNUP_START;
|
||||
payload: {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IAuthSuccessActionAction extends Action {
|
||||
type: AuthTypes.AUTH_SUCCESS;
|
||||
payload: {
|
||||
@@ -44,6 +54,17 @@ export function authStart(
|
||||
return { type: AuthTypes.AUTH_START, payload: { username, password } };
|
||||
}
|
||||
|
||||
export function signupStart(
|
||||
username: string,
|
||||
password: string,
|
||||
email: string,
|
||||
): ISignupStartActionAction {
|
||||
return {
|
||||
type: AuthTypes.SIGNUP_START,
|
||||
payload: { username, password, email },
|
||||
};
|
||||
}
|
||||
|
||||
export function authSuccess(jwt: string): IAuthSuccessActionAction {
|
||||
return { type: AuthTypes.AUTH_SUCCESS, payload: { jwt } };
|
||||
}
|
||||
@@ -56,4 +77,5 @@ export type AuthAction =
|
||||
| IAuthStartActionAction
|
||||
| IAuthSuccessActionAction
|
||||
| IAuthFailureActionAction
|
||||
| IAuthStartFormSpinnerAction;
|
||||
| IAuthStartFormSpinnerAction
|
||||
| ISignupStartActionAction;
|
||||
|
||||
@@ -22,6 +22,7 @@ export const auth: Reducer<IAuthState, AuthAction> = (
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case AuthTypes.AUTH_START:
|
||||
case AuthTypes.SIGNUP_START:
|
||||
return { ...defaultAuthState, inProgress: true };
|
||||
break;
|
||||
case AuthTypes.AUTH_SUCCESS:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { delay } from "redux-saga";
|
||||
import { call, cancel, fork, put, race, takeLatest } from "redux-saga/effects";
|
||||
import { login } from "~redux/api/auth";
|
||||
import {
|
||||
all,
|
||||
call,
|
||||
cancel,
|
||||
fork,
|
||||
put,
|
||||
race,
|
||||
takeLatest,
|
||||
} from "redux-saga/effects";
|
||||
import { login, signup } from "~redux/api/auth";
|
||||
import { setToken } from "~redux/api/utils";
|
||||
|
||||
import {
|
||||
@@ -8,6 +16,7 @@ import {
|
||||
authSuccess,
|
||||
AuthTypes,
|
||||
IAuthStartActionAction,
|
||||
ISignupStartActionAction,
|
||||
startFormSpinner,
|
||||
} from "./actions";
|
||||
|
||||
@@ -44,6 +53,37 @@ function* authStart(action: IAuthStartActionAction) {
|
||||
}
|
||||
}
|
||||
|
||||
export function* authSaga() {
|
||||
yield takeLatest(AuthTypes.AUTH_START, authStart);
|
||||
function* signupStart(action: ISignupStartActionAction) {
|
||||
const { username, password, email } = action.payload;
|
||||
try {
|
||||
const spinner = yield fork(startSpinner);
|
||||
|
||||
const { response, timeout } = yield race({
|
||||
response: call(signup, username, password, email),
|
||||
timeout: call(delay, 10000),
|
||||
});
|
||||
|
||||
yield cancel(spinner);
|
||||
|
||||
if (timeout) {
|
||||
yield put(authFail("Timeout"));
|
||||
return;
|
||||
}
|
||||
if (response.data) {
|
||||
const user = response.data;
|
||||
yield call(setToken, user.jwt);
|
||||
yield put(authSuccess(user.jwt));
|
||||
} else {
|
||||
yield put(authFail(response.error));
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(authFail(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
export function* authSaga() {
|
||||
yield all([
|
||||
takeLatest(AuthTypes.AUTH_START, authStart),
|
||||
takeLatest(AuthTypes.SIGNUP_START, signupStart),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user