Landing page improvements and various supporting improvements
9
.github/workflows/run-tests.yaml
vendored
|
|
@ -11,10 +11,16 @@ on:
|
|||
paths-ignore:
|
||||
- 'apple/**'
|
||||
|
||||
env:
|
||||
NEXT_PUBLIC_APP_ENV: prod
|
||||
NEXT_PUBLIC_BASE_URL: http://localhost:3000
|
||||
NEXT_PUBLIC_SERVER_BASE_URL: http://localhost:4000
|
||||
NEXT_PUBLIC_HIGHLIGHTS_BASE_URL: http://localhost:3000
|
||||
|
||||
jobs:
|
||||
run-code-tests:
|
||||
name: Run Codebase tests
|
||||
runs-on: ubuntu-latest-m
|
||||
runs-on: ${{ github.repository_owner == 'omnivore-app' && 'ubuntu-latest-m' || 'ubuntu-latest' }}
|
||||
services:
|
||||
postgres:
|
||||
image: ankane/pgvector
|
||||
|
|
@ -71,6 +77,7 @@ jobs:
|
|||
- name: TypeScript Build and Lint
|
||||
run: |
|
||||
source ~/.nvm/nvm.sh
|
||||
env
|
||||
yarn build
|
||||
yarn lint
|
||||
- name: Tests
|
||||
|
|
|
|||
2
.gitignore
vendored
|
|
@ -69,3 +69,5 @@ data.json
|
|||
# android
|
||||
*.aab
|
||||
*.apk
|
||||
|
||||
tsconfig.tsbuildinfo
|
||||
|
|
|
|||
|
|
@ -10,5 +10,8 @@
|
|||
"express": "^4.18.1",
|
||||
"express-graphql": "^0.12.0",
|
||||
"graphql": "^16.8.1"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
package.json
|
|
@ -6,16 +6,16 @@
|
|||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"license": "UNLICENSED",
|
||||
"license": "AGPL-3.0-only",
|
||||
"scripts": {
|
||||
"test": "lerna run --no-bail test --ignore @omnivore/web",
|
||||
"lint": "lerna run lint --ignore @omnivore/web",
|
||||
"build": "lerna run build --ignore @omnivore/web",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"test": "lerna run --no-bail test",
|
||||
"lint": "lerna run lint",
|
||||
"build": "lerna run build",
|
||||
"test:scoped:example": "lerna run test --scope={@omnivore/pdf-handler,@omnivore/web}",
|
||||
"gql-typegen": "graphql-codegen",
|
||||
"deploy:web": "vercel --prod"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@ardatan/aggregate-error": "^0.0.6",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
|
|
@ -33,11 +33,10 @@
|
|||
"graphql-tag": "^2.11.0",
|
||||
"lerna": "^7.4.1",
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.4.3"
|
||||
"typescript": "4.5.2"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.1",
|
||||
"yarn": "1.22.19"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"start": "node dist/server.js",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"lint:fix": "eslint src --fix --ext ts,js,tsx,jsx",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"test": "nyc mocha -r ts-node/register --config mocha-config.json --timeout 10000",
|
||||
"copy-files": "copyfiles -u 1 src/**/*.html dist/"
|
||||
},
|
||||
|
|
@ -145,7 +146,6 @@
|
|||
"node": "18.16.1"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.1",
|
||||
"yarn": "1.22.19"
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
"webpack-dev-server": "^4.7.4"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.0",
|
||||
"yarn": "1.22.10"
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ const config: Configuration = {
|
|||
],
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js'],
|
||||
fallback: {
|
||||
stream: false,
|
||||
fs: false,
|
||||
zlib: false,
|
||||
}
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
|
|
|
|||
|
|
@ -20,5 +20,8 @@
|
|||
"start_gcf": "npx functions-framework --port=9090 --target=puppeteer",
|
||||
"start_preview": "npx functions-framework --target=preview",
|
||||
"test": "mocha test/*.js"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc"
|
||||
|
|
@ -38,5 +39,8 @@
|
|||
"redis": "^4.3.1",
|
||||
"underscore": "^1.13.6",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ const getTweetIds = async (
|
|||
timeout: 60000, // 60 seconds
|
||||
})
|
||||
|
||||
return (await page.evaluate(async (author) => {
|
||||
return await page.evaluate(async (author) => {
|
||||
/**
|
||||
* Wait for `ms` amount of milliseconds
|
||||
* @param {number} ms
|
||||
|
|
@ -278,7 +278,7 @@ const getTweetIds = async (
|
|||
}
|
||||
|
||||
return ids
|
||||
}, author)) as string[]
|
||||
}, author)
|
||||
} catch (error) {
|
||||
console.error('Error getting tweets', error)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"devDependencies": {},
|
||||
"volta": {
|
||||
"node": "18.16.1",
|
||||
"yarn": "1.22.10"
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"description": "",
|
||||
"scripts": {
|
||||
"migrate": "ts-node ./migrate.ts",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"generate": "plop"
|
||||
},
|
||||
"author": "",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc && yarn copy-files",
|
||||
|
|
@ -52,5 +53,8 @@
|
|||
"unzip-stream": "^0.3.1",
|
||||
"urlsafe-base64": "^1.0.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"keywords": [],
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -42,5 +43,8 @@
|
|||
"parse-multipart-data": "^1.2.1",
|
||||
"rfc2047": "^4.0.1",
|
||||
"showdown": "^2.1.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"keywords": [],
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -33,5 +34,8 @@
|
|||
"axios": "^0.27.2",
|
||||
"concurrently": "^7.0.0",
|
||||
"pdfjs-dist": "^2.9.359"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,5 +25,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "mocha test/*.js"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -28,5 +29,8 @@
|
|||
"axios": "^1.4.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,5 +41,8 @@
|
|||
"dependencies": {
|
||||
"html-entities": "^2.3.2",
|
||||
"parse-srcset": "^1.0.2"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -28,5 +29,8 @@
|
|||
"dotenv": "^16.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"rss-parser": "^3.13.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -26,5 +27,8 @@
|
|||
"dotenv": "^16.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"search-query-parser": "^1.6.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"keywords": [],
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -45,5 +46,8 @@
|
|||
"natural": "^6.2.0",
|
||||
"redis": "^4.3.1",
|
||||
"underscore": "^1.13.4"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||
"compile": "tsc",
|
||||
"build": "tsc",
|
||||
|
|
@ -30,5 +31,8 @@
|
|||
"jsonwebtoken": "^8.5.1",
|
||||
"linkedom": "^0.14.26",
|
||||
"urlsafe-base64": "^1.0.0"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,9 @@ export const Button = styled('button', {
|
|||
color: '#3D3D3D',
|
||||
bg: '#FFEA9F',
|
||||
p: '10px 15px',
|
||||
'&:hover': {
|
||||
'&:hover, &:focus': {
|
||||
bg: '$omnivoreCtaYellow',
|
||||
},
|
||||
'&:focus': {
|
||||
border: '1px solid $omnivoreCtaYellow',
|
||||
outline: '1px solid $omnivoreCtaYellow',
|
||||
},
|
||||
},
|
||||
cancelGeneric: {
|
||||
|
|
@ -45,18 +43,13 @@ export const Button = styled('button', {
|
|||
border: '1px solid transparent',
|
||||
p: '10px 15px',
|
||||
bg: 'transparent',
|
||||
'&:hover': {
|
||||
'&:hover, &:focus': {
|
||||
bg: '#EBEBEB',
|
||||
},
|
||||
'&:focus': {
|
||||
outline: 'none !important',
|
||||
border: '1px solid $omnivoreCtaYellow',
|
||||
outline: '1px solid $omnivoreCtaYellow',
|
||||
},
|
||||
},
|
||||
ctaOutlineYellow: {
|
||||
boxSizing: 'border-box',
|
||||
'-moz-box-sizing': 'border-box',
|
||||
'-webkit-box-sizing': 'border-box',
|
||||
borderColor: 'unset',
|
||||
border: '1px solid $omnivoreCtaYellow',
|
||||
fontSize: '14px',
|
||||
|
|
@ -159,7 +152,6 @@ export const Button = styled('button', {
|
|||
outlineColor: 'rgba(0, 0, 0, 0)',
|
||||
border: '1px solid rgba(0, 0, 0, 0.06)',
|
||||
cursor: 'pointer',
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
ctaModal: {
|
||||
height: '32px',
|
||||
|
|
@ -172,7 +164,6 @@ export const Button = styled('button', {
|
|||
border: '1px solid $grayBorder',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '8px',
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
ctaSecondary: {
|
||||
color: '$grayText',
|
||||
|
|
@ -245,7 +236,6 @@ export const Button = styled('button', {
|
|||
'&:hover': {
|
||||
opacity: 0.7,
|
||||
},
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
highlightBarIcon: {
|
||||
p: '0px',
|
||||
|
|
@ -257,7 +247,6 @@ export const Button = styled('button', {
|
|||
opacity: 0.5,
|
||||
},
|
||||
|
||||
'&:focus': { outline: 'none' },
|
||||
},
|
||||
articleActionIcon: {
|
||||
bg: 'transparent',
|
||||
|
|
|
|||
|
|
@ -11,37 +11,35 @@ type HeaderNavLinkProps = {
|
|||
|
||||
export function HeaderNavLink(props: HeaderNavLinkProps): JSX.Element {
|
||||
return (
|
||||
<Link passHref href={props.href}>
|
||||
<a style={{ textDecoration: 'none' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
<Link passHref href={props.href} style={{ textDecoration: 'none' }}>
|
||||
<VStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{
|
||||
cursor: 'pointer',
|
||||
px: '$3',
|
||||
color: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '100%',
|
||||
}}
|
||||
>
|
||||
<StyledText style="navLink">{props.text}</StyledText>
|
||||
<Box
|
||||
css={{
|
||||
cursor: 'pointer',
|
||||
px: '$3',
|
||||
color: 'inherit',
|
||||
textDecoration: 'inherit',
|
||||
fontFamily: 'inherit',
|
||||
fontSize: '100%',
|
||||
width: '100%',
|
||||
bg: 'rgb(255, 210, 52)',
|
||||
height: '2px',
|
||||
opacity: props.isActive ? 1 : 0,
|
||||
display: props.isActive ? 'unset' : 'none',
|
||||
mt: '4px',
|
||||
animation: `${expandWidthAnim('0%', '100%')} 0.2s ease-out`,
|
||||
'&:hover': {
|
||||
opacity: props.isActive ? 0.7 : 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<StyledText style="navLink">{props.text}</StyledText>
|
||||
<Box
|
||||
css={{
|
||||
width: '100%',
|
||||
bg: 'rgb(255, 210, 52)',
|
||||
height: '2px',
|
||||
opacity: props.isActive ? 1 : 0,
|
||||
display: props.isActive ? 'unset' : 'none',
|
||||
mt: '4px',
|
||||
animation: `${expandWidthAnim('0%', '100%')} 0.2s ease-out`,
|
||||
'&:hover': {
|
||||
opacity: props.isActive ? 0.7 : 0,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</VStack>
|
||||
</a>
|
||||
/>
|
||||
</VStack>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useRef, useState } from 'react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { Box, HStack } from './LayoutPrimitives'
|
||||
import { StyledText } from './StyledText'
|
||||
|
|
@ -6,9 +6,14 @@ import {
|
|||
LabelColorDropdownProps,
|
||||
LabelOptionProps,
|
||||
} from '../../utils/settings-page/labels/types'
|
||||
import { TwitterPicker } from 'react-color'
|
||||
import { TwitterPicker as TwitterPicker_, TwitterPickerProps } from 'react-color'
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
|
||||
// TwitterPicker is a Class component, but the types are broken in React 18.
|
||||
// TODO: Maybe move away from this component, since it hasn't been updated for 3 years.
|
||||
// https://github.com/casesandberg/react-color/issues/883
|
||||
const TwitterPicker = TwitterPicker_ as unknown as React.FunctionComponent<TwitterPickerProps>
|
||||
|
||||
const DropdownMenuContent = styled(DropdownMenuPrimitive.Content, {
|
||||
borderRadius: 6,
|
||||
backgroundColor: '$grayBg',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import AutosizeInput from 'react-input-autosize'
|
||||
import AutosizeInput_, { AutosizeInputProps } from 'react-input-autosize'
|
||||
import { Box, SpanBox } from './LayoutPrimitives'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Label } from '../../lib/networking/fragments/labelFragment'
|
||||
|
|
@ -8,6 +8,11 @@ import { EditLabelChip } from './EditLabelChip'
|
|||
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
|
||||
import { EditLabelChipStack } from './EditLabelChipStack'
|
||||
|
||||
// AutosizeInput is a Class component, but the types are broken in React 18.
|
||||
// TODO: Maybe move away from this component, since it hasn't been updated for 3 years.
|
||||
// https://github.com/JedWatson/react-input-autosize/issues
|
||||
const AutosizeInput = AutosizeInput_ as unknown as React.FunctionComponent<AutosizeInputProps>
|
||||
|
||||
const MaxUnstackedLabels = 7
|
||||
|
||||
type LabelsPickerProps = {
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ const InternalOrExternalLink = (props: InternalOrExternalLinkProps) => {
|
|||
}}
|
||||
>
|
||||
{!isExternal ? (
|
||||
<Link href={props.link}>{props.children}</Link>
|
||||
<Link href={props.link} legacyBehavior>{props.children}</Link>
|
||||
) : (
|
||||
<a href={props.link} target="_blank" rel="noreferrer">
|
||||
{props.children}
|
||||
</a>
|
||||
)}
|
||||
</SpanBox>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export const SuggestionBox = (props: SuggestionBoxProps) => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export const TooltipContent = StyledContent
|
|||
export const TooltipArrow = StyledArrow
|
||||
|
||||
type TooltipWrappedProps = {
|
||||
children: React.ReactNode;
|
||||
tooltipContent: string;
|
||||
active?: boolean;
|
||||
tooltipSide?: TooltipPrimitive.TooltipContentProps['side']
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Image from 'next/image'
|
||||
|
||||
export function ChromeIcon(): JSX.Element {
|
||||
return <Image src="/static/icons/chrome@2x.png" width="24" height="24" />
|
||||
return <Image src="/static/icons/chrome@2x.png" width="24" height="24" alt="" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Image from 'next/image'
|
||||
|
||||
export function EdgeIcon(): JSX.Element {
|
||||
return <Image src="/static/icons/edge@2x.png" width="24" height="24" />
|
||||
return <Image src="/static/icons/edge@2x.png" width="24" height="24" alt="" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Image from 'next/image'
|
||||
|
||||
export function FirefoxIcon(): JSX.Element {
|
||||
return <Image src="/static/icons/firefox@2x.png" width="24" height="24" />
|
||||
return <Image src="/static/icons/firefox@2x.png" width="24" height="24" alt="" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,20 +14,22 @@ export function OmnivoreFestiveLogo(
|
|||
const href = props.href || '/home'
|
||||
|
||||
return (
|
||||
<Link passHref href={href}>
|
||||
<a
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src="/static/images/omnivore-logo-santa.png"
|
||||
width="27"
|
||||
height="27"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
(<Link
|
||||
passHref
|
||||
href={href}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
|
||||
<Image
|
||||
src="/static/images/omnivore-logo-santa.png"
|
||||
width="27"
|
||||
height="27"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
</Link>)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { ReactChildren } from 'react'
|
||||
import { config } from '../../tokens/stitches.config'
|
||||
|
||||
export type OmnivoreLogoBaseProps = {
|
||||
color?: string
|
||||
href?: string
|
||||
|
|
@ -15,23 +12,25 @@ export function OmnivoreLogoBase(props: OmnivoreLogoBaseProps): JSX.Element {
|
|||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<Link passHref href={href}>
|
||||
<a
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onClick={(event) => {
|
||||
const query = window.sessionStorage.getItem('q')
|
||||
if (query) {
|
||||
router.push(`/home?${query}`)
|
||||
event.preventDefault()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</a>
|
||||
<Link
|
||||
passHref
|
||||
href={href}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
onClick={(event) => {
|
||||
const query = window.sessionStorage.getItem('q')
|
||||
if (query) {
|
||||
router.push(`/home?${query}`)
|
||||
event.preventDefault()
|
||||
}
|
||||
}}
|
||||
tabIndex={-1}
|
||||
aria-label="Omnivore logo"
|
||||
>
|
||||
{props.children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Image from 'next/image'
|
||||
|
||||
export function SafariIcon(): JSX.Element {
|
||||
return <Image src="/static/icons/safari@2x.png" width="24" height="24" />
|
||||
return <Image src="/static/icons/safari@2x.png" width="24" height="24" alt="" />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ type NoteSectionProps = {
|
|||
|
||||
export function ArticleNotes(props: NoteSectionProps): JSX.Element {
|
||||
const saveText = useCallback(
|
||||
(text) => {
|
||||
(text: string) => {
|
||||
props.saveText(text)
|
||||
},
|
||||
[props]
|
||||
|
|
@ -78,7 +78,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
||||
|
||||
const saveText = useCallback(
|
||||
(text) => {
|
||||
(text: string) => {
|
||||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
annotation: text,
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||
const [errorSaving, setErrorSaving] = useState<string | undefined>(undefined)
|
||||
|
||||
const saveText = useCallback(
|
||||
(text, updateTime, interactive) => {
|
||||
(text: string, updateTime: Date, interactive: boolean) => {
|
||||
;(async () => {
|
||||
const success = await updateHighlightMutation({
|
||||
annotation: text,
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ export function LibraryHighlightGridCard(
|
|||
props: LibraryHighlightGridCardProps
|
||||
): JSX.Element {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const higlightCount = props.item.highlights?.length ?? 0
|
||||
const highlightCount = props.item.highlights?.length ?? 0
|
||||
const router = useRouter()
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
(highlightId: string) => {
|
||||
if (!router || !router.isReady || !props.viewer) {
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
|
|
@ -197,7 +197,7 @@ export function LibraryHighlightGridCard(
|
|||
event.preventDefault()
|
||||
}}
|
||||
>
|
||||
{`View ${higlightCount} highlight${higlightCount > 1 ? 's' : ''}`}
|
||||
{`View ${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`}
|
||||
<CaretDown
|
||||
size={10}
|
||||
weight="bold"
|
||||
|
|
|
|||
|
|
@ -33,13 +33,14 @@ export function About(props: AboutProps): JSX.Element {
|
|||
}}
|
||||
>
|
||||
<Box
|
||||
as="p"
|
||||
css={{
|
||||
fontWeight: '700',
|
||||
color: '#3D3D3D',
|
||||
fontSize: 45,
|
||||
lineHeight: '53px',
|
||||
padding: '10px',
|
||||
paddingBottom: '0px',
|
||||
padding: '10px 10px 0',
|
||||
margin: 0,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
|
|
@ -49,9 +50,11 @@ export function About(props: AboutProps): JSX.Element {
|
|||
readers.`}
|
||||
</Box>
|
||||
<Box
|
||||
as="p"
|
||||
css={{
|
||||
color: 'rgb(125, 125, 125)',
|
||||
padding: '10px',
|
||||
margin: 0,
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
fontWeight: '600',
|
||||
|
|
@ -64,9 +67,11 @@ export function About(props: AboutProps): JSX.Element {
|
|||
</Box>
|
||||
|
||||
<Box
|
||||
as="p"
|
||||
css={{
|
||||
color: 'rgb(125, 125, 125)',
|
||||
padding: '10px',
|
||||
margin: 0,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ export function ErrorLayout(props: ErrorLayoutProps): JSX.Element {
|
|||
</StyledText>
|
||||
</HStack>
|
||||
<SpanBox css={{ height: '64px' }} />
|
||||
<Link passHref href={viewerData?.me ? '/home' : '/login'}>
|
||||
<Link passHref href={viewerData?.me ? '/home' : '/login'} legacyBehavior>
|
||||
<Button style="ctaDarkYellow">
|
||||
{viewerData?.me ? 'Go Home' : 'Login'}
|
||||
</Button>
|
||||
</Link>
|
||||
</VStack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,21 +50,21 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||
>
|
||||
Save articles and read them later in our distraction-free reader.
|
||||
</StyledText>
|
||||
<Link passHref href="/about">
|
||||
<a style={{ textDecoration: 'none' }}>
|
||||
<StyledText
|
||||
css={{
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Learn More ->
|
||||
</StyledText>
|
||||
</a>
|
||||
<Link passHref href="/about" style={{ textDecoration: 'none' }}>
|
||||
|
||||
<StyledText
|
||||
css={{
|
||||
fontStyle: 'normal',
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '120%',
|
||||
m: '0px',
|
||||
color: '$omnivoreGray',
|
||||
}}
|
||||
>
|
||||
Learn More ->
|
||||
</StyledText>
|
||||
|
||||
</Link>
|
||||
|
||||
<SpanBox css={{ height: '24px' }} />
|
||||
|
|
@ -103,7 +103,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Link href="/auth/email-login" passHref>
|
||||
<Link href="/auth/email-login" passHref legacyBehavior>
|
||||
<StyledTextSpan
|
||||
style="actionLink"
|
||||
css={{ color: '$omnivoreGray', pt: '12px' }}
|
||||
|
|
@ -114,7 +114,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||
</VStack>
|
||||
<TermAndConditionsFooter />
|
||||
</VStack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function GoogleAuthButton() {
|
||||
|
|
@ -154,17 +154,17 @@ export function TermAndConditionsFooter(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
By signing up, you agree to Omnivore’s{' '}
|
||||
<Link href="/terms" passHref>
|
||||
<Link href="/terms" passHref legacyBehavior>
|
||||
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Terms of Service
|
||||
</StyledTextSpan>
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link href="/privacy" passHref>
|
||||
<Link href="/privacy" passHref legacyBehavior>
|
||||
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Privacy Policy
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { unstable_getImgProps as getImgProps } from 'next/image'
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
|
|
@ -9,6 +10,9 @@ import type { LoginFormProps } from './LoginForm'
|
|||
import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
|
||||
import featureFullWidthImage from '../../public/static/images/login/login-feature-image-full.png'
|
||||
import featureHalfWidthImage from '../../public/static/images/login/login-feature-image-half.png'
|
||||
|
||||
export function LoginLayout(props: LoginFormProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -86,7 +90,30 @@ function MediumLoginLayout(props: LoginFormProps) {
|
|||
)
|
||||
}
|
||||
|
||||
const srcSetToImageSet = (srcFallback: string, srcSet?: string): string => {
|
||||
if (!srcSet) return `url(${srcFallback})`
|
||||
|
||||
return `image-set( ${srcSet
|
||||
.split(', ')
|
||||
.map((subSrc) => {
|
||||
const [src, resolution] = subSrc.split(' ')
|
||||
return `url("${decodeURIComponent(src)}") ${resolution}`
|
||||
})
|
||||
.join(',')}
|
||||
)`
|
||||
}
|
||||
|
||||
function OmnivoreIllustration() {
|
||||
const { props: halfWidthImgProps } = getImgProps({
|
||||
src: featureHalfWidthImage,
|
||||
alt: '',
|
||||
})
|
||||
|
||||
const { props: fullWidthImgProps } = getImgProps({
|
||||
src: featureFullWidthImage,
|
||||
alt: '',
|
||||
})
|
||||
|
||||
return (
|
||||
<Box
|
||||
css={{
|
||||
|
|
@ -95,14 +122,12 @@ function OmnivoreIllustration() {
|
|||
marginLeft: 'auto',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundImage: `-webkit-image-set(
|
||||
url('/static/images/landingPage-feature@1x.png') 1x,
|
||||
url('/static/landing/landingPage-feature@2x.png') 2x
|
||||
)`,
|
||||
'background-image': `image-set(
|
||||
url('/static/images/landingPage-feature@1x.png') 1x,
|
||||
url('/static/landing/landingPage-feature@2x.png') 2x
|
||||
)`,
|
||||
backgroundPosition: 'left',
|
||||
backgroundImage: srcSetToImageSet(halfWidthImgProps.src, halfWidthImgProps.srcSet),
|
||||
|
||||
'@media (min-aspect-ratio: 2/1)': {
|
||||
backgroundImage: srcSetToImageSet(fullWidthImgProps.src, fullWidthImgProps.srcSet),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ function SettingsButton(props: SettingsButtonProps): JSX.Element {
|
|||
}, [props, router])
|
||||
|
||||
return (
|
||||
<Link href={props.destination} passHref title={props.name}>
|
||||
<Link href={props.destination} passHref title={props.name} legacyBehavior>
|
||||
<SpanBox
|
||||
css={{
|
||||
mx: '10px',
|
||||
|
|
@ -249,5 +249,5 @@ function SettingsButton(props: SettingsButtonProps): JSX.Element {
|
|||
{props.name}
|
||||
</SpanBox>
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
|
|||
const dropzoneRef = useRef<DropzoneRef | null>(null)
|
||||
|
||||
const openDialog = useCallback(
|
||||
(event) => {
|
||||
(event: React.MouseEvent) => {
|
||||
if (dropzoneRef.current) {
|
||||
dropzoneRef.current.open()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
|||
}, [highlights])
|
||||
|
||||
const handleSaveNoteText = useCallback(
|
||||
(text) => {
|
||||
(text: string) => {
|
||||
const changeTime = new Date()
|
||||
|
||||
setLastChanged(changeTime)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
|||
props.onClose(allAnnotations ?? [], deletedHighlights ?? [])
|
||||
}, [props, allAnnotations])
|
||||
|
||||
const handleAnnotationsChange = useCallback((allAnnotations) => {
|
||||
const handleAnnotationsChange = useCallback((allAnnotations: Highlight[]) => {
|
||||
setAllAnnotations(allAnnotations)
|
||||
}, [])
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
|||
}, [allAnnotations])
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
(highlightId: string) => {
|
||||
props.viewHighlightInReader(highlightId)
|
||||
handleClose()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ function FontControls(props: FontControlsProps): JSX.Element {
|
|||
})
|
||||
|
||||
const handleFontSizeChange = useCallback(
|
||||
(value) => {
|
||||
(value: number) => {
|
||||
readerSettings.actionHandler('setFontSize', value)
|
||||
},
|
||||
[readerSettings]
|
||||
|
|
@ -399,7 +399,7 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
|||
const { readerSettings } = props
|
||||
|
||||
const handleMarginWidthChange = useCallback(
|
||||
(value) => {
|
||||
(value: number) => {
|
||||
readerSettings.setMarginWidth(value)
|
||||
},
|
||||
[readerSettings]
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export function EmailLogin(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
Don't have an account?{' '}
|
||||
<Link href="/auth/email-signup" passHref>
|
||||
<Link href="/auth/email-signup" passHref legacyBehavior>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Sign up
|
||||
</StyledTextSpan>
|
||||
|
|
@ -140,7 +140,7 @@ export function EmailLogin(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
Forgot your password?{' '}
|
||||
<Link href="/auth/forgot-password" passHref>
|
||||
<Link href="/auth/forgot-password" passHref legacyBehavior>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Click here
|
||||
</StyledTextSpan>
|
||||
|
|
@ -148,5 +148,5 @@ export function EmailLogin(): JSX.Element {
|
|||
</StyledText>
|
||||
</VStack>
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export function EmailSignup(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
Already have an account?{' '}
|
||||
<Link href="/auth/email-login" passHref>
|
||||
<Link href="/auth/email-login" passHref legacyBehavior>
|
||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||
Login instead
|
||||
</StyledTextSpan>
|
||||
|
|
@ -215,5 +215,5 @@ export function EmailSignup(): JSX.Element {
|
|||
<TermAndConditionsFooter />
|
||||
</VStack>
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
<DropdownSeparator />
|
||||
<Link
|
||||
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
||||
>
|
||||
legacyBehavior>
|
||||
<StyledLinkItem
|
||||
onClick={(event) => {
|
||||
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
||||
|
|
@ -129,7 +129,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||
</Link>
|
||||
</Dropdown>
|
||||
</VStack>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const sortHighlights = (highlights: Highlight[]) => {
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
|||
}, [props.item.node.highlights])
|
||||
|
||||
const viewInReader = useCallback(
|
||||
(highlightId) => {
|
||||
(highlightId: string) => {
|
||||
if (!router || !router.isReady || !props.viewer) {
|
||||
showErrorToast('Error navigating to highlight')
|
||||
return
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
}, [libraryItems, activeCardId])
|
||||
|
||||
const getItem = useCallback(
|
||||
(itemId) => {
|
||||
(itemId: string) => {
|
||||
return libraryItems.find((item) => item.node.id === itemId)
|
||||
},
|
||||
[libraryItems]
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@ type EditButtonProps = {
|
|||
|
||||
function EditButton(props: EditButtonProps): JSX.Element {
|
||||
return (
|
||||
<Link href={props.destination} passHref>
|
||||
<Link href={props.destination} passHref legacyBehavior>
|
||||
<SpanBox
|
||||
css={{
|
||||
ml: '10px',
|
||||
|
|
@ -584,5 +584,5 @@ function EditButton(props: EditButtonProps): JSX.Element {
|
|||
{props.title}
|
||||
</SpanBox>
|
||||
</Link>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export function Webhooks(): JSX.Element {
|
|||
}}
|
||||
>
|
||||
<h3>{item.method}</h3>
|
||||
<p>{item.createdAt}</p>
|
||||
<p>{item.createdAt?.toLocaleDateString()}</p>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { HStack, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { styled } from '../../tokens/stitches.config'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
|
||||
|
|
@ -30,11 +30,29 @@ export function LandingFooter(): JSX.Element {
|
|||
textDecoration: 'underline',
|
||||
},
|
||||
},
|
||||
'@mdDown': {
|
||||
columns: 2,
|
||||
width: '100%',
|
||||
marginTop: "8px",
|
||||
marginBottom: "30px",
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<HStack css={containerStyles}>
|
||||
<HStack css={{ width: '100%', maxWidth: '1224px' }}>
|
||||
<Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
maxWidth: '1024px',
|
||||
'@md': {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack>
|
||||
<StyledText style="aboutFooter">Install</StyledText>
|
||||
<FooterList>
|
||||
|
|
@ -77,7 +95,7 @@ export function LandingFooter(): JSX.Element {
|
|||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="mailto:feedback@omnivore.app">Contact us via email</a>
|
||||
<a href="mailto:feedback@omnivore.app">Contact us via email</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.gg/h2z5rppzz9">
|
||||
|
|
@ -112,7 +130,7 @@ export function LandingFooter(): JSX.Element {
|
|||
</li>
|
||||
</FooterList>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo'
|
||||
import { Button } from '../../elements/Button'
|
||||
import Link from 'next/link'
|
||||
|
||||
const LoginButton = (): JSX.Element => {
|
||||
return (
|
||||
<Button
|
||||
as={Link}
|
||||
href="/login"
|
||||
style="ctaDarkYellow"
|
||||
css={{
|
||||
display: 'flex',
|
||||
marginLeft: 'auto',
|
||||
borderRadius: 4,
|
||||
border: 'unset',
|
||||
background: 'unset',
|
||||
color: '#3D3D3D',
|
||||
height: '42px',
|
||||
|
|
@ -19,10 +21,8 @@ const LoginButton = (): JSX.Element => {
|
|||
fontWeight: 'normal',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
document.location.href = '/login'
|
||||
e.preventDefault()
|
||||
textDecoration: "none",
|
||||
transition: "all ease-in 50ms"
|
||||
}}
|
||||
>
|
||||
Login
|
||||
|
|
|
|||
|
|
@ -1,50 +1,11 @@
|
|||
import { HStack, VStack, Box } from '../../elements/LayoutPrimitives'
|
||||
|
||||
type LandingSectionProps = {
|
||||
export interface LandingSectionProps {
|
||||
titleText: string
|
||||
descriptionText: React.ReactElement
|
||||
descriptionText: React.ReactElement | string | number
|
||||
icon?: React.ReactElement
|
||||
image: React.ReactElement
|
||||
}
|
||||
|
||||
const titleTextStyles = {
|
||||
fontWeight: '700',
|
||||
color: '#3D3D3D',
|
||||
lineHeight: 1.25,
|
||||
'@mdDown': {
|
||||
fontSize: 24,
|
||||
},
|
||||
'@md': {
|
||||
fontSize: '$5',
|
||||
},
|
||||
'@xl': {
|
||||
fontSize: 45,
|
||||
},
|
||||
}
|
||||
|
||||
const imageContainerStyles = {
|
||||
display: 'flex',
|
||||
width: '49%',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
'@md': {
|
||||
marginBottom: '60px',
|
||||
},
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
}
|
||||
|
||||
const layoutStyles = {
|
||||
width: '49%',
|
||||
alignSelf: 'start',
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
paddingTop: '30px',
|
||||
},
|
||||
paddingLeft: '30px',
|
||||
paddingRight: '30px',
|
||||
paddingBottom: '30px',
|
||||
imagePosition?: 'left' | 'right'
|
||||
}
|
||||
|
||||
export function LandingSection(props: LandingSectionProps): JSX.Element {
|
||||
|
|
@ -53,24 +14,72 @@ export function LandingSection(props: LandingSectionProps): JSX.Element {
|
|||
css={{
|
||||
width: '100%',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'row-reverse',
|
||||
flexDirection: (props?.imagePosition ?? 'left') === 'left' ? 'row-reverse' : 'row',
|
||||
marginBottom: 20,
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack distribution="center" alignment={'center'} css={layoutStyles}>
|
||||
<Box css={titleTextStyles}>{props.titleText}</Box>
|
||||
<VStack
|
||||
distribution="center"
|
||||
alignment="center"
|
||||
css={{
|
||||
width: '49%',
|
||||
alignSelf: 'start',
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
paddingTop: '30px',
|
||||
},
|
||||
paddingLeft: '30px',
|
||||
paddingRight: '30px',
|
||||
paddingBottom: '30px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
as="h2"
|
||||
css={{
|
||||
color: 'rgb(125, 125, 125)',
|
||||
fontWeight: '700',
|
||||
color: '#3D3D3D',
|
||||
lineHeight: 1.25,
|
||||
'@mdDown': {
|
||||
fontSize: 24,
|
||||
},
|
||||
'@md': {
|
||||
fontSize: '$5',
|
||||
},
|
||||
'@xl': {
|
||||
fontSize: 45,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.titleText}
|
||||
</Box>
|
||||
<Box
|
||||
as="p"
|
||||
css={{
|
||||
color: '#666',
|
||||
}}
|
||||
>
|
||||
{props.descriptionText}
|
||||
</Box>
|
||||
</VStack>
|
||||
<Box css={imageContainerStyles}>{props.image}</Box>
|
||||
<Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
width: '49%',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
'@md': {
|
||||
marginBottom: '60px',
|
||||
},
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.image}
|
||||
</Box>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
import Image from 'next/image'
|
||||
import { VStack, Box } from '../../elements/LayoutPrimitives'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { LandingSection } from './LandingSection'
|
||||
|
||||
type GetStartedButtonProps = {
|
||||
lang: 'en' | 'zh'
|
||||
}
|
||||
import landingPageHeroImage from '../../../public/static/images/landing/landing-00-hero.png'
|
||||
import landingSection1Image from '../../../public/static/images/landing/landing-01-save-it-now.png'
|
||||
import landingSection2Image from '../../../public/static/images/landing/landing-02-newsletters.png'
|
||||
import landingSection3Image from '../../../public/static/images/landing/landing-03-organisation.png'
|
||||
import landingSection4Image from '../../../public/static/images/landing/landing-04-highlights-and-notes.png'
|
||||
import landingSection5Image from '../../../public/static/images/landing/landing-05-sync.png'
|
||||
import landingSection6Image from '../../../public/static/images/landing/landing-06-tts.png'
|
||||
import landingSection7Image from '../../../public/static/images/landing/landing-07-oss.png'
|
||||
import Link from 'next/link'
|
||||
|
||||
export function GetStartedButton(props: GetStartedButtonProps): JSX.Element {
|
||||
export function GetStartedButton(props: { lang: 'en' | 'zh' }): JSX.Element {
|
||||
return (
|
||||
<Button
|
||||
as={Link}
|
||||
href="/login"
|
||||
style="ctaDarkYellow"
|
||||
css={{
|
||||
display: 'flex',
|
||||
borderRadius: 4,
|
||||
background: 'rgb(255, 210, 52)',
|
||||
background: '$omnivoreCtaYellow',
|
||||
padding: '12px 25px',
|
||||
color: '#3D3D3D',
|
||||
width: '172px',
|
||||
height: '42px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: '600',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
document.location.href = '/login'
|
||||
e.preventDefault()
|
||||
textDecoration: 'none',
|
||||
transition: 'background-color ease-out 50ms',
|
||||
'&:hover': {
|
||||
backgroundColor: '$omnivoreYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.lang == 'zh' ? `免费注册` : `Sign Up for Free`}
|
||||
|
|
@ -50,38 +56,6 @@ const containerStyles = {
|
|||
},
|
||||
}
|
||||
|
||||
const callToActionStyles = {
|
||||
background: 'white',
|
||||
borderRadius: '24px',
|
||||
boxSizing: 'border-box',
|
||||
border: '1px solid #D8D7D5',
|
||||
boxShadow:
|
||||
'0px 7px 8px rgba(32, 31, 29, 0.03), 0px 18px 24px rgba(32, 31, 29, 0.03)',
|
||||
padding: 40,
|
||||
marginTop: 64,
|
||||
minheight: 330,
|
||||
width: 'inherit',
|
||||
|
||||
'@md': {
|
||||
width: '100%',
|
||||
},
|
||||
'@xl': {
|
||||
width: '95%',
|
||||
},
|
||||
}
|
||||
|
||||
const callToActionText = {
|
||||
color: '#3D3D3D',
|
||||
fontWeight: '700',
|
||||
fontSize: 64,
|
||||
lineHeight: '1.25',
|
||||
textAlign: 'center',
|
||||
paddingBottom: '20px',
|
||||
'@mdDown': {
|
||||
fontSize: '32px',
|
||||
},
|
||||
}
|
||||
|
||||
type LandingSectionsContainerProps = {
|
||||
lang: 'en' | 'zh'
|
||||
}
|
||||
|
|
@ -98,7 +72,8 @@ const sections = [
|
|||
titleText: `先保存,后阅读`,
|
||||
descriptionText: `看到有趣的内容,但没时间阅读?不论是文章、PDF或是推特线程,只需将它们保存下来,等稍后有空再阅读。Omnivore 应用程序适用于iOS、Android 和主要网络浏览扩展程序。`,
|
||||
},
|
||||
imageIdx: `03`,
|
||||
image: landingSection1Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -111,7 +86,8 @@ const sections = [
|
|||
titleText: `集合邮件订阅`,
|
||||
descriptionText: `您不再需要到不同收件箱提取订阅的邮件,只要将它们发送到 Omnivore Library,即可在同一处随心阅读,不受其他电子邮件或 substack 的干扰。`,
|
||||
},
|
||||
imageIdx: `04`,
|
||||
image: landingSection2Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -125,7 +101,8 @@ const sections = [
|
|||
titleText: `按喜好组织阅读系统`,
|
||||
descriptionText: `我们不会限定您如何组织系统,只提供您所需的工具,如标签、过滤器和完整的文本索引搜索,让您按自己的喜好和需求设定组织规则。`,
|
||||
},
|
||||
imageIdx: `05`,
|
||||
image: landingSection3Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -139,7 +116,8 @@ const sections = [
|
|||
titleText: `添加高亮和注释`,
|
||||
descriptionText: `想提高阅读效率?积极动用大脑,为关键的段落添加高亮或注释,能提高您阅读记忆的保留。这些标注将永久保存在文件里,方便您随时搜索使用。`,
|
||||
},
|
||||
imageIdx: `06`,
|
||||
image: landingSection4Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -152,7 +130,8 @@ const sections = [
|
|||
titleText: `与您的“第二大脑”同步`,
|
||||
descriptionText: `Omnivore 应用程序能与个人知识管理系统如 Logseq 和 Obsidian 同步,让您轻而易举地综合所有保存文章、高亮和注释。`,
|
||||
},
|
||||
imageIdx: `07`,
|
||||
image: landingSection5Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -165,8 +144,8 @@ const sections = [
|
|||
titleText: `使用 text-to-speech 功能聆听阅读`,
|
||||
descriptionText: `使用TTS逼真、自然的人工智能声音为您阅读待读列表中的读物,让眼睛好好休息一下。这便是我们iOS 版Omnivore 应用程序的独家功能。`,
|
||||
},
|
||||
imageIdx: `08`,
|
||||
maxWidth: '85%',
|
||||
image: landingSection6Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
{
|
||||
en: {
|
||||
|
|
@ -180,7 +159,8 @@ const sections = [
|
|||
titleText: `开源软件给予您控制权`,
|
||||
descriptionText: `阅读是终身的活动,不应担心失去自己多年辛苦建立的图书馆。我们的开源平台,就是为了确保您的阅读不会受限于任何专有系统。`,
|
||||
},
|
||||
imageIdx: `09`,
|
||||
image: landingSection7Image,
|
||||
imageAlt: '',
|
||||
},
|
||||
]
|
||||
export function LandingSectionsContainer(
|
||||
|
|
@ -198,42 +178,65 @@ export function LandingSectionsContainer(
|
|||
},
|
||||
}}
|
||||
>
|
||||
<img
|
||||
height="647"
|
||||
width="1015"
|
||||
srcSet="/static/landing/landingPage-feature@1x.png,
|
||||
/static/landing/landingPage-feature@2x.png 2x,
|
||||
/static/landing/landingPage-feature@3x.png 3x"
|
||||
alt="landingHero-1"
|
||||
<Image
|
||||
src={landingPageHeroImage}
|
||||
alt="Hero image"
|
||||
sizes="(max-width: 1024px) 100vw"
|
||||
style={{
|
||||
width: '85%',
|
||||
maxWidth: '85%',
|
||||
height: 'auto',
|
||||
}}
|
||||
priority
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{sections.map((section) => {
|
||||
{sections.map((section, sectionIndex) => {
|
||||
return (
|
||||
<LandingSection
|
||||
key={section.imageIdx}
|
||||
key={sectionIndex}
|
||||
titleText={section[props.lang].titleText}
|
||||
descriptionText={<p>{section[props.lang].descriptionText}</p>}
|
||||
descriptionText={section[props.lang].descriptionText}
|
||||
imagePosition={sectionIndex % 2 ? 'left' : 'right'}
|
||||
image={
|
||||
<img
|
||||
srcSet={`/static/landing/landingPage-${section.imageIdx}@1x.png,
|
||||
/static/landing/landingPage-${section.imageIdx}@2x.png 2x,
|
||||
/static/landing/landingPage-${section.imageIdx}@3x.png 3x`}
|
||||
alt={`landing-${section.imageIdx}`}
|
||||
style={{ maxWidth: section.maxWidth ?? '100%' }}
|
||||
<Image
|
||||
alt={section.imageAlt}
|
||||
src={section.image}
|
||||
sizes="(max-width: 512px) 50vw, (max-width: 512px) 100vw"
|
||||
style={{ maxWidth: '100%', height: 'auto' }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
<VStack alignment="center" css={callToActionStyles}>
|
||||
<VStack alignment="center" css={{
|
||||
width: "100vw",
|
||||
backgroundColor: "#fff",
|
||||
paddingBottom: "40px",
|
||||
marginTop: "40px",
|
||||
borderTop: "1px solid var(--colors-omnivoreYellow)",
|
||||
borderBottom: "1px solid var(--colors-omnivoreYellow)",
|
||||
"@md": {
|
||||
marginTop: 0,
|
||||
}
|
||||
}}>
|
||||
{props.lang == 'en' && (
|
||||
<Box css={callToActionText}>Get Started With Omnivore Today</Box>
|
||||
<Box
|
||||
as="p"
|
||||
css={{
|
||||
color: '#3D3D3D',
|
||||
fontWeight: '700',
|
||||
fontSize: '2.5rem',
|
||||
lineHeight: '1.25',
|
||||
textAlign: 'center',
|
||||
marginBottom: '40px',
|
||||
'@mdDown': {
|
||||
fontSize: '2rem',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Get Started With Omnivore Today
|
||||
</Box>
|
||||
)}
|
||||
<GetStartedButton lang={props.lang} />
|
||||
</VStack>
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ const darkThemeSpec = {
|
|||
|
||||
labelButtonsBg: '#5F5E58',
|
||||
|
||||
// New theme, special naming to keep things straigh
|
||||
// New theme, special naming to keep things straight
|
||||
// once all switch over, we will rename
|
||||
// DARK
|
||||
colorScheme: 'dark',
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function disableWordSnap(str: string): boolean {
|
|||
}
|
||||
|
||||
const isWhitespace = (c?: string): boolean => {
|
||||
return !!c && /\u2014|\u2013|,|\s/.test(c)
|
||||
return !!c && /\u2014|\u2013|,|\s/.test(c);
|
||||
}
|
||||
|
||||
function findNextWord(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export function useSelection(
|
|||
)
|
||||
|
||||
const handleFinishTouch = useCallback(
|
||||
async (mouseEvent) => {
|
||||
async (mouseEvent: any) => {
|
||||
let wasDragEvent = false
|
||||
const tapAttributes = {
|
||||
tapX: mouseEvent.clientX,
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
|
|||
)
|
||||
|
||||
const keydownListener = useCallback(
|
||||
(keydownEvent) => {
|
||||
(keydownEvent: any) => {
|
||||
const { target } = keydownEvent
|
||||
if (!keydownEvent.key) return
|
||||
const key = keydownEvent.key.toLowerCase()
|
||||
|
|
@ -129,7 +129,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
|
|||
)
|
||||
|
||||
const keyupListener = useCallback(
|
||||
(keyupEvent) => {
|
||||
(keyupEvent: any) => {
|
||||
if (!keyupEvent.key) return
|
||||
const key = keyupEvent.key.toLowerCase()
|
||||
if (keys[key] === undefined) return
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const ContentSecurityPolicy = `
|
|||
|
||||
const moduleExports = {
|
||||
images: {
|
||||
formats: ['image/avif', 'image/webp'],
|
||||
domains: [
|
||||
'proxy-demo.omnivore-image-cache.app',
|
||||
'proxy-dev.omnivore-image-cache.app',
|
||||
|
|
@ -56,7 +57,7 @@ const moduleExports = {
|
|||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
];
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:build": "jest && next build",
|
||||
"test:typecheck": "tsc --noEmit",
|
||||
"upgrade-psdpdfkit": "cp -R '../../node_modules/pspdfkit/dist/pspdfkit-lib' public/pspdfkit-lib",
|
||||
"storybook": "start-storybook -p 6006 -s ./public",
|
||||
"build-storybook": "build-storybook -s public"
|
||||
|
|
@ -32,7 +33,6 @@
|
|||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@sentry/nextjs": "^7.42.0",
|
||||
"@stitches/react": "^1.2.5",
|
||||
"@types/react-input-autosize": "^2.2.1",
|
||||
"antd": "4.24.3",
|
||||
"axios": "^1.2.0",
|
||||
"color2k": "^2.0.0",
|
||||
|
|
@ -47,16 +47,16 @@
|
|||
"markdown-it": "^13.0.1",
|
||||
"match-sorter": "^6.3.1",
|
||||
"nanoid": "^3.1.29",
|
||||
"next": "^12.1.0",
|
||||
"next": "^13.5.6",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"phosphor-react": "^1.4.0",
|
||||
"posthog-js": "^1.78.2",
|
||||
"pspdfkit": "^2022.2.3",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-input-autosize": "^3.0.0",
|
||||
|
|
@ -68,6 +68,7 @@
|
|||
"react-super-responsive-table": "^5.2.1",
|
||||
"react-topbar-progress-indicator": "^4.1.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.32.6",
|
||||
"swr": "^1.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"yet-another-react-lightbox": "^3.12.0"
|
||||
|
|
@ -93,14 +94,14 @@
|
|||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-color": "^3.0.6",
|
||||
"@types/react-dom": "^17.0.2",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-color": "^3.0.9",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@types/react-input-autosize": "^2.2.1",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"babel-jest": "^27.4.5",
|
||||
"babel-loader": "^8.2.3",
|
||||
"eslint-config-next": "12.0.7",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
"eslint-plugin-functional": "^4.0.2",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"graphql": "^15.6.1",
|
||||
|
|
@ -108,7 +109,6 @@
|
|||
"storybook-addon-next-router": "^3.1.1"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.1",
|
||||
"yarn": "1.22.10"
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function LandingPage(): JSX.Element {
|
|||
description="Omnivore is the free, open source, read-it-later app for serious readers."
|
||||
/>
|
||||
|
||||
<About lang="en" />
|
||||
<About lang="zh" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@ import { locale, timeZone } from '../../lib/dateFormatting'
|
|||
import { SaveResponseData } from '../../lib/networking/mutations/saveUrlMutation'
|
||||
import { ssrFetcher } from '../../lib/networking/networkHelpers'
|
||||
|
||||
type Request = NextApiRequest & { cookies: { [key: string]: string } }
|
||||
type Response = NextApiResponse
|
||||
|
||||
const saveUrl = async (
|
||||
req: NextApiRequest,
|
||||
req: Request,
|
||||
url: URL,
|
||||
labels: string[] | undefined,
|
||||
state: string | undefined,
|
||||
|
|
@ -53,11 +56,7 @@ const saveUrl = async (
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): Promise<void> => {
|
||||
export default async function handler(req: Request, res: Response) {
|
||||
const urlStr = req.query['url']
|
||||
if (req.query['labels'] && typeof req.query['labels'] === 'string') {
|
||||
req.query['labels'] = [req.query['labels']]
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default function InvitePage(): JSX.Element {
|
|||
}, [isLoading, router, viewerData, viewerDataError])
|
||||
|
||||
const acceptClicked = useCallback(
|
||||
(event) => {
|
||||
(event: any) => {
|
||||
event?.stopPropagation()
|
||||
|
||||
if (!router.isReady) {
|
||||
|
|
@ -63,108 +63,106 @@ export default function InvitePage(): JSX.Element {
|
|||
[router, inviteCode]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMetaData title="Accept Invite - Omnivore" path="/invite" />
|
||||
<ProfileLayout>
|
||||
<VStack
|
||||
alignment="center"
|
||||
return <>
|
||||
<PageMetaData title="Accept Invite - Omnivore" path="/invite" />
|
||||
<ProfileLayout>
|
||||
<VStack
|
||||
alignment="center"
|
||||
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="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
You're invited
|
||||
</StyledText>
|
||||
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
padding: '16px',
|
||||
background: 'white',
|
||||
minWidth: '340px',
|
||||
width: '70vw',
|
||||
maxWidth: '576px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #3D3D3D',
|
||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||
mt: '0px',
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||
You're invited
|
||||
</StyledText>
|
||||
|
||||
You have been invited to join a recommendation group on Omnivore.
|
||||
Recommendation groups allow you to share articles with other group
|
||||
members.
|
||||
</StyledText>
|
||||
{errorMessage && (
|
||||
<StyledText
|
||||
style="action"
|
||||
style="error"
|
||||
css={{
|
||||
mt: '0px',
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
You have been invited to join a recommendation group on Omnivore.
|
||||
Recommendation groups allow you to share articles with other group
|
||||
members.
|
||||
{errorMessage}
|
||||
</StyledText>
|
||||
{errorMessage && (
|
||||
<StyledText
|
||||
style="error"
|
||||
css={{
|
||||
mt: '0px',
|
||||
pt: '4px',
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</StyledText>
|
||||
)}
|
||||
)}
|
||||
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
{viewerData?.me ? (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={acceptClicked}
|
||||
>
|
||||
Accept Invite
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={() => router.push('/login')}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
m: '0px',
|
||||
pt: '16px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
Don't have an Omnivore account?{' '}
|
||||
<Link href="/login" passHref>
|
||||
<StyledTextSpan
|
||||
style="actionLink"
|
||||
css={{ color: '$omnivoreGray' }}
|
||||
>
|
||||
Signup
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
</VStack>
|
||||
<div data-testid="invite-page-tag" />
|
||||
</ProfileLayout>
|
||||
</>
|
||||
)
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{
|
||||
gap: '10px',
|
||||
width: '100%',
|
||||
height: '80px',
|
||||
}}
|
||||
>
|
||||
{viewerData?.me ? (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={acceptClicked}
|
||||
>
|
||||
Accept Invite
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
style={'ctaDarkYellow'}
|
||||
onClick={() => router.push('/login')}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
<StyledText
|
||||
style="action"
|
||||
css={{
|
||||
m: '0px',
|
||||
pt: '16px',
|
||||
width: '100%',
|
||||
color: '$omnivoreLightGray',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'normal',
|
||||
}}
|
||||
>
|
||||
Don't have an Omnivore account?{' '}
|
||||
<Link href="/login" passHref legacyBehavior>
|
||||
<StyledTextSpan
|
||||
style="actionLink"
|
||||
css={{ color: '$omnivoreGray' }}
|
||||
>
|
||||
Signup
|
||||
</StyledTextSpan>
|
||||
</Link>
|
||||
</StyledText>
|
||||
</VStack>
|
||||
<div data-testid="invite-page-tag" />
|
||||
</ProfileLayout>
|
||||
</>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,145 +114,143 @@ export default function EmailsPage(): JSX.Element {
|
|||
return emailAddresses.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
||||
}, [emailAddresses])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsTable
|
||||
pageId="settings-emails-tag"
|
||||
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
||||
headerTitle="Address"
|
||||
createTitle="Create a new email address"
|
||||
createAction={createEmail}
|
||||
suggestionInfo={{
|
||||
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
||||
message:
|
||||
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
||||
docs: 'https://docs.omnivore.app/using/inbox.html',
|
||||
key: '--settings-emails-show-help',
|
||||
CTAText: 'Create an email address',
|
||||
onClickCTA: () => {
|
||||
createEmail()
|
||||
return <>
|
||||
<SettingsTable
|
||||
pageId="settings-emails-tag"
|
||||
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
||||
headerTitle="Address"
|
||||
createTitle="Create a new email address"
|
||||
createAction={createEmail}
|
||||
suggestionInfo={{
|
||||
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
||||
message:
|
||||
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
||||
docs: 'https://docs.omnivore.app/using/inbox.html',
|
||||
key: '--settings-emails-show-help',
|
||||
CTAText: 'Create an email address',
|
||||
onClickCTA: () => {
|
||||
createEmail()
|
||||
},
|
||||
}}
|
||||
>
|
||||
{sortedEmailAddresses.length > 0 ? (
|
||||
sortedEmailAddresses.map((email, i) => {
|
||||
return (
|
||||
<SettingsTableRow
|
||||
key={email.address}
|
||||
title={email.address}
|
||||
isLast={i === sortedEmailAddresses.length - 1}
|
||||
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
||||
deleteTitle="Delete"
|
||||
sublineElement={
|
||||
<StyledText
|
||||
css={{
|
||||
my: '5px',
|
||||
fontSize: '11px',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{`created ${formattedShortDate(email.createdAt)}, `}
|
||||
<Link href="/settings/subscriptions" legacyBehavior>{`${email.subscriptionCount} subscriptions`}</Link>
|
||||
</StyledText>
|
||||
}
|
||||
titleElement={
|
||||
<CopyTextBtnWrapper
|
||||
css={{
|
||||
marginLeft: '20px',
|
||||
'@mdDown': {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CopyTextButton
|
||||
text={email.address}
|
||||
type={TextType.EmailAddress}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
}
|
||||
extraElement={
|
||||
email.confirmationCode ? (
|
||||
<HStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
backgroundColor: '$grayBgActive',
|
||||
borderRadius: '6px',
|
||||
padding: '4px 4px 4px 0px',
|
||||
'@md': {
|
||||
width: '30%',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<StyledText
|
||||
css={{
|
||||
fontSize: '11px',
|
||||
'@md': {
|
||||
marginTop: '5px',
|
||||
},
|
||||
'@mdDown': {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
marginRight: '10px',
|
||||
}}
|
||||
>
|
||||
{`Gmail: ${email.confirmationCode}`}
|
||||
</StyledText>
|
||||
<Box>
|
||||
<CopyTextBtnWrapper>
|
||||
<CopyTextButton
|
||||
text={email.confirmationCode || ''}
|
||||
type={TextType.ConfirmationCode}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
</Box>
|
||||
</>
|
||||
</HStack>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
text={isValidating ? '-' : 'No Email Addresses Found'}
|
||||
/>
|
||||
)}
|
||||
<SpanBox
|
||||
css={{
|
||||
pt: '15px',
|
||||
fontSize: '12px',
|
||||
marginLeft: 'auto',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{sortedEmailAddresses.length > 0 ? (
|
||||
sortedEmailAddresses.map((email, i) => {
|
||||
return (
|
||||
<SettingsTableRow
|
||||
key={email.address}
|
||||
title={email.address}
|
||||
isLast={i === sortedEmailAddresses.length - 1}
|
||||
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
||||
deleteTitle="Delete"
|
||||
sublineElement={
|
||||
<StyledText
|
||||
css={{
|
||||
my: '5px',
|
||||
fontSize: '11px',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{`created ${formattedShortDate(email.createdAt)}, `}
|
||||
<Link href="/settings/subscriptions">{`${email.subscriptionCount} subscriptions`}</Link>
|
||||
</StyledText>
|
||||
}
|
||||
titleElement={
|
||||
<CopyTextBtnWrapper
|
||||
css={{
|
||||
marginLeft: '20px',
|
||||
'@mdDown': {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CopyTextButton
|
||||
text={email.address}
|
||||
type={TextType.EmailAddress}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
}
|
||||
extraElement={
|
||||
email.confirmationCode ? (
|
||||
<HStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
backgroundColor: '$grayBgActive',
|
||||
borderRadius: '6px',
|
||||
padding: '4px 4px 4px 0px',
|
||||
'@md': {
|
||||
width: '30%',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<StyledText
|
||||
css={{
|
||||
fontSize: '11px',
|
||||
'@md': {
|
||||
marginTop: '5px',
|
||||
},
|
||||
'@mdDown': {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
marginRight: '10px',
|
||||
}}
|
||||
>
|
||||
{`Gmail: ${email.confirmationCode}`}
|
||||
</StyledText>
|
||||
<Box>
|
||||
<CopyTextBtnWrapper>
|
||||
<CopyTextButton
|
||||
text={email.confirmationCode || ''}
|
||||
type={TextType.ConfirmationCode}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
</Box>
|
||||
</>
|
||||
</HStack>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
text={isValidating ? '-' : 'No Email Addresses Found'}
|
||||
/>
|
||||
)}
|
||||
<SpanBox
|
||||
css={{
|
||||
pt: '15px',
|
||||
fontSize: '12px',
|
||||
marginLeft: 'auto',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Link href="/settings/emails/recent">
|
||||
View recently received emails
|
||||
</Link>
|
||||
</SpanBox>
|
||||
</SettingsTable>
|
||||
<Link href="/settings/emails/recent">
|
||||
View recently received emails
|
||||
</Link>
|
||||
</SpanBox>
|
||||
</SettingsTable>
|
||||
|
||||
{confirmDeleteEmailId ? (
|
||||
<ConfirmationModal
|
||||
message={
|
||||
'Are you sure? You will stop receiving emails sent to this address.'
|
||||
}
|
||||
onAccept={async () => {
|
||||
await deleteEmail(confirmDeleteEmailId)
|
||||
setConfirmDeleteEmailId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
{confirmDeleteEmailId ? (
|
||||
<ConfirmationModal
|
||||
message={
|
||||
'Are you sure? You will stop receiving emails sent to this address.'
|
||||
}
|
||||
onAccept={async () => {
|
||||
await deleteEmail(confirmDeleteEmailId)
|
||||
setConfirmDeleteEmailId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
||||
/>
|
||||
) : null}
|
||||
</>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||
at{' '}
|
||||
<Link
|
||||
href={`/settings/emails?address=${subscription.newsletterEmail}`}
|
||||
>
|
||||
legacyBehavior>
|
||||
{subscription.newsletterEmail}
|
||||
</Link>
|
||||
</>
|
||||
|
|
@ -101,7 +101,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||
</StyledText>
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
|
|
@ -125,5 +125,5 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||
) : null}
|
||||
</>
|
||||
</SettingsTable>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 487 KiB After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 626 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 839 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 982 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 68 KiB |