Skip to content

Commit

Permalink
Add page create account (#116)
Browse files Browse the repository at this point in the history
* feat: init form register user

* feat(ui): create account

* fix(ui): build
  • Loading branch information
isaqueveras committed May 28, 2023
1 parent 76af6b8 commit b162630
Show file tree
Hide file tree
Showing 35 changed files with 323 additions and 59 deletions.
5 changes: 3 additions & 2 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@tailwindcss/forms": "^0.5.3",
"axios": "^0.27.2",
"postcss": "^8.4.16",
"react": "^18.2.0",
Expand Down Expand Up @@ -45,10 +46,10 @@
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.11.57",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^4.2.0",
"@types/react": "^18.0.18",
"@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^4.2.0",
"eslint": "^7.9.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-es": "^4.1.0",
Expand Down
1 change: 1 addition & 0 deletions ui/src/data/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './remote-authentication'
export * from './remote-create-account'
18 changes: 4 additions & 14 deletions ui/src/data/usecases/remote-authentication.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
import { HttpClient, HttpStatusCode } from '../../data/protocols/http'
import { Authentication } from '../../domain/usecases'
import { InvalidCredentialsError, UnexpectedError, UnauthorizedAuthenticationError } from '../../domain/errors'
import { Oops } from '../../domain/errors'

export class RemoteAuthentication implements Authentication {
constructor (
private readonly url: string,
private readonly httpClient: HttpClient<RemoteAuthentication.Model>
private readonly httpClient: HttpClient<Authentication.Model>
) {}

async auth (params: Authentication.Params): Promise<Authentication.Model> {
const httpResponse = await this.httpClient.request({
url: this.url,
method: 'post',
body: params
})
const httpResponse = await this.httpClient.request({ url: this.url, method: 'post', body: params })
switch (httpResponse.statusCode) {
case HttpStatusCode.ok: return httpResponse.body
case HttpStatusCode.unauthorized: throw new InvalidCredentialsError()
case HttpStatusCode.forbidden: throw new UnauthorizedAuthenticationError()
default: throw new UnexpectedError()
default: throw new Oops(httpResponse.body.message)
}
}
}

export namespace RemoteAuthentication {
export type Model = Authentication.Model
}
18 changes: 18 additions & 0 deletions ui/src/data/usecases/remote-create-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { HttpClient, HttpStatusCode } from '../../data/protocols/http'
import { CreateAccount } from '../../domain/usecases'
import { Oops } from '../../domain/errors'

export class RemoteCreateAccount implements CreateAccount {
constructor (
private readonly url: string,
private readonly httpClient: HttpClient<CreateAccount.Model>
) {}

async register (params: CreateAccount.Params): Promise<CreateAccount.Model> {
const httpResponse = await this.httpClient.request({ url: this.url, method: 'post', body: params })
switch (httpResponse.statusCode) {
case HttpStatusCode.ok: return httpResponse.body
default: throw new Oops(httpResponse.body.message)
}
}
}
1 change: 1 addition & 0 deletions ui/src/domain/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './invalid-credentials-error'
export * from './unexpected-error'
export * from './unauthorized-authentication-error'
export * from './oops'
6 changes: 6 additions & 0 deletions ui/src/domain/errors/oops.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class Oops extends Error {
constructor (message: string) {
super(message)
this.name = 'OopsError'
}
}
4 changes: 4 additions & 0 deletions ui/src/domain/models/err.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Err = {
message: string
code: number
}
1 change: 1 addition & 0 deletions ui/src/domain/models/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './account-model'
export * from './err'
4 changes: 2 additions & 2 deletions ui/src/domain/usecases/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountModel } from '@/domain/models'
import { AccountModel, Err } from '@/domain/models'

export interface Authentication {
auth: (params: Authentication.Params) => Promise<Authentication.Model>
Expand All @@ -10,5 +10,5 @@ export namespace Authentication {
password: string
}

export type Model = AccountModel
export type Model = AccountModel & Err
}
16 changes: 16 additions & 0 deletions ui/src/domain/usecases/create-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Err } from '@/domain/models'

export interface CreateAccount {
register: (params: CreateAccount.Params) => Promise<CreateAccount.Model>
}

export namespace CreateAccount {
export type Params = {
first_name: string
last_name: string
email: string
password: string
}

export type Model = Err
}
1 change: 1 addition & 0 deletions ui/src/domain/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './authentication'
export * from './create-account'
14 changes: 14 additions & 0 deletions ui/src/main/factories/pages/create-account-factory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react'

import { CreateAccountPage } from '../../../presentation/pages'
import { makeRemoteCreateAccount } from '../usecases'
import { makeCreateAccountValidation } from '../validation'

export const makeCreateAccount: React.FC = () => {
return (
<CreateAccountPage
usecase={makeRemoteCreateAccount()}
validation={makeCreateAccountValidation()}
/>
)
}
1 change: 1 addition & 0 deletions ui/src/main/factories/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './login-factory'
export * from './home-factory'
export * from './create-account-factory'
6 changes: 3 additions & 3 deletions ui/src/main/factories/pages/login-factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React from 'react'

import { makeLoginValidation } from '../../../main/factories/validation'
import { makeRemoteAuthentication } from '../../../main/factories/usecases'
import { Login } from '../../../presentation/pages'
import { LoginPage } from '../../../presentation/pages'

export const makeLogin: React.FC = () => {
return (
<Login
authentication={makeRemoteAuthentication()}
<LoginPage
usecase={makeRemoteAuthentication()}
validation={makeLoginValidation()}
/>
)
Expand Down
1 change: 1 addition & 0 deletions ui/src/main/factories/usecases/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './remote-authentication-factory'
export * from './remote-register-factory'
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import { makeApiUrl, makeAxiosHttpClient } from '../../../main/factories/http'
import { RemoteAuthentication } from '../../../data/usecases'
import { Authentication } from '../../../domain/usecases'

export const makeRemoteAuthentication = (): Authentication => new RemoteAuthentication(makeApiUrl('auth/login'), makeAxiosHttpClient())
export const makeRemoteAuthentication = (): Authentication =>
new RemoteAuthentication(makeApiUrl('auth/login'), makeAxiosHttpClient())
6 changes: 6 additions & 0 deletions ui/src/main/factories/usecases/remote-register-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { makeApiUrl, makeAxiosHttpClient } from '../../../main/factories/http'
import { RemoteCreateAccount } from '../../../data/usecases'
import { CreateAccount } from '../../../domain/usecases'

export const makeRemoteCreateAccount = (): CreateAccount =>
new RemoteCreateAccount(makeApiUrl('auth/register'), makeAxiosHttpClient())
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ValidationComposite } from '../../../main/composites'
import { ValidationBuilder } from '../../../main/builders'

export const makeCreateAccountValidation = (): ValidationComposite => ValidationComposite.build([
...ValidationBuilder.field('firstName').required().build(),
...ValidationBuilder.field('lastName').required().build(),
...ValidationBuilder.field('email').required().email().build(),
...ValidationBuilder.field('password').required().min(5).build(),
...ValidationBuilder.field('confirmPassword').required().min(5).sameAs('password').build()
])
1 change: 1 addition & 0 deletions ui/src/main/factories/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './login-validation-factory'
export * from './create-account-validation-factory'
3 changes: 2 additions & 1 deletion ui/src/main/routes/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RecoilRoot } from 'recoil'

import { setCurrentAccountAdapter, getCurrentAccountAdapter } from '../../main/adapters'
import { currentAccountState } from '../../presentation/components'
import { makeLogin, makeHome } from '../../main/factories/pages'
import { makeLogin, makeHome, makeCreateAccount } from '../../main/factories/pages'
import { PrivateRoute } from '../proxies'

const Router: React.FC = () => {
Expand All @@ -18,6 +18,7 @@ const Router: React.FC = () => {
<BrowserRouter>
<Switch>
<Route path="/auth/login" exact component={makeLogin} />
<Route path="/auth/register" exact component={makeCreateAccount} />
<PrivateRoute path="/" exact component={makeHome} />
</Switch>
</BrowserRouter>
Expand Down
9 changes: 4 additions & 5 deletions ui/src/presentation/components/form-status/form-status.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React from 'react'

type Props = {
state: any
messageError: string
}

const FormStatus: React.FC<Props> = ({ state }: Props) => {
const { mainError } = state
const FormStatus: React.FC<Props> = ({ messageError }: Props) => {
return (
<>
{mainError && (
{messageError && (
<div data-testid="error-wrap" className="flex items-center justify-center text-black text-sm p-3 bg-red-200 mb-3 rounded">
<svg className="fill-current w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M12.432 0c1.34 0 2.01.912 2.01 1.957 0 1.305-1.164 2.512-2.679 2.512-1.269 0-2.009-.75-1.974-1.99C9.789 1.436 10.67 0 12.432 0zM8.309 20c-1.058 0-1.833-.652-1.093-3.524l1.214-5.092c.211-.814.246-1.141 0-1.141-.317 0-1.689.562-2.502 1.117l-.528-.88c2.572-2.186 5.531-3.467 6.801-3.467 1.057 0 1.233 1.273.705 3.23l-1.391 5.352c-.246.945-.141 1.271.106 1.271.317 0 1.357-.392 2.379-1.207l.6.814C12.098 19.02 9.365 20 8.309 20z"/></svg>
<span data-testid="main-error">{mainError}</span>
<span data-testid="main-error">{messageError}</span>
</div>
)}
</>
Expand Down
5 changes: 3 additions & 2 deletions ui/src/presentation/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ const Input: React.FC<Props> = ({ state, setState, ...props }: Props) => {
const error = state[`${props.name}Error`]
return (
<div>
<label title={error} className="text-gray-600 py-2">{props.placeholder}</label>
<label title={error} className="block text-sm font-medium leading-6 text-gray-900 py-2">{props.placeholder}</label>
<input
{...props}
ref={inputRef}
title={error}
placeholder={props.placeholder}
data-testid={props.name}
readOnly
className="mb-3 p-3 form-control block w-full border border-solid rounded transition ease-in-out" data-status={error ? 'invalid' : 'valid'}
className="mb-3 block w-full form-control rounded-md border-0 p-3 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6"
data-status={error ? 'invalid' : 'valid'}
onFocus={e => { e.target.readOnly = false }}
onChange={e => { setState({ ...state, [e.target.name]: e.target.value }) }}
/>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/presentation/components/submit-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const SubmitButton: React.FC<Props> = ({ state, text }: Props) => {
data-testid='submit'
disabled={state.isFormInvalid}
type='submit'
className='w-full p-3 bg-rose-600 text-white font-medium text-sm rounded enabled:hover:shadow-lg focus:outline-none focus:ring-0 disabled:bg-rose-400'
className="block rounded-md bg-pink-600 w-full px-3 py-3 text-sm font-semibold text-white shadow-sm hover:bg-pink-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pink-600 disabled:bg-rose-500"
>
{ state.isLoading && (
<>
Expand Down
20 changes: 20 additions & 0 deletions ui/src/presentation/pages/create-account/components/atoms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { atom } from 'recoil'

export const createAccountState = atom({
key: 'createAccountState',
default: {
isLoading: false,
isFormInvalid: true,
email: '',
password: '',
confirmPassword: '',
firstName: '',
lastName: '',
messageError: '',
passwordError: '',
firstNameError: '',
lastNameError: '',
confirmPasswordError: '',
mainError: ''
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'
import { useRecoilValue } from 'recoil'

import { createAccountState } from './atoms'
import { FormStatusBase } from '../../../../presentation/components'

const FormStatus: React.FC = () => {
const state = useRecoilValue(createAccountState)
return <FormStatusBase messageError={state.messageError} />
}

export default FormStatus
4 changes: 4 additions & 0 deletions ui/src/presentation/pages/create-account/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './atoms'
export { default as Input } from './input'
export { default as SubmitButton } from './submit-button'
export { default as FormStatus } from './form-status'
26 changes: 26 additions & 0 deletions ui/src/presentation/pages/create-account/components/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createAccountState } from './atoms'
import { InputBase } from '../../../../presentation/components'

import { useRecoilState } from 'recoil'
import React from 'react'

type Props = {
type: string
name: string
placeholder: string
}

const Input: React.FC<Props> = ({ type, name, placeholder }: Props) => {
const [state, setState] = useRecoilState(createAccountState)
return (
<InputBase
type={type}
name={name}
placeholder={placeholder}
state={state}
setState={setState}
/>
)
}

export default Input
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react'

import { useRecoilValue } from 'recoil'
import { createAccountState } from './atoms'
import { SubmitButtonBase } from '../../../../presentation/components'

type Props = {
text: string
}

const SubmitButton: React.FC<Props> = ({ text }: Props) => {
return <SubmitButtonBase text={text} state={useRecoilValue(createAccountState)} />
}

export default SubmitButton
Loading

1 comment on commit b162630

@vercel
Copy link

@vercel vercel bot commented on b162630 May 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

power-sso – ./

power-sso.vercel.app
power-sso-git-main-isaqueveras.vercel.app
power-sso-isaqueveras.vercel.app

Please sign in to comment.