From 491aacb0df7af9e3c5df576f10dc80d08efd9a95 Mon Sep 17 00:00:00 2001 From: stpoa Date: Thu, 7 Nov 2019 11:04:48 +0100 Subject: [PATCH 1/4] Convert signup to hooks --- .../client/src/pages/Register/Register.tsx | 215 +++++++----------- 1 file changed, 84 insertions(+), 131 deletions(-) diff --git a/packages/client/src/pages/Register/Register.tsx b/packages/client/src/pages/Register/Register.tsx index d05db40..395c064 100644 --- a/packages/client/src/pages/Register/Register.tsx +++ b/packages/client/src/pages/Register/Register.tsx @@ -6,10 +6,10 @@ import { } from '@notowork/lib/validators' import { Status } from '@notowork/models/interfaces' import React, { - ChangeEvent, ChangeEventHandler, - Component, + FC, MouseEventHandler, + useState, } from 'react' import { withAuth, WithAuth } from '~auth' import { @@ -23,129 +23,96 @@ import { } from '~generic/Sign' import Switch from './Switch' -class Register extends Component { - public readonly state: RegisterState = { - email: '', - emailError: '', - password: '', - passwordError: '', - showSignInError: false, - acceptDataProcessingTerms: false, - acceptError: '', - acceptTermsOfService: false, - } +const Register: FC = ({ auth: { status }, classes }) => { + const [email, setEmail] = useState('') + const [emailError, setEmailError] = useState('') + const [password, setPassword] = useState('') + const [passwordError, setPasswordError] = useState('') + // const [showSignInError, setShowSignInError] = useState(false) + const [acceptDataProcessingTerms, setAcceptDataProcessingTerms] = useState( + false, + ) + const [_, setAcceptError] = useState('') + const [acceptTermsOfService, setAcceptTermsOfService] = useState(false) - public render() { - const { - auth: { status }, - classes, - } = this.props - const { - acceptDataProcessingTerms, - acceptTermsOfService, - email, - emailError, - password, - passwordError, - } = this.state - const isPending = status === Status.Pending + const isPending = status === Status.Pending - return ( - - - - - - - - Hasło powinno zawierać minimum 6 znaków, w tym jedną wielką literę i - cyfrę - - -
- - Akceptuję regulamin. - - } - checked={acceptTermsOfService} - disabled={isPending} - onChange={this.handleChangeCheckbox('acceptTermsOfService')} - name="acceptTermsOfService" - /> - -
- - -
+ const handleSubmit: MouseEventHandler = () => { + setEmailError(emailValidator(email)) + setPasswordError(passwordValidator(password)) + setAcceptError( + requiredValidator(acceptTermsOfService) || + requiredValidator(acceptDataProcessingTerms) || + '', ) } - private handleSubmit: MouseEventHandler = () => { - this.setState(state => { - const errors = { - emailError: emailValidator(state.email), - passwordError: passwordValidator(state.password), - acceptError: - requiredValidator(state.acceptTermsOfService) || - requiredValidator(state.acceptDataProcessingTerms) || - '', - } - - const hasErrors = Object.values(errors).some(error => - Boolean(error.length), - ) - - if (hasErrors) return { ...errors } - - return null - }) + const handleChangePassword: ChangeEventHandler = e => { + setPassword(e.target.value) + setPasswordError('') } - - private handleChangeText: ChangeEventHandler = e => { - const state = { - [e.target.name]: e.target.value, - emailError: '', - passwordError: '', - } as any - - // https://github.com/Microsoft/TypeScript/issues/13948 - this.setState(state) + const handleChangeEmail: ChangeEventHandler = e => { + setEmail(e.target.value) + setEmailError('') } - private handleChangeCheckbox = ( - name: 'acceptDataProcessingTerms' | 'acceptTermsOfService', - ) => (_: ChangeEvent, checked: boolean) => { - switch (name) { - case 'acceptDataProcessingTerms': - return this.setState({ [name]: checked }) - case 'acceptTermsOfService': - return this.setState({ [name]: checked }) - } - } + const handleAcceptDataTerms = (_: any, checked: boolean) => + setAcceptDataProcessingTerms(checked) + const handleAcceptServiceTerms = (_: any, checked: boolean) => + setAcceptTermsOfService(checked) + + return ( + + + + + + + + Hasło powinno zawierać minimum 6 znaków, w tym jedną wielką literę i + cyfrę + + +
+ + Akceptuję regulamin. + + } + checked={acceptTermsOfService} + disabled={isPending} + onChange={handleAcceptServiceTerms} + name="acceptTermsOfService" + /> + +
+ + +
+ ) } const styles = { @@ -163,18 +130,4 @@ const styles = { interface RegisterProps extends WithAuth, WithStyles {} -interface Errors { - emailError: string - passwordError: string -} - -interface RegisterState extends Errors { - email: string - password: string - showSignInError: boolean - acceptDataProcessingTerms: boolean - acceptError: string - acceptTermsOfService: boolean -} - export default withAuth(withStyles(styles)(Register)) From 4999d1bd298f5e4a563e29e0077d9bf7c36beddf Mon Sep 17 00:00:00 2001 From: stpoa Date: Thu, 7 Nov 2019 11:18:21 +0100 Subject: [PATCH 2/4] Add global test script --- notes.txt | 2 ++ package.json | 9 ++++++--- packages/client/src/pages/Register/Register.tsx | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 notes.txt diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..92af249 --- /dev/null +++ b/notes.txt @@ -0,0 +1,2 @@ +~/W/s/notowork   feature/start-whole-app +-> Zmiany diff --git a/package.json b/package.json index 197edb2..f3076d6 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,13 @@ "lib": "lib" }, "scripts": { - "start": "run-p server client", + "start": "run-p server:start client:start", + "test": "run-p server:test client:test", "build": "lerna run build", - "server": "lerna run start --scope @notowork/server", - "client": "lerna run start --scope @notowork/client", + "server:start": "lerna run start --scope @notowork/server", + "client:start": "lerna run start --scope @notowork/client", + "server:test": "lerna run test --scope @notowork/server", + "client:test": "lerna run test --scope @notowork/client", "init": "npm run bootstrap", "bootstrap": "lerna bootstrap", "lerna": "lerna", diff --git a/packages/client/src/pages/Register/Register.tsx b/packages/client/src/pages/Register/Register.tsx index 395c064..b3e7f0e 100644 --- a/packages/client/src/pages/Register/Register.tsx +++ b/packages/client/src/pages/Register/Register.tsx @@ -32,7 +32,7 @@ const Register: FC = ({ auth: { status }, classes }) => { const [acceptDataProcessingTerms, setAcceptDataProcessingTerms] = useState( false, ) - const [_, setAcceptError] = useState('') + const [, setAcceptError] = useState('') const [acceptTermsOfService, setAcceptTermsOfService] = useState(false) const isPending = status === Status.Pending From 1f78be03c06b7a29cb9d20cbaf9f78435d8d3542 Mon Sep 17 00:00:00 2001 From: stpoa Date: Thu, 7 Nov 2019 12:12:41 +0100 Subject: [PATCH 3/4] Convert signin to hooks --- packages/client/src/auth/Provider.tsx | 9 +- packages/client/src/auth/context.ts | 9 +- packages/client/src/auth/withAuth.tsx | 9 +- packages/client/src/pages/SignIn/SignIn.tsx | 216 +++++++------------- 4 files changed, 92 insertions(+), 151 deletions(-) diff --git a/packages/client/src/auth/Provider.tsx b/packages/client/src/auth/Provider.tsx index 46b0524..bfd9fc6 100644 --- a/packages/client/src/auth/Provider.tsx +++ b/packages/client/src/auth/Provider.tsx @@ -1,6 +1,11 @@ import { Status } from '@notowork/models/interfaces' import React, { Component } from 'react' -import context, { defaultValue, SignIn, SignOut, Value } from './context' +import context, { + AuthContextValue, + defaultValue, + SignIn, + SignOut, +} from './context' import { signIn } from './service' export default class Provider extends Component<{}, ProviderState> { @@ -53,4 +58,4 @@ export default class Provider extends Component<{}, ProviderState> { } } -export type ProviderState = Value +export type ProviderState = AuthContextValue diff --git a/packages/client/src/auth/context.ts b/packages/client/src/auth/context.ts index 19bd3ab..c6103d6 100644 --- a/packages/client/src/auth/context.ts +++ b/packages/client/src/auth/context.ts @@ -1,7 +1,7 @@ import { Status, User } from '@notowork/models/interfaces' import { Context, createContext } from 'react' -export const defaultValue: Value = { +export const defaultValue: AuthContextValue = { signIn: () => new Promise(resolve => resolve()), signOut: () => undefined, signedIn: false, @@ -9,18 +9,17 @@ export const defaultValue: Value = { user: null, } -const context: Context = createContext(defaultValue) +const context: Context = createContext(defaultValue) export default context export type SignIn = (email: string, password: string) => Promise export type SignOut = () => void -export type MaybeUser = User | null -export interface Value { +export interface AuthContextValue { signedIn: boolean status: Status | undefined - user: MaybeUser + user: User | null signIn: SignIn signOut: SignOut } diff --git a/packages/client/src/auth/withAuth.tsx b/packages/client/src/auth/withAuth.tsx index e59046c..f7305e5 100644 --- a/packages/client/src/auth/withAuth.tsx +++ b/packages/client/src/auth/withAuth.tsx @@ -1,6 +1,6 @@ import React, { ComponentType } from 'react' import { Subtract } from 'utility-types' -import context, { Value } from './context' +import context, { AuthContextValue } from './context' const withAuth =

(Component: ComponentType

) => { return class AuthedComponent extends React.Component> { @@ -8,9 +8,8 @@ const withAuth =

(Component: ComponentType

) => { return {this.renderComponent} } - public renderComponent = (authProps: Value) => { - const props = { auth: authProps, ...this.props } - return + public renderComponent = (authProps: AuthContextValue) => { + return } } } @@ -18,5 +17,5 @@ const withAuth =

(Component: ComponentType

) => { export default withAuth export interface WithAuth { - auth: Value + auth: AuthContextValue } diff --git a/packages/client/src/pages/SignIn/SignIn.tsx b/packages/client/src/pages/SignIn/SignIn.tsx index e87df76..a2cfdee 100644 --- a/packages/client/src/pages/SignIn/SignIn.tsx +++ b/packages/client/src/pages/SignIn/SignIn.tsx @@ -11,10 +11,10 @@ import { } from '@notowork/lib/validators' import { Status } from '@notowork/models/interfaces' import React, { - ChangeEvent, ChangeEventHandler, - Component, + FC, MouseEventHandler, + useState, } from 'react' import { Redirect } from 'react-router-dom' import { withAuth, WithAuth } from '~auth' @@ -30,137 +30,87 @@ import { import Checkbox from './components/Checkbox' import SnackbarError from './components/SnackbarError' -class SignIn extends Component { - public readonly state: SignInState = { - email: '', - emailError: '', - password: '', - passwordError: '', - showSignInError: false, - rememberMe: false, +const SignIn: FC = ({ + auth: { status, signIn, signedIn }, + classes, +}) => { + const [email, setEmail] = useState('') + const [emailError, setEmailError] = useState('') + const [password, setPassword] = useState('') + const [passwordError, setPasswordError] = useState('') + const [showSignInError, setShowSignInError] = useState(false) + const [rememberMe, setRememberMe] = useState(false) + + const handleChangePassword: ChangeEventHandler = e => { + setPassword(e.target.value) + setPasswordError('') } - - public componentDidUpdate(prevProps: SignInProps) { - if ( - prevProps.auth.status === Status.Pending && - this.props.auth.status === Status.Failure - ) { - this.setState({ showSignInError: true }) - } - } - - public render() { - if (this.props.auth.signedIn) return - - const { - auth: { status }, - classes, - } = this.props - const { - rememberMe, - email, - emailError, - password, - passwordError, - showSignInError, - } = this.state - const isPending = status === Status.Pending - - return ( - - - - - - - - - - -

- - Nie pamiętam hasła -
- - - - ) + const handleChangeEmail: ChangeEventHandler = e => { + setEmail(e.target.value) + setEmailError('') } - private handleChangeText: ChangeEventHandler = e => { - const state = { - [e.target.name]: e.target.value, - emailError: '', - passwordError: '', - } - - // https://github.com/Microsoft/TypeScript/issues/13948 - this.setState(state as Pick< - SignInState, - Exclude - >) - } - - private handleSubmit: MouseEventHandler = () => { - this.setState((state, props) => { - const errors = { - emailError: emailValidator(state.email), - passwordError: passwordLengthValidator( - state.password, - 'Nieprawidłowe hasło!', - ), - } - - const hasErrors = Object.values(errors).some(error => - Boolean(error.length), - ) - - if (hasErrors) return { ...errors } - - props.auth.signIn(state.email, state.password).catch(logError) + const handleSubmit: MouseEventHandler = () => { + setEmailError(emailValidator(email)) + setPasswordError(passwordLengthValidator(password, 'Nieprawidłowe hasło!')) - return null - }) + signIn(email, password).catch(logError) } - private handleChangeCheckbox = ( - e: ChangeEvent, - checked: boolean, - ) => { - const state = { [e.target.name]: checked } - - this.setState(state as Pick) - } - - private hideSignInError: () => void = () => { - this.setState({ showSignInError: false }) - } + const handleChangeRememberMe = (_: any, checked: boolean) => + setRememberMe(checked) + + const hideSignInError = () => setShowSignInError(false) + + if (signedIn) return + + const isPending = status === Status.Pending + + return ( + + + + + + + + + + +
+ + Nie pamiętam hasła +
+ + +
+ ) } const styles: StyleRulesCallback = () => ({ @@ -174,16 +124,4 @@ const styles: StyleRulesCallback = () => ({ interface SignInProps extends WithAuth, WithStyles {} -interface Errors { - emailError: string - passwordError: string -} - -interface SignInState extends Errors { - email: string - password: string - rememberMe: boolean - showSignInError: boolean -} - export default withAuth(withStyles(styles)(SignIn)) From df9456588a9e7c191510af17150ee3f14883cd22 Mon Sep 17 00:00:00 2001 From: stpoa Date: Thu, 7 Nov 2019 14:35:38 +0100 Subject: [PATCH 4/4] Accomplish successfull login --- packages/client/src/auth/Provider.tsx | 109 +++++++++++--------- packages/client/src/auth/context.ts | 1 + packages/client/src/pages/SignIn/SignIn.tsx | 3 +- 3 files changed, 62 insertions(+), 51 deletions(-) diff --git a/packages/client/src/auth/Provider.tsx b/packages/client/src/auth/Provider.tsx index bfd9fc6..4f8c28a 100644 --- a/packages/client/src/auth/Provider.tsx +++ b/packages/client/src/auth/Provider.tsx @@ -1,61 +1,70 @@ import { Status } from '@notowork/models/interfaces' -import React, { Component } from 'react' -import context, { - AuthContextValue, - defaultValue, - SignIn, - SignOut, -} from './context' -import { signIn } from './service' - -export default class Provider extends Component<{}, ProviderState> { - public constructor(props: {}) { - super(props) - - this.state = { - ...defaultValue, - signIn: this.signIn, - signOut: this.signOut, +import gql from 'graphql-tag' +import React, { FC, useState } from 'react' +import { useMutation } from 'react-apollo-hooks' +import context, { AuthContextValue, SignIn, SignOut } from './context' + +const SIGN_IN = gql` + mutation($email: String!, $password: String!) { + login(email: $email, password: $password) { + token + user { + id + name + email + roles + } } } +` - public render() { - return ( - - {this.props.children} - - ) - } +const Provider: FC = ({ children }) => { + const [signedIn, setSignedIn] = useState(false) + const [status, setStatus] = useState() + const [user, setUser] = useState(null) + const [error, setError] = useState() + + const [execSignIn] = useMutation(SIGN_IN) + + const signIn: SignIn = async (email, password) => { + setStatus(Status.Pending) - private signIn: SignIn = (email, password) => { - return new Promise(resolve => { - this.setState( - { - status: Status.Pending, - }, - async () => { - const maybeUser = await signIn(email, password) - - this.setState( - { - signedIn: Boolean(maybeUser), - status: maybeUser ? Status.Success : Status.Failure, - user: maybeUser, - }, - () => resolve(), - ) - }, - ) - }) + try { + const { data } = await execSignIn({ variables: { email, password } }) + const login = (data as any).login + + setUser(login.user) + setStatus(login.user ? Status.Success : Status.Failure) + setSignedIn(true) + } catch (e) { + setError(e.message) + setStatus(undefined) + setUser(null) + setSignedIn(false) + } } - private signOut: SignOut = () => { - this.setState({ - signedIn: false, - status: undefined, - user: null, - }) + const signOut: SignOut = () => { + setSignedIn(false) + setStatus(undefined) + setUser(null) } + + return ( + + {children} + + ) } export type ProviderState = AuthContextValue +export default Provider diff --git a/packages/client/src/auth/context.ts b/packages/client/src/auth/context.ts index c6103d6..2abd044 100644 --- a/packages/client/src/auth/context.ts +++ b/packages/client/src/auth/context.ts @@ -22,4 +22,5 @@ export interface AuthContextValue { user: User | null signIn: SignIn signOut: SignOut + error?: string } diff --git a/packages/client/src/pages/SignIn/SignIn.tsx b/packages/client/src/pages/SignIn/SignIn.tsx index a2cfdee..4ea45ca 100644 --- a/packages/client/src/pages/SignIn/SignIn.tsx +++ b/packages/client/src/pages/SignIn/SignIn.tsx @@ -31,7 +31,7 @@ import Checkbox from './components/Checkbox' import SnackbarError from './components/SnackbarError' const SignIn: FC = ({ - auth: { status, signIn, signedIn }, + auth: { status, signIn, signedIn, error }, classes, }) => { const [email, setEmail] = useState('') @@ -104,6 +104,7 @@ const SignIn: FC = ({ + {error ?
{error}
: null}