Merge pull request #976 from omnivore-app/895
#895 - Improve table used on the /settings/api page
This commit is contained in:
@ -356,6 +356,11 @@ export function authRouter() {
|
||||
cors<express.Request>(corsConfig),
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const { email, password } = req.body
|
||||
if (!email || !password) {
|
||||
res.redirect(`${env.client.url}/email-login?errorCodes=AUTH_FAILED`)
|
||||
return
|
||||
}
|
||||
|
||||
const query = `
|
||||
mutation login{
|
||||
login(input: {
|
||||
@ -418,6 +423,11 @@ export function authRouter() {
|
||||
cors<express.Request>(corsConfig),
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const { email, password, name, username, bio } = req.body
|
||||
if (!email || !password || !name || !username) {
|
||||
res.redirect(`${env.client.url}/email-signup?errorCodes=BAD_DATA`)
|
||||
return
|
||||
}
|
||||
|
||||
const query = `
|
||||
mutation signup {
|
||||
signup(input: {
|
||||
@ -425,7 +435,7 @@ export function authRouter() {
|
||||
password: "${password}",
|
||||
name: "${name}",
|
||||
username: "${username}",
|
||||
bio: "${bio}"
|
||||
bio: "${bio ?? ''}"
|
||||
}) {
|
||||
__typename
|
||||
... on SignupSuccess {
|
||||
|
||||
@ -217,3 +217,22 @@ export const Button = styled('button', {
|
||||
style: 'ctaWhite',
|
||||
},
|
||||
})
|
||||
|
||||
export const IconButton = styled(Button, {
|
||||
variants: {
|
||||
style: {
|
||||
ctaWhite: {
|
||||
color: 'red',
|
||||
padding: '10px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid $grayBorder',
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: 6,
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -34,6 +34,7 @@ const Modal = styled(Content, {
|
||||
boxShadow: theme.shadows.cardBoxShadow.toString(),
|
||||
position: 'fixed',
|
||||
'&:focus': { outline: 'none' },
|
||||
zIndex:'1',
|
||||
})
|
||||
|
||||
export const ModalContent = styled(Modal, {
|
||||
|
||||
@ -1,10 +1,20 @@
|
||||
import { Box, HStack, SpanBox, VStack } from './LayoutPrimitives'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
import {
|
||||
Table as ResponsiveTable,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
} from 'react-super-responsive-table'
|
||||
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css'
|
||||
import { PencilSimple, Plus, Trash } from 'phosphor-react'
|
||||
import { Box, SpanBox, VStack } from './LayoutPrimitives'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { StyledText } from './StyledText'
|
||||
import { InfoLink } from './InfoLink'
|
||||
import { Button } from './Button'
|
||||
import { PencilSimple, Plus, Trash } from 'phosphor-react'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
import { IconButton } from './Button'
|
||||
|
||||
interface TableProps {
|
||||
heading: string
|
||||
@ -23,61 +33,25 @@ const HeaderWrapper = styled(Box, {
|
||||
},
|
||||
})
|
||||
|
||||
const TableCard = styled(Box, {
|
||||
backgroundColor: '$grayBg',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '10px 12px',
|
||||
const StyledTable = styled(ResponsiveTable, {
|
||||
margin: ' 0 auto',
|
||||
border: '0.5px solid $grayBgActive',
|
||||
backgroundColor: '$graySolid',
|
||||
borderCollapse: 'collapse',
|
||||
borderRadius: '5px',
|
||||
width: '100%',
|
||||
|
||||
mt: '$3',
|
||||
'&:hover': {
|
||||
border: '0.5px solid #FFD234',
|
||||
},
|
||||
'@md': {
|
||||
paddingLeft: '0',
|
||||
},
|
||||
})
|
||||
const TableBody = styled(Tbody, {
|
||||
backgroundColor: '$grayBg',
|
||||
})
|
||||
|
||||
const TableHeading = styled(Box, {
|
||||
backgroundColor: '$grayBgActive',
|
||||
border: '1px solid rgba(0, 0, 0, 0.06)',
|
||||
display: 'none',
|
||||
alignItems: 'center',
|
||||
padding: '14px 0 14px 40px',
|
||||
borderRadius: '5px 5px 0px 0px',
|
||||
width: '100%',
|
||||
'@md': {
|
||||
display: 'flex',
|
||||
},
|
||||
})
|
||||
|
||||
const Input = styled('input', {
|
||||
backgroundColor: 'transparent',
|
||||
color: '$grayTextContrast',
|
||||
marginTop: '5px',
|
||||
'&[disabled]': {
|
||||
border: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
const IconButton = styled(Button, {
|
||||
variants: {
|
||||
style: {
|
||||
ctaWhite: {
|
||||
color: 'red',
|
||||
padding: '10px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
border: '1px solid $grayBorder',
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: 6,
|
||||
width: 40,
|
||||
height: 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
const TableRow = styled(Tr, {
|
||||
border: '0 !important',
|
||||
borderTop: '0.5px solid $grayBgActive !important',
|
||||
})
|
||||
|
||||
export function Table(props: TableProps): JSX.Element {
|
||||
@ -140,108 +114,87 @@ export function Table(props: TableProps): JSX.Element {
|
||||
)}
|
||||
</Box>
|
||||
</HeaderWrapper>
|
||||
<TableHeading>
|
||||
{props.headers.map((header, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
css={{
|
||||
flex: 'auto',
|
||||
}}
|
||||
>
|
||||
<StyledText
|
||||
key={index}
|
||||
style="menuTitle"
|
||||
css={{
|
||||
color: '$grayTextContrast',
|
||||
'@md': {
|
||||
fontWeight: '600',
|
||||
color: '$grayTextContrast',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
width: '240px',
|
||||
}}
|
||||
>
|
||||
{header}
|
||||
</StyledText>
|
||||
</Box>
|
||||
))}
|
||||
<Box
|
||||
css={{
|
||||
width: '120px',
|
||||
}}
|
||||
></Box>
|
||||
</TableHeading>
|
||||
{Array.from(props.rows.keys()).map((key, index) => (
|
||||
<TableCard
|
||||
key={index}
|
||||
css={{
|
||||
'&:hover': {
|
||||
background: 'rgba(255, 234, 159, 0.12)',
|
||||
},
|
||||
'@mdDown': {
|
||||
borderTopLeftRadius: index === 0 ? '5px' : '',
|
||||
borderTopRightRadius: index === 0 ? '5px' : '',
|
||||
},
|
||||
borderBottomLeftRadius: index == props.rows.size - 1 ? '5px' : '',
|
||||
borderBottomRightRadius: index == props.rows.size - 1 ? '5px' : '',
|
||||
padding: '10px 20px 10px 40px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
'@md': {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{Object.values(props.rows.get(key) || {}).map((cell, index) => (
|
||||
<HStack
|
||||
key={index}
|
||||
css={{
|
||||
flex: 'auto',
|
||||
display: 'flex',
|
||||
padding: '4px 4px 4px 0px',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
value={cell}
|
||||
disabled
|
||||
<StyledTable>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{props.headers.map((header: string, index: number) => (
|
||||
<Th key={index}>
|
||||
<SpanBox
|
||||
css={{
|
||||
width: '100%',
|
||||
textTransform: 'uppercase',
|
||||
display: 'flex',
|
||||
fontWeight: 600,
|
||||
padding: '20px 10px 20px 40px',
|
||||
color: '$grayTextContrast',
|
||||
fontSize: '$2',
|
||||
}}
|
||||
></Input>
|
||||
</HStack>
|
||||
>
|
||||
{header}
|
||||
</SpanBox>
|
||||
</Th>
|
||||
))}
|
||||
{props.onEdit && (
|
||||
<IconButton
|
||||
style="ctaWhite"
|
||||
css={{ mr: '$1', background: '$labelButtonsBg' }}
|
||||
onClick={() => {
|
||||
props.onEdit &&
|
||||
props.onEdit({ ...props.rows.get(key), id: key })
|
||||
}}
|
||||
>
|
||||
<PencilSimple size={24} color={iconColor} />
|
||||
</IconButton>
|
||||
)}
|
||||
{props.onDelete && (
|
||||
<IconButton
|
||||
style="ctaWhite"
|
||||
css={{ mr: '$1', background: '$labelButtonsBg' }}
|
||||
onClick={() => {
|
||||
props.onDelete && props.onDelete(key)
|
||||
}}
|
||||
>
|
||||
<Trash size={16} color={iconColor} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</TableCard>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
|
||||
<TableBody>
|
||||
{Array.from(props.rows.keys()).map((key, index) => (
|
||||
<TableRow key={index}>
|
||||
{Object.values(props.rows.get(key) || {}).map((cell, index) => (
|
||||
<Td key={index}>
|
||||
<SpanBox
|
||||
key={index}
|
||||
css={{
|
||||
display: 'flex',
|
||||
justifyContent: 'left',
|
||||
padding: '20px 10px 20px 40px',
|
||||
color: '$grayTextContrast',
|
||||
fontSize: '$1',
|
||||
}}
|
||||
>
|
||||
{cell}
|
||||
</SpanBox>
|
||||
</Td>
|
||||
))}
|
||||
|
||||
{props.onDelete && (
|
||||
<Td>
|
||||
<IconButton
|
||||
style="ctaWhite"
|
||||
css={{
|
||||
mr: '$1',
|
||||
background: '$labelButtonsBg',
|
||||
margin: '20px 10px 20px 40px',
|
||||
}}
|
||||
onClick={() => {
|
||||
props.onDelete && props.onDelete(key)
|
||||
}}
|
||||
>
|
||||
<Trash size={16} color={iconColor} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
)}
|
||||
{props.onEdit && (
|
||||
<Td>
|
||||
<IconButton
|
||||
style="ctaWhite"
|
||||
css={{
|
||||
mr: '$1',
|
||||
background: '$labelButtonsBg',
|
||||
margin: '20px 10px 20px 40px',
|
||||
}}
|
||||
onClick={() => {
|
||||
props.onEdit &&
|
||||
props.onEdit({ ...props.rows.get(key), id: key })
|
||||
}}
|
||||
>
|
||||
<PencilSimple size={24} color={iconColor} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTable>
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ export function EmailLogin(): JSX.Element {
|
||||
<BorderedFormInput
|
||||
key="email"
|
||||
type="text"
|
||||
name="email"
|
||||
value={email}
|
||||
placeholder="Email"
|
||||
onChange={(e) => { e.preventDefault(); setEmail(e.target.value); }}
|
||||
@ -63,6 +64,7 @@ export function EmailLogin(): JSX.Element {
|
||||
<BorderedFormInput
|
||||
key="password"
|
||||
type="password"
|
||||
name="password"
|
||||
value={password}
|
||||
placeholder="Password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-super-responsive-table": "^5.2.1",
|
||||
"react-topbar-progress-indicator": "^4.1.1",
|
||||
"react-twitter-widgets": "^1.10.0",
|
||||
"swr": "^1.0.1",
|
||||
|
||||
@ -10,6 +10,7 @@ import { revokeApiKeyMutation } from '../../lib/networking/mutations/revokeApiKe
|
||||
|
||||
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
|
||||
import { Table } from '../../components/elements/Table'
|
||||
|
||||
import { FormInputProps } from '../../components/elements/FormElements'
|
||||
import { FormModal } from '../../components/patterns/FormModal'
|
||||
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
|
||||
@ -57,9 +58,10 @@ export default function Api(): JSX.Element {
|
||||
usedAt: apiKey.usedAt
|
||||
? new Date(apiKey.usedAt).toISOString()
|
||||
: 'Never used',
|
||||
expiresAt: new Date(apiKey.expiresAt).getTime() != neverExpiresDate.getTime()
|
||||
? new Date(apiKey.expiresAt).toDateString()
|
||||
: 'Never',
|
||||
expiresAt:
|
||||
new Date(apiKey.expiresAt).getTime() != neverExpiresDate.getTime()
|
||||
? new Date(apiKey.expiresAt).toDateString()
|
||||
: 'Never',
|
||||
})
|
||||
)
|
||||
return rows
|
||||
@ -117,7 +119,7 @@ export default function Api(): JSX.Element {
|
||||
additionalDays = 365
|
||||
break
|
||||
case 'Never':
|
||||
break;
|
||||
break
|
||||
}
|
||||
const newExpires = additionalDays ? new Date() : neverExpiresDate
|
||||
if (additionalDays) {
|
||||
@ -126,7 +128,13 @@ export default function Api(): JSX.Element {
|
||||
setExpiresAt(newExpires)
|
||||
},
|
||||
type: 'select',
|
||||
options: ['in 7 days', 'in 30 days', 'in 90 days', 'in 1 year', 'Never'],
|
||||
options: [
|
||||
'in 7 days',
|
||||
'in 30 days',
|
||||
'in 90 days',
|
||||
'in 1 year',
|
||||
'Never',
|
||||
],
|
||||
value: defaultExpiresAt,
|
||||
},
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user