improve accessibility of password field

pull/134/head
Alex Kern 1 year ago
parent b5cc41558b
commit 450841484a
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

@ -1,3 +1,4 @@
import { notFound } from 'next/navigation'
import { channelRepo } from '../../../channel'
import Spinner from '../../../components/Spinner'
import Wordmark from '../../../components/Wordmark'
@ -21,13 +22,7 @@ export default async function DownloadPage({
const channel = await channelRepo.fetch(slug)
if (!channel) {
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="down" />
<Wordmark />
<p>Not found</p>
</div>
)
notFound()
}
return (

@ -13,7 +13,98 @@ import Spinner from '../components/Spinner'
import Wordmark from '../components/Wordmark'
import CancelButton from '../components/CancelButton'
export default function IndexPage(): JSX.Element {
function PageWrapper({
children,
isRotating = false,
}: {
children: React.ReactNode
isRotating?: boolean
}): JSX.Element {
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" isRotating={isRotating} />
<Wordmark />
{children}
</div>
)
}
function InitialState({
onDrop,
}: {
onDrop: (files: UploadedFile[]) => void
}): JSX.Element {
return (
<PageWrapper>
<div className="flex flex-col items-center space-y-1 max-w-md">
<p className="text-lg text-center text-stone-800">
Peer-to-peer file transfers in your browser.
</p>
<p className="text-sm text-center text-stone-600">
We never store anything. Files only served fresh.
</p>
</div>
<DropZone onDrop={onDrop} />
</PageWrapper>
)
}
function ConfirmUploadState({
uploadedFiles,
password,
onChangePassword,
onCancel,
onStart,
onFileListChange,
}: {
uploadedFiles: UploadedFile[]
password: string
onChangePassword: (pw: string) => void
onCancel: () => void
onStart: () => void
onFileListChange: (updatedFiles: UploadedFile[]) => void
}): JSX.Element {
return (
<PageWrapper>
<p className="text-lg text-center text-stone-800 max-w-md">
You are about to start uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} onChange={onFileListChange} />
<PasswordField value={password} onChange={onChangePassword} />
<div className="flex space-x-4">
<CancelButton onClick={onCancel} />
<StartButton onClick={onStart} />
</div>
</PageWrapper>
)
}
function UploadingState({
uploadedFiles,
password,
onStop,
}: {
uploadedFiles: UploadedFile[]
password: string
onStop: () => void
}): JSX.Element {
return (
<PageWrapper isRotating={true}>
<p className="text-lg text-center text-stone-800 max-w-md">
You are uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} />
<WebRTCProvider>
<Uploader files={uploadedFiles} password={password} />
</WebRTCProvider>
<StopButton onClick={onStop} />
</PageWrapper>
)
}
export default function UploadPage(): JSX.Element {
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const [password, setPassword] = useState('')
const [uploading, setUploading] = useState(false)
@ -44,55 +135,27 @@ export default function IndexPage(): JSX.Element {
}, [])
if (!uploadedFiles.length) {
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" />
<Wordmark />
<div className="flex flex-col items-center space-y-1 max-w-md">
<p className="text-lg text-center text-stone-800">
Peer-to-peer file transfers in your browser.
</p>
<p className="text-sm text-center text-stone-600">
We never store anything. Files only served fresh.
</p>
</div>
<DropZone onDrop={handleDrop} />
</div>
)
return <InitialState onDrop={handleDrop} />
}
if (!uploading) {
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" />
<Wordmark />
<p className="text-lg text-center text-stone-800 max-w-md">
You are about to start uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} onChange={handleFileListChange} />
<PasswordField value={password} onChange={handleChangePassword} />
<div className="flex space-x-4">
<CancelButton onClick={handleCancel} />
<StartButton onClick={handleStart} />
</div>
</div>
<ConfirmUploadState
uploadedFiles={uploadedFiles}
password={password}
onChangePassword={handleChangePassword}
onCancel={handleCancel}
onStart={handleStart}
onFileListChange={handleFileListChange}
/>
)
}
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" isRotating />
<Wordmark />
<p className="text-lg text-center text-stone-800 max-w-md">
You are uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p>
<UploadFileList files={uploadedFiles} />
<WebRTCProvider>
<Uploader files={uploadedFiles} password={password} />
</WebRTCProvider>
<StopButton onClick={handleStop} />
</div>
<UploadingState
uploadedFiles={uploadedFiles}
password={password}
onStop={handleStop}
/>
)
}

@ -8,7 +8,7 @@ export default function CancelButton({
return (
<button
onClick={onClick}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
className="px-4 py-2 text-sm font-medium text-stone-700 bg-white border border-stone-300 rounded-md hover:bg-stone-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Cancel
</button>

@ -97,7 +97,7 @@ export default function DropZone({
multiple
/>
<button
className="inline-block cursor-pointer relative py-3 px-6 text-base font-bold text-gray-700 bg-white border-2 border-gray-700 rounded-lg transition-all duration-300 ease-in-out outline-none hover:shadow-md active:shadow-inner focus:shadow-outline"
className="inline-block cursor-pointer relative py-3 px-6 text-base font-bold text-stone-700 bg-white border-2 border-stone-700 rounded-lg transition-all duration-300 ease-in-out outline-none hover:shadow-md active:shadow-inner focus:shadow-outline"
onClick={handleClick}
>
<span className="text-center text-stone-700">

@ -4,7 +4,7 @@ export default function Loading({ text }: { text: string }): JSX.Element {
return (
<div className="flex flex-col items-center">
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
<p className="text-sm text-gray-600 mt-2">{text}</p>
<p className="text-sm text-stone-600 mt-2">{text}</p>
</div>
)
}

@ -1,4 +1,5 @@
import React, { useCallback } from 'react'
import InputLabel from './InputLabel'
export default function PasswordField(props: {
value: string
@ -14,17 +15,20 @@ export default function PasswordField(props: {
)
return (
<input
autoFocus
type="password"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
props.isInvalid ? 'border-red-500' : 'border-gray-300'
}`}
placeholder={
props.isRequired ? 'Enter password...' : 'Add password (optional)...'
}
value={props.value}
onChange={handleChange}
/>
<div className="flex flex-col w-full">
<InputLabel>
{props.isRequired ? 'Password' : 'Password (optional)'}
</InputLabel>
<input
autoFocus
type="password"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${
props.isInvalid ? 'border-red-500' : 'border-stone-300'
}`}
placeholder="Enter a secret password for this FilePizza..."
value={props.value}
onChange={handleChange}
/>
</div>
)
}

@ -11,7 +11,7 @@ export default function ProgressBar({
const isComplete = value === max
return (
<div className="w-full h-12 bg-gray-200 rounded-md overflow-hidden">
<div className="w-full h-12 bg-stone-200 rounded-md overflow-hidden">
<div
className={`h-full ${
isComplete ? 'bg-green-500' : 'bg-blue-500'

@ -5,7 +5,7 @@ function getTypeColor(fileType: string): string {
if (fileType.startsWith('text/')) return 'bg-green-100 text-green-800'
if (fileType.startsWith('audio/')) return 'bg-purple-100 text-purple-800'
if (fileType.startsWith('video/')) return 'bg-red-100 text-red-800'
return 'bg-gray-100 text-gray-800'
return 'bg-stone-100 text-stone-800'
}
export default function TypeBadge({ type }: { type: string }): JSX.Element {

@ -10,6 +10,7 @@ import * as t from 'io-ts'
import Loading from './Loading'
import ProgressBar from './ProgressBar'
import useClipboard from '../hooks/useClipboard'
import InputLabel from './InputLabel'
enum UploaderConnectionStatus {
Pending = 'PENDING',
@ -337,9 +338,7 @@ export default function Uploader({
</div>
<div className="flex-auto flex flex-col justify-center space-y-2">
<div className="flex flex-col w-full">
<label className="text-[10px] text-gray-400 mb-0.5 font-bold">
Long URL
</label>
<InputLabel>Long URL</InputLabel>
<div className="flex w-full">
<input
className="flex-grow px-3 py-2 text-xs border border-r-0 rounded-l"
@ -347,7 +346,7 @@ export default function Uploader({
readOnly
/>
<button
className="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r border-t border-r border-b"
className="px-4 py-2 text-sm text-stone-700 bg-stone-100 hover:bg-stone-200 rounded-r border-t border-r border-b"
onClick={onCopyLongURL}
>
{hasCopiedLongURL ? 'Copied' : 'Copy'}
@ -355,9 +354,7 @@ export default function Uploader({
</div>
</div>
<div className="flex flex-col w-full mt-2">
<label className="text-[10px] text-gray-400 mb-0.5 font-bold">
Short URL
</label>
<InputLabel>Short URL</InputLabel>
<div className="flex w-full">
<input
className="flex-grow px-3 py-2 text-xs border border-r-0 rounded-l"
@ -365,7 +362,7 @@ export default function Uploader({
readOnly
/>
<button
className="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r border-t border-r border-b"
className="px-4 py-2 text-sm text-stone-700 bg-stone-100 hover:bg-stone-200 rounded-r border-t border-r border-b"
onClick={onCopyShortURL}
>
{hasCopiedShortURL ? 'Copied' : 'Copy'}

Loading…
Cancel
Save