Merge pull request #460 from omnivore-app/feature/subscription-ui
Add basic subscriptions UI
This commit is contained in:
@ -37,25 +37,22 @@ export const subscriptionsResolver = authorized<
|
||||
try {
|
||||
const sortBy = sort?.by === SortBy.UpdatedTime ? 'updatedAt' : 'createdAt'
|
||||
const sortOrder = sort?.order === SortOrder.Ascending ? 'ASC' : 'DESC'
|
||||
const user = await getRepository(User).findOne({
|
||||
where: { id: uid, subscriptions: { status: SubscriptionStatus.Active } },
|
||||
relations: {
|
||||
subscriptions: true,
|
||||
},
|
||||
order: {
|
||||
subscriptions: {
|
||||
[sortBy]: sortOrder,
|
||||
},
|
||||
},
|
||||
})
|
||||
const user = await getRepository(User).findOneBy({ id: uid })
|
||||
if (!user) {
|
||||
return {
|
||||
errorCodes: [SubscriptionsErrorCode.Unauthorized],
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptions = await getRepository(Subscription).find({
|
||||
where: { user: { id: uid }, status: SubscriptionStatus.Active },
|
||||
order: {
|
||||
[sortBy]: sortOrder,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
subscriptions: user.subscriptions || [],
|
||||
subscriptions,
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
@ -73,9 +70,12 @@ export const unsubscribeResolver = authorized<
|
||||
log.info('unsubscribeResolver')
|
||||
|
||||
try {
|
||||
const subscription = await getRepository(Subscription).findOneBy({
|
||||
name,
|
||||
user: { id: uid },
|
||||
const subscription = await getRepository(Subscription).findOne({
|
||||
where: {
|
||||
name,
|
||||
user: { id: uid },
|
||||
},
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!subscription) {
|
||||
return {
|
||||
|
||||
10
packages/db/migrations/0082.do.add_default_subscription_status.sql
Executable file
10
packages/db/migrations/0082.do.add_default_subscription_status.sql
Executable file
@ -0,0 +1,10 @@
|
||||
-- Type: DO
|
||||
-- Name: add_default_subscription_status
|
||||
-- Description: Add default value to subscription status field
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.subscriptions
|
||||
ALTER COLUMN status SET DEFAULT 'ACTIVE';
|
||||
|
||||
COMMIT;
|
||||
9
packages/db/migrations/0082.undo.add_default_subscription_status.sql
Executable file
9
packages/db/migrations/0082.undo.add_default_subscription_status.sql
Executable file
@ -0,0 +1,9 @@
|
||||
-- Type: UNDO
|
||||
-- Name: add_default_subscription_status
|
||||
-- Description: Add default value to subscription status field
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE omnivore.subscriptions ALTER status DROP DEFAULT;
|
||||
|
||||
COMMIT;
|
||||
229
packages/web/components/elements/Table.tsx
Normal file
229
packages/web/components/elements/Table.tsx
Normal file
@ -0,0 +1,229 @@
|
||||
import { Box, HStack, SpanBox, VStack } from './LayoutPrimitives'
|
||||
import { styled } from '../tokens/stitches.config'
|
||||
import { StyledText } from './StyledText'
|
||||
import { InfoLink } from './InfoLink'
|
||||
import { Button } from './Button'
|
||||
import { Plus, Trash } from 'phosphor-react'
|
||||
import { isDarkTheme } from '../../lib/themeUpdater'
|
||||
|
||||
interface TableProps {
|
||||
heading: string
|
||||
infoLink?: string
|
||||
onAdd?: () => void
|
||||
headers: string[]
|
||||
rows: string[][]
|
||||
onDelete?: (id: string) => void
|
||||
}
|
||||
|
||||
const HeaderWrapper = styled(Box, {
|
||||
width: '100%',
|
||||
'@md': {
|
||||
display: 'block',
|
||||
},
|
||||
})
|
||||
|
||||
const TableCard = styled(Box, {
|
||||
backgroundColor: '$grayBg',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '10px 12px',
|
||||
border: '0.5px solid $grayBgActive',
|
||||
width: '100%',
|
||||
|
||||
'&:hover': {
|
||||
border: '0.5px solid #FFD234',
|
||||
},
|
||||
'@md': {
|
||||
paddingLeft: '0',
|
||||
},
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export function Table(props: TableProps): JSX.Element {
|
||||
const iconColor = isDarkTheme() ? '#D8D7D5' : '#5F5E58'
|
||||
|
||||
return (
|
||||
<VStack
|
||||
distribution="center"
|
||||
css={{
|
||||
mx: '10px',
|
||||
color: '$grayText',
|
||||
paddingBottom: '5rem',
|
||||
paddingTop: '2rem',
|
||||
'@md': {
|
||||
m: '16px',
|
||||
alignSelf: 'center',
|
||||
mx: '42px',
|
||||
paddingTop: '0',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HeaderWrapper>
|
||||
<Box style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box>
|
||||
<StyledText style="fixedHeadline">{props.heading}</StyledText>
|
||||
</Box>
|
||||
{props.infoLink && <InfoLink href={props.infoLink} />}
|
||||
{props.onAdd && (
|
||||
<Button
|
||||
onClick={props.onAdd}
|
||||
style="ctaDarkYellow"
|
||||
css={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'none',
|
||||
'@md': {
|
||||
display: 'flex',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SpanBox>Add More</SpanBox>
|
||||
</SpanBox>
|
||||
<SpanBox
|
||||
css={{
|
||||
p: '0',
|
||||
display: 'flex',
|
||||
'@md': {
|
||||
display: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Plus size={24} />
|
||||
</SpanBox>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</HeaderWrapper>
|
||||
<TableHeading>
|
||||
{props.headers.map((header, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
css={{
|
||||
flex: 'auto',
|
||||
}}
|
||||
>
|
||||
<StyledText
|
||||
key={index}
|
||||
style="highlightTitle"
|
||||
css={{
|
||||
color: '$grayTextContrast',
|
||||
'@md': {
|
||||
fontWeight: '600',
|
||||
color: '$grayTextContrast',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{header}
|
||||
</StyledText>
|
||||
</Box>
|
||||
))}
|
||||
</TableHeading>
|
||||
{props.rows.map((row, 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.length - 1 ? '5px' : '',
|
||||
borderBottomRightRadius:
|
||||
index == props.rows.length - 1 ? '5px' : '',
|
||||
padding: '10px 20px 10px 40px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
css={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
'@md': {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{row.map((cell, index) => (
|
||||
<HStack
|
||||
key={index}
|
||||
css={{
|
||||
flex: 'auto',
|
||||
display: 'flex',
|
||||
padding: '4px 4px 4px 0px',
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
type="text"
|
||||
value={cell}
|
||||
disabled
|
||||
css={{
|
||||
width: '100%',
|
||||
}}
|
||||
></Input>
|
||||
</HStack>
|
||||
))}
|
||||
{props.onDelete && (
|
||||
<IconButton
|
||||
style="ctaWhite"
|
||||
css={{ mr: '$1', background: '$labelButtonsBg' }}
|
||||
onClick={() => {
|
||||
props.onDelete && props.onDelete(row[0])
|
||||
}}
|
||||
>
|
||||
<Trash size={16} color={iconColor} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</TableCard>
|
||||
))}
|
||||
</VStack>
|
||||
)
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import { ReactNode, useMemo, useState } from 'react'
|
||||
import { Box, HStack, VStack } from './../elements/LayoutPrimitives'
|
||||
import { HStack, VStack } from './../elements/LayoutPrimitives'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownSeparator,
|
||||
DropdownOption,
|
||||
DropdownSeparator,
|
||||
} from '../elements/DropdownElements'
|
||||
import { StyledText } from '../elements/StyledText'
|
||||
import { Button } from '../elements/Button'
|
||||
@ -18,6 +18,7 @@ export type HeaderDropdownAction =
|
||||
| 'navigate-to-emails'
|
||||
| 'navigate-to-labels'
|
||||
| 'navigate-to-profile'
|
||||
| 'navigate-to-subscriptions'
|
||||
| 'increaseFontSize'
|
||||
| 'decreaseFontSize'
|
||||
| 'logout'
|
||||
@ -38,19 +39,27 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
|
||||
return (
|
||||
<Dropdown triggerElement={props.triggerElement}>
|
||||
<VStack css={{ p: '$2' }}>
|
||||
<StyledText style='menuTitle'>Theme</StyledText>
|
||||
<StyledText style="menuTitle">Theme</StyledText>
|
||||
<HStack css={{ mt: '6px', mb: '6px', width: '100%', gap: '8px' }}>
|
||||
<Button style='themeSwitch' css={{ background: "#FFFFFF", width: '50%' }} onClick={() => {
|
||||
props.actionHandler('apply-lighter-theme')
|
||||
setCurrentTheme(currentThemeName())
|
||||
}}>
|
||||
{ isDark ? '' : '✓' }
|
||||
<Button
|
||||
style="themeSwitch"
|
||||
css={{ background: '#FFFFFF', width: '50%' }}
|
||||
onClick={() => {
|
||||
props.actionHandler('apply-lighter-theme')
|
||||
setCurrentTheme(currentThemeName())
|
||||
}}
|
||||
>
|
||||
{isDark ? '' : '✓'}
|
||||
</Button>
|
||||
<Button style='themeSwitch' css={{ background: "#3D3D3D", width: '50%' }} onClick={() => {
|
||||
props.actionHandler('apply-dark-theme')
|
||||
setCurrentTheme(currentThemeName())
|
||||
}}>
|
||||
{ isDark ? '✓' : '' }
|
||||
<Button
|
||||
style="themeSwitch"
|
||||
css={{ background: '#3D3D3D', width: '50%' }}
|
||||
onClick={() => {
|
||||
props.actionHandler('apply-dark-theme')
|
||||
setCurrentTheme(currentThemeName())
|
||||
}}
|
||||
>
|
||||
{isDark ? '✓' : ''}
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
@ -67,6 +76,10 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
|
||||
onSelect={() => props.actionHandler('navigate-to-labels')}
|
||||
title="Labels"
|
||||
/>
|
||||
{/* <DropdownOption
|
||||
onSelect={() => props.actionHandler('navigate-to-subscriptions')}
|
||||
title="Subscriptions"
|
||||
/> */}
|
||||
<DropdownOption
|
||||
onSelect={() => window.Intercom('show')}
|
||||
title="Feedback"
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { Box, HStack, SpanBox } from '../elements/LayoutPrimitives'
|
||||
import { Box, HStack } from '../elements/LayoutPrimitives'
|
||||
import { OmnivoreNameLogo } from './../elements/images/OmnivoreNameLogo'
|
||||
import { DropdownMenu, HeaderDropdownAction } from './../patterns/DropdownMenu'
|
||||
import { updateTheme } from '../../lib/themeUpdater'
|
||||
import { darkenTheme, lightenTheme, updateTheme } from '../../lib/themeUpdater'
|
||||
import { AvatarDropdown } from './../elements/AvatarDropdown'
|
||||
import { theme, ThemeId } from './../tokens/stitches.config'
|
||||
import { ThemeId } from './../tokens/stitches.config'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
ScrollOffsetChangeset,
|
||||
@ -12,7 +12,6 @@ import {
|
||||
import { useRouter } from 'next/router'
|
||||
import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts'
|
||||
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { darkenTheme, lightenTheme } from '../../lib/themeUpdater'
|
||||
import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { setupAnalytics } from '../../lib/analytics'
|
||||
import { Button } from '../elements/Button'
|
||||
@ -111,6 +110,9 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element {
|
||||
router.push(`/${props.user.profile.username}`)
|
||||
}
|
||||
break
|
||||
case 'navigate-to-subscriptions':
|
||||
router.push('/settings/subscriptions')
|
||||
break
|
||||
case 'logout':
|
||||
props.setShowLogoutConfirmation(true)
|
||||
break
|
||||
@ -187,13 +189,17 @@ function NavHeader(props: NavHeaderProps): JSX.Element {
|
||||
</HStack>
|
||||
|
||||
{props.toolbarControl && (
|
||||
<HStack distribution="end" alignment="center" css={{
|
||||
height: '100%', width: '100%',
|
||||
mr: '16px',
|
||||
display: props.alwaysDisplayToolbar ? 'flex' : 'none',
|
||||
'@lgDown': {
|
||||
display: 'flex',
|
||||
},
|
||||
<HStack
|
||||
distribution="end"
|
||||
alignment="center"
|
||||
css={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
mr: '16px',
|
||||
display: props.alwaysDisplayToolbar ? 'flex' : 'none',
|
||||
'@lgDown': {
|
||||
display: 'flex',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.toolbarControl}
|
||||
|
||||
39
packages/web/lib/networking/mutations/unsubscribeMutation.ts
Normal file
39
packages/web/lib/networking/mutations/unsubscribeMutation.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { gql } from 'graphql-request'
|
||||
import { gqlFetcher } from '../networkHelpers'
|
||||
import { Subscription } from '../queries/useGetSubscriptionsQuery'
|
||||
|
||||
type UnsubscribeResult = {
|
||||
unsubscribe: Unsubscribe
|
||||
errorCodes?: unknown[]
|
||||
}
|
||||
|
||||
type Unsubscribe = {
|
||||
subscription: Subscription
|
||||
}
|
||||
|
||||
export async function unsubscribeMutation(
|
||||
subscribeName: string
|
||||
): Promise<any | undefined> {
|
||||
const mutation = gql`
|
||||
mutation {
|
||||
unsubscribe(name: "${subscribeName}") {
|
||||
... on UnsubscribeSuccess {
|
||||
subscription {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on UnsubscribeError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
try {
|
||||
const data = (await gqlFetcher(mutation)) as UnsubscribeResult
|
||||
return data.errorCodes ? undefined : data.unsubscribe.subscription.id
|
||||
} catch (error) {
|
||||
console.log('unsubscribeMutation error', error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
import { gql } from 'graphql-request'
|
||||
import useSWR from 'swr'
|
||||
import { publicGqlFetcher } from '../networkHelpers'
|
||||
|
||||
export type SubscriptionStatus = 'ACTIVE' | 'DELETED' | 'UNSUBSCRIBED'
|
||||
|
||||
export type Subscription = {
|
||||
id: string
|
||||
name: string
|
||||
newsletterEmail: string
|
||||
|
||||
url?: string
|
||||
description?: string
|
||||
|
||||
status: SubscriptionStatus
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
type SubscriptionsQueryResponse = {
|
||||
isValidating: boolean
|
||||
subscriptions: Subscription[]
|
||||
revalidate: () => void
|
||||
}
|
||||
|
||||
type SubscriptionsResponseData = {
|
||||
subscriptions: SubscriptionsData
|
||||
}
|
||||
|
||||
type SubscriptionsData = {
|
||||
subscriptions: unknown
|
||||
}
|
||||
|
||||
export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
|
||||
const query = gql`
|
||||
query GetSubscriptions {
|
||||
subscriptions(sort: { by: UPDATED_TIME }) {
|
||||
... on SubscriptionsSuccess {
|
||||
subscriptions {
|
||||
id
|
||||
name
|
||||
newsletterEmail
|
||||
url
|
||||
description
|
||||
status
|
||||
unsubscribeMailTo
|
||||
unsubscribeHttpUrl
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
... on SubscriptionsError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher)
|
||||
console.log('subscriptions data', data)
|
||||
|
||||
try {
|
||||
if (data) {
|
||||
const result = data as SubscriptionsResponseData
|
||||
const subscriptions = result.subscriptions.subscriptions as Subscription[]
|
||||
return {
|
||||
isValidating,
|
||||
subscriptions,
|
||||
revalidate: () => {
|
||||
mutate()
|
||||
},
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
}
|
||||
return {
|
||||
isValidating: false,
|
||||
subscriptions: [],
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
revalidate: () => {},
|
||||
}
|
||||
}
|
||||
77
packages/web/pages/settings/subscriptions.tsx
Normal file
77
packages/web/pages/settings/subscriptions.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import { useState } from 'react'
|
||||
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { ConfirmationModal } from '../../components/patterns/ConfirmationModal'
|
||||
import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
import { unsubscribeMutation } from '../../lib/networking/mutations/unsubscribeMutation'
|
||||
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
|
||||
import { Table } from '../../components/elements/Table'
|
||||
|
||||
export default function SubscriptionsPage(): JSX.Element {
|
||||
const { subscriptions, revalidate } = useGetSubscriptionsQuery()
|
||||
const [confirmUnsubscribeName, setConfirmUnsubscribeName] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
|
||||
applyStoredTheme(false)
|
||||
|
||||
async function onUnsubscribe(name: string): Promise<void> {
|
||||
const result = await unsubscribeMutation(name)
|
||||
if (result) {
|
||||
showSuccessToast('Unsubscribed', { position: 'bottom-right' })
|
||||
} else {
|
||||
showErrorToast('Failed to unsubscribe', { position: 'bottom-right' })
|
||||
}
|
||||
revalidate()
|
||||
}
|
||||
|
||||
const headers = ['Name', 'Email', 'Updated Time']
|
||||
const rows = subscriptions.map((subscription) => [
|
||||
subscription.name,
|
||||
subscription.newsletterEmail,
|
||||
subscription.updatedAt.toString(),
|
||||
])
|
||||
|
||||
return (
|
||||
<PrimaryLayout pageTestId="settings-subscriptions-tag">
|
||||
<Toaster
|
||||
containerStyle={{
|
||||
top: '5rem',
|
||||
}}
|
||||
/>
|
||||
|
||||
{confirmUnsubscribeName ? (
|
||||
<ConfirmationModal
|
||||
message={
|
||||
'Are you sure? You will stop receiving newsletters from this subscription.'
|
||||
}
|
||||
onAccept={async () => {
|
||||
await onUnsubscribe(confirmUnsubscribeName)
|
||||
setConfirmUnsubscribeName(null)
|
||||
}}
|
||||
onOpenChange={() => setConfirmUnsubscribeName(null)}
|
||||
/>
|
||||
) : null}
|
||||
<Table
|
||||
heading={'Subscriptions'}
|
||||
headers={headers}
|
||||
rows={rows}
|
||||
onDelete={setConfirmUnsubscribeName}
|
||||
/>
|
||||
|
||||
{/* TODO: Dynamically loaded from API response */}
|
||||
<Table
|
||||
heading={'Popular Newsletters'}
|
||||
headers={['Substack', 'Axios', 'Bloomberg']}
|
||||
rows={[
|
||||
[
|
||||
'https://substack.com/',
|
||||
'https://www.axios.com/newsletters',
|
||||
'https://www.bloomberg.com/account/newsletters',
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</PrimaryLayout>
|
||||
)
|
||||
}
|
||||
18
yarn.lock
18
yarn.lock
@ -11328,9 +11328,9 @@ core-js-compat@^3.20.2, core-js-compat@^3.21.0:
|
||||
semver "7.0.0"
|
||||
|
||||
core-js-compat@^3.8.1:
|
||||
version "3.22.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.2.tgz#eec621eb276518efcf718d0a6d9d042c3d0cad48"
|
||||
integrity sha512-Fns9lU06ZJ07pdfmPMu7OnkIKGPKDzXKIiuGlSvHHapwqMUF2QnnsWwtueFZtSyZEilP0o6iUeHQwpn7LxtLUw==
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.1.tgz#47b9c5e79efbf13935f637449fa1cdec8cd9515f"
|
||||
integrity sha512-CWbNqTluLMvZg1cjsQUbGiCM91dobSHKfDIyCoxuqxthdjGuUlaMbCsSehP3CBiVvG0C7P6UIrC1v0hgFE75jw==
|
||||
dependencies:
|
||||
browserslist "^4.20.2"
|
||||
semver "7.0.0"
|
||||
@ -11341,14 +11341,14 @@ core-js-pure@^3.16.0:
|
||||
integrity sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw==
|
||||
|
||||
core-js-pure@^3.8.1, core-js-pure@^3.8.2:
|
||||
version "3.22.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.2.tgz#c10bffdc3028d25c2aae505819a05543db61544f"
|
||||
integrity sha512-Lb+/XT4WC4PaCWWtZpNPaXmjiNDUe5CJuUtbkMrIM1kb1T/jJoAIp+bkVP/r5lHzMr+ZAAF8XHp7+my6Ol0ysQ==
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.1.tgz#4d94e0c9a7b710da20dadd727fe98b43543119f0"
|
||||
integrity sha512-TChjCtgcMDc8t12RiwAsThjqrS/VpBlEvDgL009ot4HESzBo3h2FSZNa6ZS1nWKZEPDoulnszxUll9n0/spflQ==
|
||||
|
||||
core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2:
|
||||
version "3.22.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.2.tgz#3ea0a245b0895fa39d1faa15fe75d91ade504a01"
|
||||
integrity sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==
|
||||
version "3.22.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.1.tgz#1936e4f1da82675fe22ae10ee60ef638cd9752fd"
|
||||
integrity sha512-l6CwCLq7XgITOQGhv1dIUmwCFoqFjyQ6zQHUCQlS0xKmb9d6OHIg8jDiEoswhaettT21BSF5qKr6kbvE+aKwxw==
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
|
||||
Reference in New Issue
Block a user