mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Update some style, add Matter archive importer tool
This commit is contained in:
parent
c240f1d2c8
commit
824899760a
10 changed files with 262 additions and 32 deletions
|
|
@ -18,7 +18,7 @@ import { v4 as uuidv4 } from 'uuid'
|
|||
import { buildLogger } from '../../utils/logger'
|
||||
|
||||
const MAX_DAILY_UPLOADS = 4
|
||||
const VALID_CONTENT_TYPES = ['text/csv']
|
||||
const VALID_CONTENT_TYPES = ['text/csv', 'application/zip']
|
||||
|
||||
const logger = buildLogger('app.dispatch')
|
||||
|
||||
|
|
@ -26,6 +26,8 @@ const extensionForContentType = (contentType: string) => {
|
|||
switch (contentType) {
|
||||
case 'text/csv':
|
||||
return 'csv'
|
||||
case 'application/zip':
|
||||
return 'zip'
|
||||
}
|
||||
return '.unknown'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ export function ConfirmProfileModal(): JSX.Element {
|
|||
const [username, setUsername] = useState('')
|
||||
const [debouncedUsername, setDebouncedUsername] = useState('')
|
||||
const [bio, setBio] = useState('')
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
const { isUsernameValid, usernameErrorMessage } = useValidateUsernameQuery({
|
||||
username: debouncedUsername,
|
||||
|
|
@ -110,8 +109,9 @@ export function ConfirmProfileModal(): JSX.Element {
|
|||
<HStack
|
||||
css={{
|
||||
width: '100%',
|
||||
borderRadius: '6px',
|
||||
border: `1px solid $grayBorder`,
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
p: '$2',
|
||||
}}
|
||||
>
|
||||
|
|
@ -139,7 +139,12 @@ export function ConfirmProfileModal(): JSX.Element {
|
|||
{isUsernameValid && (
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ m: 0, pl: '$2', alignSelf: 'flex-start', color: '$omnivoreGray' }}
|
||||
css={{
|
||||
m: 0,
|
||||
pl: '$2',
|
||||
alignSelf: 'flex-start',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Username is available.
|
||||
</StyledText>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,12 @@ export function ProfileLayout(props: ProfileLayoutProps): JSX.Element {
|
|||
<VStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{ bg: '$omnivoreYellow', height: '100vh' }}
|
||||
css={{
|
||||
// bg: '$omnivoreYellow',
|
||||
height: '100vh',
|
||||
background:
|
||||
'-webkit-linear-gradient(-65deg, rgba(255, 255, 255, 1.0) 45%, rgba(255, 210, 52, 1.0) 0%)',
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</VStack>
|
||||
|
|
|
|||
|
|
@ -12,9 +12,8 @@ import { parseErrorCodes } from '../../../lib/queryParamParser'
|
|||
export function EmailForgotPassword(): JSX.Element {
|
||||
const router = useRouter()
|
||||
const [email, setEmail] = useState<string>('')
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
|
@ -36,7 +35,8 @@ export function EmailForgotPassword(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ export function EmailLogin(): JSX.Element {
|
|||
const router = useRouter()
|
||||
const [email, setEmail] = useState<string | undefined>(undefined)
|
||||
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
|
@ -39,7 +38,8 @@ export function EmailLogin(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ export function EmailResetPassword(): JSX.Element {
|
|||
const router = useRouter()
|
||||
const [token, setToken] = useState<string | undefined>(undefined)
|
||||
const [password, setPassword] = useState<string>('')
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
|
@ -45,7 +44,8 @@ export function EmailResetPassword(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<VStack
|
||||
|
|
|
|||
|
|
@ -19,12 +19,10 @@ export function EmailSignup(): JSX.Element {
|
|||
const [password, setPassword] = useState<string | undefined>(undefined)
|
||||
const [fullname, setFullname] = useState<string | undefined>(undefined)
|
||||
const [username, setUsername] = useState<string | undefined>(undefined)
|
||||
const [debouncedUsername, setDebouncedUsername] = useState<
|
||||
string | undefined
|
||||
>(undefined)
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [debouncedUsername, setDebouncedUsername] =
|
||||
useState<string | undefined>(undefined)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
|
@ -60,7 +58,8 @@ export function EmailSignup(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@ export default function InvitePage(): JSX.Element {
|
|||
const router = useRouter()
|
||||
const { viewerData, viewerDataError, isLoading } = useGetViewerQuery()
|
||||
const { inviteCode } = router.query
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [errorMessage, setErrorMessage] =
|
||||
useState<string | undefined>(undefined)
|
||||
|
||||
// Check if the user is logged in and display an error message if they are not
|
||||
useEffect(() => {
|
||||
|
|
@ -77,7 +76,8 @@ export default function InvitePage(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ export default function ImportUploader(): JSX.Element {
|
|||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: 'rgb(224 224 224) 9px 9px 9px -9px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText
|
||||
|
|
|
|||
218
packages/web/pages/tools/import/matter-archive.tsx
Normal file
218
packages/web/pages/tools/import/matter-archive.tsx
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import { ChangeEvent, useCallback, useMemo, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
|
||||
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { applyStoredTheme } from '../../../lib/themeUpdater'
|
||||
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
VStack,
|
||||
} from '../../../components/elements/LayoutPrimitives'
|
||||
|
||||
import 'antd/dist/antd.compact.css'
|
||||
import { StyledText } from '../../../components/elements/StyledText'
|
||||
import { ProfileLayout } from '../../../components/templates/ProfileLayout'
|
||||
import {
|
||||
uploadImportFileRequestMutation,
|
||||
UploadImportFileType,
|
||||
} from '../../../lib/networking/mutations/uploadImportFileMutation'
|
||||
import { Button } from '../../../components/elements/Button'
|
||||
import Dropzone, { useDropzone } from 'react-dropzone'
|
||||
|
||||
import { SyncLoader } from 'react-spinners'
|
||||
import { theme } from '../../../components/tokens/stitches.config'
|
||||
import { Tray } from 'phosphor-react'
|
||||
|
||||
type UploadState = 'none' | 'uploading' | 'completed'
|
||||
|
||||
export default function ImportUploader(): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>()
|
||||
const [file, setFile] = useState<File>()
|
||||
const [type, setType] = useState<UploadImportFileType>()
|
||||
const [uploadState, setUploadState] = useState<UploadState>('none')
|
||||
const { acceptedFiles, getRootProps, getInputProps } = useDropzone()
|
||||
|
||||
const onDropAccepted = async (acceptedFiles: File[]) => {
|
||||
const contentType = 'application/zip'
|
||||
const file = acceptedFiles.find(() => true)
|
||||
if (!file) {
|
||||
setErrorMessage('No file selected.')
|
||||
return
|
||||
}
|
||||
|
||||
setUploadState('uploading')
|
||||
|
||||
try {
|
||||
const result = await uploadImportFileRequestMutation(
|
||||
UploadImportFileType.MATTER,
|
||||
contentType
|
||||
)
|
||||
|
||||
if (result && result.uploadSignedUrl) {
|
||||
const uploadRes = await fetch(result.uploadSignedUrl, {
|
||||
method: 'PUT',
|
||||
body: file,
|
||||
headers: {
|
||||
'content-type': contentType,
|
||||
'content-length': `${file.size}`,
|
||||
},
|
||||
})
|
||||
setUploadState('completed')
|
||||
} else {
|
||||
setErrorMessage(
|
||||
'Unable to create file upload. Please ensure you are logged in.'
|
||||
)
|
||||
setUploadState('none')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('caught error', error)
|
||||
if (error == 'UPLOAD_DAILY_LIMIT_EXCEEDED') {
|
||||
setErrorMessage('You have exceeded your maximum daily upload limit.')
|
||||
}
|
||||
setUploadState('none')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ProfileLayout>
|
||||
<VStack
|
||||
alignment="start"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
}}
|
||||
>
|
||||
<StyledText
|
||||
style="modalHeadline"
|
||||
css={{
|
||||
color: theme.colors.omnivoreGray.toString(),
|
||||
}}
|
||||
>
|
||||
Import Matter Archive
|
||||
</StyledText>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ pt: '10px', color: theme.colors.omnivoreGray.toString() }}
|
||||
>
|
||||
Omnivore supports uploading the Archive.zip file generated by
|
||||
exporting your data from the Matter app.
|
||||
</StyledText>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ color: theme.colors.omnivoreGray.toString() }}
|
||||
>
|
||||
To export your data from Matter, go to My Account, and choose Export
|
||||
data, this will send you an email with your data in a file
|
||||
Archive.zip. Upload that file using the uploader on this page.
|
||||
</StyledText>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ color: theme.colors.omnivoreGray.toString() }}
|
||||
>
|
||||
<a href="https://docs.omnivore.app/">More info</a>
|
||||
</StyledText>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{ pt: '20px', color: theme.colors.omnivoreGray.toString() }}
|
||||
>
|
||||
<b>Note:</b> Please note you are limited to three import uploads per a
|
||||
day, and the maximum file size is 10MB.
|
||||
</StyledText>
|
||||
<VStack css={{ pt: '36px', width: '100%' }}>
|
||||
{uploadState == 'completed' ? (
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{
|
||||
pt: '10px',
|
||||
pb: '20px',
|
||||
color: theme.colors.omnivoreGray.toString(),
|
||||
}}
|
||||
>
|
||||
Your upload has completed. Please note that it can take some time
|
||||
for your library to be updated. You will be sent an email when the
|
||||
process completes.
|
||||
</StyledText>
|
||||
) : (
|
||||
<>
|
||||
<Box css={{ width: '100%' }}>
|
||||
<Dropzone
|
||||
accept={{ 'application/zip': ['.zip'] }}
|
||||
multiple={false}
|
||||
maxSize={1e7}
|
||||
onDropAccepted={onDropAccepted}
|
||||
disabled={uploadState == 'uploading'}
|
||||
>
|
||||
{({ getRootProps, getInputProps }) => (
|
||||
<div {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
<VStack
|
||||
alignment="center"
|
||||
css={{
|
||||
borderRadius: '8px',
|
||||
border: '1px dashed #d9d9d9',
|
||||
minHeight: '64px',
|
||||
mb: '10px',
|
||||
pt: '10px',
|
||||
}}
|
||||
>
|
||||
{uploadState == 'uploading' ? (
|
||||
<SyncLoader
|
||||
color={theme.colors.omnivoreGray.toString()}
|
||||
size={8}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Tray
|
||||
size={34}
|
||||
color={theme.colors.omnivoreGray.toString()}
|
||||
/>
|
||||
<StyledText
|
||||
style="caption"
|
||||
css={{
|
||||
pt: '10px',
|
||||
color: theme.colors.omnivoreGray.toString(),
|
||||
}}
|
||||
>
|
||||
Click or Drag Archive.zip file here to upload
|
||||
</StyledText>
|
||||
</>
|
||||
)}
|
||||
</VStack>
|
||||
</div>
|
||||
)}
|
||||
</Dropzone>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{uploadState == 'completed' && (
|
||||
<VStack css={{ width: '100%' }} alignment="center">
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
window.location.href = '/home'
|
||||
e.preventDefault()
|
||||
}}
|
||||
style="ctaDarkYellow"
|
||||
>
|
||||
Return to Library
|
||||
</Button>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
<StyledText style="error">{errorMessage}</StyledText>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</ProfileLayout>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue