-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: experiment with a simpler upload page
- Loading branch information
Showing
15 changed files
with
378 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +0,0 @@ | ||
export const isLoginPage = (path: string) => path.includes('/login'); | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import { isLoginPage } from '../NavigationBar/helpers/isLoginPage'; | ||
const isLoginPage = (path: string) => path.includes('/login'); | ||
export const isSimplePage = () => window.location.pathname.endsWith('simple'); | ||
|
||
export const canShowNavbar = (path: string) => !isLoginPage(path); | ||
export const canShowNavbar = (path: string) => | ||
!isLoginPage(path) && !isSimplePage(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { useContext, useEffect, useState } from 'react'; | ||
|
||
import { ErrorPresenter } from '../../components/errors/ErrorPresenter'; | ||
import { Main, PageContainer } from '../../components/styled'; | ||
import StoreContext from '../../store/StoreContext'; | ||
import UploadForm from './components/UploadForm/UploadForm'; | ||
import { | ||
InfoMessage, | ||
UploadContainer | ||
} from './styled'; | ||
|
||
export function SimplePage() { | ||
const store = useContext(StoreContext); | ||
const [errorMessage, setErrorMessage] = useState<Error | null>(null); | ||
|
||
// Make sure the defaults are set if not present to ensure backwards compatability | ||
useEffect(() => { | ||
store.syncLocalStorage(); | ||
}, [store]); | ||
|
||
if (errorMessage) { | ||
return <ErrorPresenter error={errorMessage} />; | ||
} | ||
|
||
return ( | ||
<PageContainer> | ||
<UploadContainer> | ||
<Main> | ||
<div className="container"> | ||
<UploadForm | ||
setErrorMessage={(error) => setErrorMessage(error as Error)} | ||
/> | ||
<InfoMessage> | ||
All files uploaded here are automatically deleted after 21 | ||
minutes. | ||
</InfoMessage> | ||
</div> | ||
</Main> | ||
</UploadContainer> | ||
</PageContainer> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { useEffect, useRef } from 'react'; | ||
import { getDownloadFileName } from '../../DownloadsPage/helpers/getDownloadFileName'; | ||
|
||
interface Props { | ||
downloadLink: string | null | undefined; | ||
deckName: string | undefined; | ||
uploading: boolean; | ||
} | ||
|
||
function DownloadButton(props: Props) { | ||
const { downloadLink, deckName, uploading } = props; | ||
const isDownloadable = downloadLink && deckName; | ||
const downloadRef = useRef<HTMLAnchorElement>(null); | ||
|
||
const className = `button cta | ||
${isDownloadable ? 'is-primary' : 'is-light'} | ||
${uploading ? 'is-loading' : ''}`; | ||
|
||
const isReady = downloadLink && !uploading; | ||
|
||
useEffect(() => { | ||
if (isReady) { | ||
downloadRef.current?.click(); | ||
} | ||
}, [isReady, downloadRef]); | ||
|
||
return ( | ||
<div> | ||
<button | ||
type="button" | ||
className={className} | ||
onClick={(event) => { | ||
if (!isDownloadable) { | ||
event?.preventDefault(); | ||
} | ||
downloadRef.current?.click(); | ||
}} | ||
disabled={!isDownloadable} | ||
> | ||
Download | ||
</button> | ||
{downloadLink && ( | ||
<a | ||
hidden | ||
target="_blank" | ||
aria-label="download link" | ||
href={downloadLink} | ||
download={getDownloadFileName(deckName ?? 'Untitled')} | ||
ref={downloadRef} | ||
rel="noreferrer" | ||
> | ||
{downloadLink} | ||
</a> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
export default DownloadButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import styled from 'styled-components'; | ||
|
||
const DropParagraph = styled.div<{ hover: boolean }>` | ||
border: 1.3px dashed; | ||
border-radius: 3px; | ||
border-color: ${(props) => (props.hover ? '#5997f5' : 'lightgray')}; | ||
padding: 4rem; | ||
margin-bottom: 1rem; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
grid-gap: 1rem; | ||
`; | ||
|
||
export default DropParagraph; |
25 changes: 25 additions & 0 deletions
25
src/pages/SimplePage/components/UploadForm/UploadForm.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { render } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
|
||
import UploadForm from './UploadForm'; | ||
|
||
describe('UploadForm', () => { | ||
test('download button is light by default', () => { | ||
const { container } = render( | ||
<UploadForm setErrorMessage={(error) => fail(error)} /> | ||
); | ||
expect(container.querySelector('.button.cta.is-light')).toBeInTheDocument(); | ||
}); | ||
|
||
test('no null classes', () => { | ||
const { container } = render( | ||
<UploadForm setErrorMessage={(error) => fail(error)} /> | ||
); | ||
expect(container.querySelector('.null')).toBeNull(); | ||
}); | ||
|
||
test('download button is disabled', () => { | ||
render(<UploadForm setErrorMessage={(error) => fail(error)} />); | ||
expect(document.querySelector('.button.cta')).toBeDisabled(); | ||
}); | ||
}); |
130 changes: 130 additions & 0 deletions
130
src/pages/SimplePage/components/UploadForm/UploadForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { SyntheticEvent, useRef, useState } from 'react'; | ||
import { ErrorHandlerType } from '../../../../components/errors/helpers/getErrorMessage'; | ||
import handleRedirect from '../../../../lib/handleRedirect'; | ||
import getAcceptedContentTypes from '../../helpers/getAcceptedContentTypes'; | ||
import getHeadersFilename from '../../helpers/getHeadersFilename'; | ||
import DownloadButton from '../DownloadButton'; | ||
import DropParagraph from '../DropParagraph'; | ||
import { useDrag } from './hooks/useDrag'; | ||
|
||
interface UploadFormProps { | ||
setErrorMessage: ErrorHandlerType; | ||
} | ||
|
||
function UploadForm({ setErrorMessage }: Readonly<UploadFormProps>) { | ||
const [uploading, setUploading] = useState(false); | ||
const [downloadLink, setDownloadLink] = useState<null | string>(''); | ||
const [deckName, setDeckName] = useState(''); | ||
const fileInputRef = useRef<HTMLInputElement>(null); | ||
const convertRef = useRef<HTMLButtonElement>(null); | ||
const { dropHover } = useDrag({ | ||
onDrop: (event) => { | ||
const { dataTransfer } = event; | ||
|
||
if (dataTransfer && dataTransfer.files.length > 0) { | ||
fileInputRef.current!.files = dataTransfer.files; | ||
convertRef.current?.click(); | ||
} | ||
|
||
event.preventDefault(); | ||
}, | ||
}); | ||
|
||
const handleSubmit = async (event: SyntheticEvent) => { | ||
event.preventDefault(); | ||
setUploading(true); | ||
try { | ||
const storedFields = Object.entries(window.localStorage); | ||
const element = event.currentTarget as HTMLFormElement; | ||
const formData = new FormData(element); | ||
storedFields.forEach((sf) => formData.append(sf[0], sf[1])); | ||
const request = await window.fetch('/api/upload/file', { | ||
method: 'post', | ||
body: formData, | ||
}); | ||
const contentType = request.headers.get('Content-Type'); | ||
const notOK = request.status !== 200; | ||
if (request.redirected) { | ||
return handleRedirect(request); | ||
} | ||
|
||
if (notOK) { | ||
const text = await request.text(); | ||
setDownloadLink(null); | ||
return setErrorMessage(text); | ||
} | ||
const fileNameHeader = getHeadersFilename(request.headers); | ||
if (fileNameHeader) { | ||
setDeckName(fileNameHeader); | ||
} else { | ||
const fallback = | ||
contentType === 'application/zip' | ||
? 'Your Decks.zip' | ||
: 'Your deck.apkg'; | ||
setDeckName(fallback); | ||
} | ||
const blob = await request.blob(); | ||
setDownloadLink(window.URL.createObjectURL(blob)); | ||
setUploading(false); | ||
} catch (error) { | ||
setDownloadLink(null); | ||
setErrorMessage(error as Error); | ||
setUploading(false); | ||
return false; | ||
} | ||
return true; | ||
}; | ||
|
||
const fileSelected = () => { | ||
convertRef.current?.click(); | ||
}; | ||
|
||
return ( | ||
<form | ||
encType="multipart/form-data" | ||
method="post" | ||
onSubmit={(event) => { | ||
handleSubmit(event); | ||
}} | ||
> | ||
<div className="container"> | ||
<div> | ||
<div className="field"> | ||
<DropParagraph hover={dropHover}> | ||
<h1>Drag a file and Drop it here</h1> | ||
<p className="my-2"> | ||
<i>or</i> | ||
</p> | ||
<label htmlFor="pakker"> | ||
<input | ||
ref={fileInputRef} | ||
className="file-input" | ||
type="file" | ||
name="pakker" | ||
accept={getAcceptedContentTypes()} | ||
required | ||
multiple | ||
onChange={() => fileSelected()} | ||
/> | ||
</label> | ||
<span className="tag">Select</span> | ||
</DropParagraph> | ||
</div> | ||
<DownloadButton | ||
downloadLink={downloadLink} | ||
deckName={deckName} | ||
uploading={uploading} | ||
/> | ||
<button | ||
aria-label="Upload file" | ||
style={{ visibility: 'hidden' }} | ||
ref={convertRef} | ||
type="submit" | ||
/> | ||
</div> | ||
</div> | ||
</form> | ||
); | ||
} | ||
|
||
export default UploadForm; |
30 changes: 30 additions & 0 deletions
30
src/pages/SimplePage/components/UploadForm/hooks/useDrag.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
interface UseDragInput { | ||
onDrop: (event: DragEvent) => void; | ||
} | ||
|
||
export const useDrag = ({ onDrop }: UseDragInput) => { | ||
const [dropHover, setDropHover] = useState(false); | ||
|
||
useEffect(() => { | ||
const body = document.getElementsByTagName('body')[0]; | ||
body.ondragover = (event) => { | ||
setDropHover(true); | ||
event.preventDefault(); | ||
}; | ||
|
||
body.ondragenter = (event) => { | ||
event.preventDefault(); | ||
setDropHover(true); | ||
}; | ||
|
||
body.ondragleave = () => { | ||
setDropHover(false); | ||
}; | ||
|
||
body.ondrop = onDrop; | ||
}, []); | ||
|
||
return { dropHover }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
/** | ||
* Function to get accepted content types | ||
* For now this is a hardcoded string in the client but should be retrieved from the backend. | ||
* | ||
* @returns comma seperated string with supported file types | ||
*/ | ||
export default function getAcceptedContentTypes(): string { | ||
return '.zip,.html,.csv'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import getHeadersFilename from './getHeadersFilename'; | ||
|
||
const mockedHeaders = new Headers(); | ||
mockedHeaders.get = () => 'My%20uber%20cool%20deck.apkg'; | ||
test('getHeadersFilename', () => { | ||
expect(getHeadersFilename(mockedHeaders)).toBe('My uber cool deck.apkg'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
const getHeadersFilename = (headers: Response['headers']) => { | ||
const filename = headers.get('File-Name'); | ||
if (!filename) { | ||
return null; | ||
} | ||
return decodeURIComponent(filename); | ||
}; | ||
|
||
export default getHeadersFilename; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { SimplePage } from './SimplePage'; | ||
|
||
export default SimplePage; |
Oops, something went wrong.