diff --git a/babel.config.js b/babel.config.js index cd71bacf4a0..6590e779147 100644 --- a/babel.config.js +++ b/babel.config.js @@ -5,7 +5,6 @@ module.exports = { '@babel/plugin-transform-react-jsx', '@babel/plugin-proposal-class-properties', 'react-hot-loader/babel', - ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], ['babel-plugin-kea', { path: './frontend/src' }], ], presets: ['@babel/preset-env', '@babel/typescript'], diff --git a/cypress/integration/actions.js b/cypress/integration/actions.js index 27ace7e16fe..89f5a7c025f 100644 --- a/cypress/integration/actions.js +++ b/cypress/integration/actions.js @@ -10,17 +10,17 @@ describe('Actions', () => { it('Click on an action', () => { cy.get('[data-attr=action-link-0]').click() - cy.get('h1').should('contain', 'Edit action') + cy.get('h1').should('contain', 'Editing action') }) it('Create action', () => { cy.get('[data-attr=create-action]').click() cy.get('.ant-card-head-title').should('contain', 'event or pageview') cy.get('[data-attr=new-action-pageview]').click() - cy.get('h1').should('contain', 'New Action') + cy.get('h1').should('contain', 'Creating action') cy.get('[data-attr=edit-action-input]').type(Cypress._.random(0, 1e6)) - cy.get('[data-attr=action-step-pageview]').click() + cy.get('.ant-radio-group > :nth-child(3)').click() cy.get('[data-attr=edit-action-url-input]').type(Cypress.config().baseUrl) cy.get('[data-attr=save-action-button]').click() diff --git a/cypress/integration/cohorts.js b/cypress/integration/cohorts.js index 76a149aa70e..271ea343a23 100644 --- a/cypress/integration/cohorts.js +++ b/cypress/integration/cohorts.js @@ -9,10 +9,10 @@ describe('Cohorts', () => { // go to create a new cohort cy.get('[data-attr="create-cohort"]').click() - cy.get('form.card-body > .form-control').type('Test Cohort') + cy.get('[data-attr="cohort-name"]').type('Test Cohort') // select "add filter" and "property" - cy.get('[data-attr="cohort-group-property"]').click() + cy.get('.ant-radio-group > :nth-child(2)').click() cy.get('[data-attr="new-prop-filter-cohort_0"]').click() // select the first property diff --git a/cypress/integration/people.js b/cypress/integration/people.js index 7a793cc98d2..6d27cd2240b 100644 --- a/cypress/integration/people.js +++ b/cypress/integration/people.js @@ -7,13 +7,8 @@ describe('People', () => { cy.get('h1').should('contain', 'Persons') }) - it('Go to new cohort from people screen', () => { - cy.get('[data-attr=create-cohort]').click() - cy.get('span').should('contain', 'New Cohort') - }) - it('All tabs work', () => { - cy.get('.form-control').type('has:email').type('{enter}').should('have.value', 'has:email') + cy.get('[data-attr=persons-search]').type('has:email').type('{enter}').should('have.value', 'has:email') cy.wait(200) cy.get('.ant-tabs-nav-list > :nth-child(2)').click() cy.get('[data-row-key="100"] > :nth-child(2) > .ph-no-capture').should('contain', '@') @@ -24,8 +19,9 @@ describe('People', () => { it('All people route works', () => { cy.get('[data-attr=menu-item-people-cohorts]').click() - cy.get('[data-attr=menu-item-people-persons]').click() + cy.get('h1').should('contain', 'Cohorts') + cy.get('[data-attr=menu-item-people-persons]').click() cy.get('h1').should('contain', 'Persons') }) }) diff --git a/cypress/integration/trendsElements.js b/cypress/integration/trendsElements.js index 793b2c05c77..69911430e9c 100644 --- a/cypress/integration/trendsElements.js +++ b/cypress/integration/trendsElements.js @@ -27,8 +27,13 @@ describe('Trends actions & events', () => { it('Show property select dynamically', () => { cy.get('[data-attr=math-property-selector-0]').should('not.exist') - cy.get('[data-attr=math-selector-0]').click() - cy.get('[data-attr=math-avg-0]').click() + + // Test that the math selector dropdown is shown on hover + cy.get('[data-attr=math-selector-0]').trigger('mouseover') + cy.get('[data-attr=math-total-0]').should('be.visible') + + // Use `force = true` because clicking the element without dragging the mouse makes the dropdown disappear + cy.get('[data-attr=math-avg-0]').click({ force: true }) cy.get('[data-attr=math-property-selector-0]').should('exist') }) diff --git a/frontend/src/antd.less b/frontend/src/antd.less new file mode 100644 index 00000000000..ce3117fad13 --- /dev/null +++ b/frontend/src/antd.less @@ -0,0 +1,20 @@ +/* This file sets theming configuration on Ant Design for PostHog. Ant uses LESS which is incompatible +with SASS, which is why configuration is duplicated. To change any variable here, please update vars.scss too */ +@import 'antd/lib/style/themes/default.less'; +@import 'antd/dist/antd.less'; + +@text-color: #2d2d2d; +@text-muted: #d9d9d9; +@primary-color: #5375ff; +@link-color: #5375ff; +@success-color: #77b96c; +@warning-color: #f7a501; +@error-color: #f96132; +@font-size-base: 14px; +@heading-color: @text-color; +@text-color-secondary: rgba(0, 0, 0, 0.45); +@disabled-color: @text-muted; +@border-radius-base: 2px; +@border-color-base: #d9d9d9; +@body-background:  #f2f2f2; +@layout-body-background: #fff; diff --git a/frontend/src/global.scss b/frontend/src/global.scss new file mode 100644 index 00000000000..6acc47f629a --- /dev/null +++ b/frontend/src/global.scss @@ -0,0 +1,334 @@ +/* Only styles that are shared across multiple components (i.e. global) should go here, trying to keep this file +nimble to simplify maintenance. We separate variables and mixins in vars.scss to be able to import those into local +style files without adding already imported styles. */ + +// Global components +@import 'node_modules/react-toastify/dist/ReactToastify'; +@import 'node_modules/react-datepicker/dist/react-datepicker'; +@import './vars'; + +:root { + --primary: #{$primary}; + --success: #{$success}; + --danger: #{$danger}; + --warning: #{$warning}; + --bg-menu: #{$bg_menu}; + --bg-mid: #{$bg_mid}; + // Used for graph series + --blue: #{$blue_500}; + --purple: #{purple_500}; + --salmon: #ff906e; + --yellow: #ffc035; + --green: #{$success}; + --indigo: #{$purple_700}; + --cyan: #17a2b8; + --pink: #e83e8c; + --white: #f4f6ff; +} + +// Text styles +.text-default { + color: $text_default; + font-size: 14px; + line-height: 20px; + font-weight: 400; +} + +.text-small { + @extend .text-default; + font-size: 12px; +} + +.text-extra-small { + @extend .text-default; + font-size: 10px; +} + +.page-title { + font-size: 28px; + line-height: 34px; + margin-top: 32px; + font-weight: 700; + color: $text_default; +} + +.page-caption { + @extend .text-default; + max-width: 640px; + margin-bottom: 32px; +} + +.subtitle { + margin-top: 24px; + font-size: 22px; + line-height: 26px; + font-weight: 700; +} + +.l3 { + /* Level 3 title (ideally H3) */ + font-size: 16px; + font-weight: 700; + line-height: 19px; +} + +.text-right { + text-align: right; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-muted { + color: $text_muted; +} + +// Spacing & layout +.mb { + margin-bottom: $default_spacing; +} + +.mt { + margin-top: $default_spacing; +} + +.mb-05 { + margin-bottom: $default_spacing * 0.5; +} + +.mt-05 { + margin-top: $default_spacing * 0.5; +} + +.mr { + margin-right: $default_spacing; +} + +.ml { + margin-left: $default_spacing; +} + +.pa { + // Padding all + padding: $default_spacing; +} + +.pb { + padding-bottom: $default_spacing; +} + +.pt { + padding-top: $default_spacing; +} + +.pr { + padding-right: $default_spacing; +} + +.pl { + padding-left: $default_spacing; +} + +.full-width { + width: 100%; +} + +.float-right { + float: right; +} + +.float-left { + float: left; +} + +.main-app-content { + padding: $default_spacing $default_spacing * 3; + + @media (min-width: 480px) and (max-width: 639px) { + padding: $default_spacing $default_spacing * 2 !important; + } + + @media (max-width: 480px) { + padding: $default_spacing $default_spacing !important; + } +} + +// Color styles +.bg-primary { + background-color: $primary; +} + +.text-danger { + color: $danger !important; +} + +// Random general styles +.cursor-pointer { + cursor: pointer; +} + +// Toasts +.Toastify__toast-container { + opacity: 1; + transform: none; +} + +.Toastify__toast { + padding: 16px; + border-radius: $radius; + color: $text_default; + font-family: inherit; + background-color: $bg_light; +} + +.Toastify__toast-body { + @extend .l3; + color: $success; + p { + @extend .text-default; + color: $text_default; + } +} + +.Toastify__progress-bar--default { + background: $success; +} + +.Toastify__toast--error { + h1 { + color: $danger; + } + .Toastify__progress-bar { + background: $danger; + } + .error-details { + font-style: italic; + } +} + +// Table styles +.table-bordered td { + border: 1px solid $border; +} + +// Card styles +.card-elevated { + @extend .mixin-elevated; +} + +// Form & input styles +.input-set { + padding-bottom: $default_spacing; + color: $text_default; + + label { + font-weight: bold; + @extend .text-default; + } + + .caption { + color: $text_muted; + @extend .text-small; + } + + &.errored { + .caption { + color: $danger; + } + .ant-input-password, + input[type='text'] { + border-color: $danger !important; + } + } +} + +// Button styles +.btn-close { + color: $text_muted; +} + +.ant-btn-sm { + font-size: 12px !important; +} + +.ant-btn-md { + // Size between `small` & `default` + font-size: 13px !important; + height: 28px !important; + padding: 0px 10px !important; +} + +.info-indicator { + color: $primary !important; + cursor: pointer; + margin-left: 5px; +} + +// Overlays styles +#bottom-notice { + z-index: 1000000000; + display: flex; + flex-direction: row; + position: fixed; + width: 100%; + bottom: 0; + left: 0; + background: #000; + color: #fff; + font-size: 0.75rem; + line-height: 1.5rem; + &.warning div { + height: auto; + background: $danger; + } + + div:nth-child(1) { + background: $purple_700; + } + div:nth-child(2) { + background: $purple_500; + } + div:nth-child(3) { + background: $purple_300; + } + + div { + flex-basis: 0; + flex-grow: 1; + height: 1.5rem; + text-align: center; + } + span { + display: none; + } + button { + border: none; + background: transparent; + width: 1.5rem; + height: 1.5rem; + padding: 0; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + } + @media screen and (min-width: 750px) { + font-size: 1rem; + line-height: 2rem; + div { + height: 2rem; + } + span { + display: inline; + } + button { + width: 2rem; + height: 2rem; + font-size: 1.25rem; + } + } +} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 8161ac65789..afab3e93866 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,4 +1,6 @@ -import './style.scss' +import '~/global.scss' /* Contains PostHog's main styling configurations */ +import '~/antd.less' /* Imports Ant Design's components */ +import './style.scss' /* DEPRECATED */ import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' diff --git a/frontend/src/initKea.tsx b/frontend/src/initKea.tsx index cbccd6cef85..20b597430ab 100644 --- a/frontend/src/initKea.tsx +++ b/frontend/src/initKea.tsx @@ -5,6 +5,7 @@ import { loadersPlugin } from 'kea-loaders' import { windowValuesPlugin } from 'kea-window-values' import { toast } from 'react-toastify' import React from 'react' +import { identifierToHuman } from 'lib/utils' export function initKea(): void { resetContext({ @@ -16,9 +17,11 @@ export function initKea(): void { onFailure({ error, reducerKey, actionKey }) { toast.error(
-

Error loading "{reducerKey}".

-

Action "{actionKey}" responded with

-

"{error.message || error.detail}"

+

Error on {identifierToHuman(reducerKey)}.

+

+ Attempting to {identifierToHuman(actionKey, false)} returned an error:{' '} + {error.detail} +

) window['Sentry'] ? window['Sentry'].captureException(error) : console.error(error) diff --git a/frontend/src/layout.ejs b/frontend/src/layout.ejs index 0b7c1ec121b..772bfe17cb7 100644 --- a/frontend/src/layout.ejs +++ b/frontend/src/layout.ejs @@ -6,6 +6,8 @@ PostHog {% include "head.html" %} <%= htmlWebpackPlugin.tags.headTags %><%/* This adds the main.css file! */%> + {% block head %} diff --git a/frontend/src/layout/SendEventsOverlay.js b/frontend/src/layout/SendEventsOverlay.js index cfe5ca36474..12fe712cfd4 100644 --- a/frontend/src/layout/SendEventsOverlay.js +++ b/frontend/src/layout/SendEventsOverlay.js @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react' import { router } from 'kea-router' import { useValues } from 'kea' import { JSSnippet } from 'lib/components/JSSnippet' +import './SendEventsOverlay.scss' export function SendEventsOverlay() { const overlay = useRef() diff --git a/frontend/src/style/send-events-overlay.scss b/frontend/src/layout/SendEventsOverlay.scss similarity index 100% rename from frontend/src/style/send-events-overlay.scss rename to frontend/src/layout/SendEventsOverlay.scss diff --git a/frontend/src/layout/Sidebar.js b/frontend/src/layout/Sidebar.js index f6cc5e1179e..176f468fba0 100644 --- a/frontend/src/layout/Sidebar.js +++ b/frontend/src/layout/Sidebar.js @@ -20,6 +20,7 @@ import { ClockCircleOutlined, MessageOutlined, ProjectOutlined, + SettingOutlined, LockOutlined, WalletOutlined, ApiOutlined, @@ -41,11 +42,8 @@ const itemStyle = { display: 'flex', alignItems: 'center' } function Logo() { return ( -
- +
+
) } @@ -53,7 +51,7 @@ function Logo() { // to show the right page in the sidebar const sceneOverride = { action: 'actions', - person: 'people', + person: 'persons', dashboard: 'dashboards', featureFlags: 'experiments', } @@ -63,7 +61,7 @@ const submenuOverride = { actions: 'events', liveActions: 'events', sessions: 'events', - cohorts: 'people', + cohorts: 'persons', projectSettings: 'project', plugins: 'project', organizationSettings: 'organization', @@ -111,16 +109,17 @@ function _Sidebar({ user, sidebarCollapsed, setSidebarCollapsed }) { { setSidebarCollapsed(sidebarCollapsed) triggerResizeAfterADelay() }} + style={{ backgroundColor: 'var(--bg-menu)' }} > @@ -214,18 +213,18 @@ function _Sidebar({ user, sidebarCollapsed, setSidebarCollapsed }) { } onTitleClick={() => { collapseSidebar() - location.pathname !== '/people/persons' && push('/people/persons') + location.pathname !== '/persons' && push('/persons') }} > - + Persons - + Cohorts - + @@ -245,7 +244,7 @@ function _Sidebar({ user, sidebarCollapsed, setSidebarCollapsed }) { key="project" title={ - + Project } @@ -255,7 +254,7 @@ function _Sidebar({ user, sidebarCollapsed, setSidebarCollapsed }) { }} > - + Settings diff --git a/frontend/src/layout/Sidebar.scss b/frontend/src/layout/Sidebar.scss index e068dfaab57..718cbd8d275 100644 --- a/frontend/src/layout/Sidebar.scss +++ b/frontend/src/layout/Sidebar.scss @@ -1,3 +1,14 @@ +.sidebar-logo { + margin: 16px; + height: 42px; + white-space: nowrap; + width: 168px; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; +} + @media (max-width: 991px) { #root { height: 100%; diff --git a/frontend/src/layout/TopContent/CommandPaletteButton.tsx b/frontend/src/layout/TopContent/CommandPaletteButton.tsx index a2a60fcac5c..24d3407c49f 100644 --- a/frontend/src/layout/TopContent/CommandPaletteButton.tsx +++ b/frontend/src/layout/TopContent/CommandPaletteButton.tsx @@ -3,20 +3,21 @@ import { useActions, useValues } from 'kea' import { commandPaletteLogic } from 'lib/components/CommandPalette/commandPaletteLogic' import { SearchOutlined } from '@ant-design/icons' import { platformCommandControlKey } from 'lib/utils' +import { Button } from 'antd' export function CommandPaletteButton(): JSX.Element { const { isPaletteShown } = useValues(commandPaletteLogic) const { showPalette } = useActions(commandPaletteLogic) return ( - } > - {platformCommandControlKey('K')} - + ) } diff --git a/frontend/src/layout/TopContent/LatestVersion.js b/frontend/src/layout/TopContent/LatestVersion.js index a5ff2f84389..ba9ad16f45c 100644 --- a/frontend/src/layout/TopContent/LatestVersion.js +++ b/frontend/src/layout/TopContent/LatestVersion.js @@ -18,7 +18,7 @@ export function LatestVersion() { {latestVersion ? ( {isApp ? ( - ) : ( @@ -27,7 +27,7 @@ export function LatestVersion() {
+ ) } @@ -262,22 +261,20 @@ export function Projects(): JSX.Element { ) }} > - + New Project } > -
} > - {!user ? loading : user.team ? user.team.name : none yet} -
+ ) diff --git a/frontend/src/layout/TopContent/index.tsx b/frontend/src/layout/TopContent/index.tsx index b90eaca90fe..f2adddf7665 100644 --- a/frontend/src/layout/TopContent/index.tsx +++ b/frontend/src/layout/TopContent/index.tsx @@ -13,7 +13,7 @@ export function TopContent(): JSX.Element { const { backTo } = useValues(topContentLogic) return ( -
+
getComputedStyle(document.body).getPropertyValue('--' + variable) export const darkWhites = [ diff --git a/frontend/src/lib/components/AppEditorLink/AppEditorLink.js b/frontend/src/lib/components/AppEditorLink/AppEditorLink.js index c1b3bcbf72e..8916e399b08 100644 --- a/frontend/src/lib/components/AppEditorLink/AppEditorLink.js +++ b/frontend/src/lib/components/AppEditorLink/AppEditorLink.js @@ -6,23 +6,23 @@ import { appEditorUrl } from './utils' import { teamLogic } from 'scenes/teamLogic' import { Modal, Button } from 'antd' -export function AppEditorLink({ actionId, style, className, children }) { +export function AppEditorLink({ actionId, style, children }) { const [modalOpen, setModalOpen] = useState(false) const { currentTeam } = useValues(teamLogic) return ( <> - { e.preventDefault() setModalOpen(true) }} > {children} - + {user?.billing?.should_setup_billing && user?.billing.subscription_url && ( -
-
+ +
{user?.billing?.plan?.custom_setup_billing_message || @@ -22,7 +23,7 @@ export function BillingToolbar(): JSX.Element {
-
+ )} ) diff --git a/frontend/src/lib/components/ChartFilter/ChartFilter.js b/frontend/src/lib/components/ChartFilter/ChartFilter.js index d87220cf03f..49792256828 100644 --- a/frontend/src/lib/components/ChartFilter/ChartFilter.js +++ b/frontend/src/lib/components/ChartFilter/ChartFilter.js @@ -13,13 +13,8 @@ export function ChartFilter(props) { (!filters.display || filters.display === ACTIONS_LINE_GRAPH_LINEAR || filters.display === ACTIONS_LINE_GRAPH_CUMULATIVE) && ( - trigger.parentElement} - placement="right" - title="Click on a point to see users related to the datapoint" - > - + + ), diff --git a/frontend/src/lib/components/CloseButton.tsx b/frontend/src/lib/components/CloseButton.tsx new file mode 100644 index 00000000000..c9d85c690db --- /dev/null +++ b/frontend/src/lib/components/CloseButton.tsx @@ -0,0 +1,10 @@ +import { CloseOutlined } from '@ant-design/icons' +import React from 'react' + +export function CloseButton(props: Record): JSX.Element { + return ( + + + + ) +} diff --git a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.ts b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.ts index 7037645614c..59e7f18514a 100644 --- a/frontend/src/lib/components/CommandPalette/commandPaletteLogic.ts +++ b/frontend/src/lib/components/CommandPalette/commandPaletteLogic.ts @@ -463,17 +463,17 @@ export const commandPaletteLogic = kea< }, { icon: UserOutlined, - display: 'Go to People', + display: 'Go to Persons', synonyms: ['people'], executor: () => { - push('/people/persons') + push('/persons') }, }, { icon: UsergroupAddOutlined, display: 'Go to Cohorts', executor: () => { - push('/people/cohorts') + push('/cohorts') }, }, { diff --git a/frontend/src/lib/components/CommandPalette/index.scss b/frontend/src/lib/components/CommandPalette/index.scss index 8715d8d041f..4ad21f4fcbc 100644 --- a/frontend/src/lib/components/CommandPalette/index.scss +++ b/frontend/src/lib/components/CommandPalette/index.scss @@ -1,3 +1,5 @@ +@import '~/vars'; + .palette__overlay { z-index: 2147483021; position: fixed; @@ -18,7 +20,11 @@ width: 36rem; max-width: 100%; max-height: 60%; - color: #fff; + color: $text_light; + border-radius: $radius; + background-color: $bg_menu; + @extend .mixin-elevated; + @media (max-width: 500px) { top: 10%; max-height: 80%; @@ -79,7 +85,7 @@ } .palette__result--focused { - background: rgba(0, 0, 0, 0.35); + background: darken($bg_menu, 10%); &::before, &::after { content: ''; @@ -98,7 +104,7 @@ } .palette__result--executable::after { - background: #1890ff; + background: $primary; } .palette__scope { @@ -110,5 +116,4 @@ display: flex; align-items: center; width: 1rem; - height: 100%; } diff --git a/frontend/src/lib/components/CommandPalette/index.tsx b/frontend/src/lib/components/CommandPalette/index.tsx index 8cfe93f413f..e72873a0a8e 100644 --- a/frontend/src/lib/components/CommandPalette/index.tsx +++ b/frontend/src/lib/components/CommandPalette/index.tsx @@ -52,7 +52,7 @@ export function CommandPalette(): JSX.Element | null { return !user || !isPaletteShown ? null : (
-
+
{(!activeFlow || activeFlow.instruction) && } {!commandSearchResults.length && !activeFlow ? null : }
diff --git a/frontend/src/lib/components/DateFilter/DateFilter.js b/frontend/src/lib/components/DateFilter/DateFilter.js index a5970b5c607..490737266ac 100644 --- a/frontend/src/lib/components/DateFilter/DateFilter.js +++ b/frontend/src/lib/components/DateFilter/DateFilter.js @@ -125,7 +125,7 @@ function DatePickerDropdown(props) { }, [calendarOpen]) return ( -
+ - ) -} diff --git a/frontend/src/lib/components/PageHeader.tsx b/frontend/src/lib/components/PageHeader.tsx new file mode 100644 index 00000000000..c5c25d0ca09 --- /dev/null +++ b/frontend/src/lib/components/PageHeader.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +interface PageHeaderProps { + title: string | JSX.Element + caption?: string | JSX.Element +} + +export function PageHeader({ title, caption }: PageHeaderProps): JSX.Element { + return ( + <> +

{title}

+ {caption &&
{caption}
} + + ) +} diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilter.js b/frontend/src/lib/components/PropertyFilters/PropertyFilter.js index d26f6391f55..89dea659e84 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilter.js +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilter.js @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react' -import { Select, Tabs } from 'antd' +import { Col, Row, Select, Tabs } from 'antd' import { operatorMap, isOperatorFlag } from 'lib/utils' import { PropertyValue } from './PropertyValue' import { PropertyKeyInfo, keyMapping } from 'lib/components/PropertyKeyInfo' @@ -22,130 +22,135 @@ function PropertyPaneContents({ }) { return ( <> -
- -
- {displayOperatorAndValue && ( -
+ + -
- )} - {displayOperatorAndValue && !isOperatorFlag(operator) && ( -
- { - onComplete() - setThisFilter(propkey, value, operator, type) - }} - /> - {(operator === 'gt' || operator === 'lt') && isNaN(value) && ( -

Value needs to be a number. Try "equals" or "contains" instead.

- )} -
- )} + + {displayOperatorAndValue && ( + + + + )} + {displayOperatorAndValue && !isOperatorFlag(operator) && ( + + { + onComplete() + setThisFilter(propkey, value, operator, type) + }} + /> + {(operator === 'gt' || operator === 'lt') && isNaN(value) && ( +

+ Value needs to be a number. Try "equals" or "contains" instead. +

+ )} + + )} + ) } diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilters.js b/frontend/src/lib/components/PropertyFilters/PropertyFilters.js index 596887970cb..e81cca8612d 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilters.js +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilters.js @@ -6,7 +6,8 @@ import { propertyFilterLogic } from './propertyFilterLogic' import { cohortsModel } from '../../../models/cohortsModel' import { keyMapping } from 'lib/components/PropertyKeyInfo' import { Popover, Row } from 'antd' -import { CloseButton, formatPropertyLabel } from 'lib/utils' +import { formatPropertyLabel } from 'lib/utils' +import { CloseButton } from 'lib/components/CloseButton' import '../../../scenes/actions/Actions.scss' const FilterRow = React.memo(function FilterRow({ @@ -31,7 +32,7 @@ const FilterRow = React.memo(function FilterRow({ } return ( - + +
{filters && filters.map((item, index) => { return ( diff --git a/frontend/src/lib/components/SaveToDashboard/SaveToDashboardModal.js b/frontend/src/lib/components/SaveToDashboard/SaveToDashboardModal.js index aec60bd656b..e7651585018 100644 --- a/frontend/src/lib/components/SaveToDashboard/SaveToDashboardModal.js +++ b/frontend/src/lib/components/SaveToDashboard/SaveToDashboardModal.js @@ -118,7 +118,6 @@ export function SaveToDashboardModal({ name="name" required type="text" - className="form-control" placeholder="Users who did x" autoFocus={!name} value={name} diff --git a/frontend/src/lib/utils.tsx b/frontend/src/lib/utils.tsx index 0399f998985..99de836b047 100644 --- a/frontend/src/lib/utils.tsx +++ b/frontend/src/lib/utils.tsx @@ -90,28 +90,6 @@ export function SceneLoading(): JSX.Element { ) } -export function CloseButton(props: Record): JSX.Element { - return ( - - - - ) -} - -export function Card(props: Record): JSX.Element { - return ( -
- {props.title &&
{props.title}
} - {props.children} -
- ) -} - export function deleteWithUndo({ undo = false, ...props }: Record): void { api.update('api/' + props.endpoint + '/' + props.object.id, { ...props.object, @@ -536,6 +514,19 @@ export function sampleSingle(items: T[]): T[] { return [items[Math.floor(Math.random() * items.length)]] } +export function identifierToHuman(input: string, capitalize: boolean = true): string | null { + /* Converts a camelCase, PascalCase or snake_case string to a human-friendly string. + (e.g. `feature_flags` or `featureFlags` becomes "Feature Flags") */ + const match = input.match(/[A-Za-z][a-z]*/g) + if (!match) return null + + return match + .map((group) => { + return capitalize ? group[0].toUpperCase() + group.substr(1).toLowerCase() : group.toLowerCase() + }) + .join(' ') +} + export function parseGithubRepoURL(url: string): Record { const match = url.match(/^https?:\/\/(?:www\.)?github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/?$/) if (!match) { diff --git a/frontend/src/scenes/App.tsx b/frontend/src/scenes/App.tsx index 06ce120d386..84bf150a288 100644 --- a/frontend/src/scenes/App.tsx +++ b/frontend/src/scenes/App.tsx @@ -1,5 +1,3 @@ -import 'react-toastify/dist/ReactToastify.css' -import 'react-datepicker/dist/react-datepicker.css' import { hot } from 'react-hot-loader/root' import React, { useState, useEffect } from 'react' @@ -28,6 +26,10 @@ const darkerScenes: Record = { paths: true, } +const Toast = (): JSX.Element => { + return +} + export const App = hot(_App) function _App(): JSX.Element { const { user } = useValues(userLogic) @@ -55,9 +57,9 @@ function _App(): JSX.Element { if (!user) { return unauthenticatedRoutes.includes(scene) ? ( - <> - - + + + ) : (
) @@ -67,7 +69,7 @@ function _App(): JSX.Element { return ( <> - + ) } @@ -75,16 +77,14 @@ function _App(): JSX.Element { return ( <> - + - + {currentTeam && !currentTeam.ingested_event && @@ -93,7 +93,7 @@ function _App(): JSX.Element { ) : ( )} - + diff --git a/frontend/src/scenes/PreflightCheck/index.js b/frontend/src/scenes/PreflightCheck/index.js index ff0ad75be22..209329830c2 100644 --- a/frontend/src/scenes/PreflightCheck/index.js +++ b/frontend/src/scenes/PreflightCheck/index.js @@ -14,6 +14,7 @@ import { } from '@ant-design/icons' import { volcano, green, red, grey, blue } from '@ant-design/colors' import { router } from 'kea-router' +import { PageHeader } from 'lib/components/PageHeader' function PreflightItem({ name, status, caption, failedState }) { /* @@ -108,20 +109,17 @@ function PreflightCheck() { return ( <> - -

- Welcome to PostHog! -

-
Understand your users. Build a better product.
+ + - +

- We're glad to have you here! Let's get you started with PostHog. + We're glad to have you here! Let's get you started with PostHog.

-
+
+ ) return ( -
- { - e.preventDefault() - if (isEditor && showNewActionButton) setCreateNew(true) - saveAction() - }} - > - +
+ { @@ -56,135 +46,134 @@ export function ActionEdit({ actionId, apiURL, onSave, user, isEditor, simmer, s }} data-attr="edit-action-input" /> +
- {action.count > -1 && ( -
- Matches {action.count} events -
- )} - - {!isEditor &&
} - - {action.steps.map((step, index) => ( - - {index > 0 ? ( -
- OR -
- ) : null} - { - setAction({ ...action, steps: action.steps.filter((s) => s.id != step.id) }) - setEdited(true) - }} - onChange={(newStep) => { - setAction({ - ...action, - steps: action.steps.map((s) => - (step.id && s.id == step.id) || (step.isNew && s.isNew === step.isNew) - ? { - id: step.id, - isNew: step.isNew, - ...newStep, - } - : s - ), - }) - setEdited(true) - }} - /> -
- ))} - - {!isEditor ? ( -
- ) : ( -
- )} - - {errorActionId && ( -

- Action with this name already exists.{' '} - Click here to edit. -

- )} - - {isEditor ?
{addGroup}
: null} - -
- {!isEditor ? addGroup : null} - + {action.count > -1 && ( +
+ Matches {action.count} events
- -
+ )} + + {!isEditor &&
} + + {action.steps.map((step, index) => ( + + {index > 0 ? ( +
+ OR +
+ ) : null} + { + setAction({ ...action, steps: action.steps.filter((s) => s.id != step.id) }) + setEdited(true) + }} + onChange={(newStep) => { + setAction({ + ...action, + steps: action.steps.map((s) => + (step.id && s.id == step.id) || (step.isNew && s.isNew === step.isNew) + ? { + id: step.id, + isNew: step.isNew, + ...newStep, + } + : s + ), + }) + setEdited(true) + }} + /> +
+ ))} + + {!isEditor ? ( +
+
+ { + setAction({ ...action, post_to_slack: e.target.checked }) + setEdited(true) + }} + checked={!!action.post_to_slack} + disabled={!slackEnabled} + /> + {' '} + + {slackEnabled ? 'Configure' : 'Enable'} this integration in Setup. + + {action.post_to_slack && ( + <> + { + setAction({ ...action, slack_message_format: e.target.value }) + setEdited(true) + }} + disabled={!slackEnabled || !action.post_to_slack} + data-attr="edit-slack-message-format" + /> + + + See documentation on how to format webhook messages. + + + + )} +
+
+ ) : ( +
+ )} + + {errorActionId && ( +

+ Action with this name already exists.{' '} + Click here to edit. +

+ )} + +
+ {addGroup} + +
+ ) } diff --git a/frontend/src/scenes/actions/ActionEditV2.js b/frontend/src/scenes/actions/ActionEditV2.js index bdb68cd5012..5b84d481eee 100644 --- a/frontend/src/scenes/actions/ActionEditV2.js +++ b/frontend/src/scenes/actions/ActionEditV2.js @@ -7,8 +7,8 @@ import { useValues, useActions } from 'kea' import { actionEditLogic } from './actionEditLogic' import './Actions.scss' import { ActionStep } from './ActionStepV2' -import { Col, Input, Row } from 'antd' -import { InfoCircleOutlined, PlusOutlined } from '@ant-design/icons' +import { Button, Col, Input, Row } from 'antd' +import { InfoCircleOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons' export function ActionEdit({ actionId, apiURL, onSave, user, simmer, temporaryToken }) { let logic = actionEditLogic({ @@ -30,9 +30,9 @@ export function ActionEdit({ actionId, apiURL, onSave, user, simmer, temporaryTo } const addGroup = ( - + ) return ( @@ -44,9 +44,8 @@ export function ActionEdit({ actionId, apiURL, onSave, user, simmer, temporaryTo }} > - { @@ -61,8 +60,8 @@ export function ActionEdit({ actionId, apiURL, onSave, user, simmer, temporaryTo
)} -
-

Match groups

+
+

Match groups

diff --git a/frontend/src/scenes/actions/ActionStep.js b/frontend/src/scenes/actions/ActionStep.js index 25d080c6fb8..5d91842d443 100644 --- a/frontend/src/scenes/actions/ActionStep.js +++ b/frontend/src/scenes/actions/ActionStep.js @@ -4,6 +4,8 @@ import { AppEditorLink } from 'lib/components/AppEditorLink/AppEditorLink' import { PropertyFilters } from 'lib/components/PropertyFilters/PropertyFilters' import PropTypes from 'prop-types' import { URL_MATCHING_HINTS } from 'scenes/actions/hints' +import { ExportOutlined } from '@ant-design/icons' +import { Button, Card, Checkbox, Input, Radio } from 'antd' let getSafeText = (el) => { if (!el.childNodes || !el.childNodes.length) return @@ -132,15 +134,14 @@ export class ActionStep extends Component { selectorError = true } return ( -
-1 && 'selected')}> +
-1 && 'selected')}> {props.selector && this.props.isEditor && ( {selectorError ? 'Invalid selector' : `Matches ${matches} elements`} )} {props.item === 'selector' ? ( - - - - -
- -
Examples:
- -
-

- - <i class="flaticon-add"></i> -

-
- -
-

- - <i class="flaticon-basket"></i> -

-
- -
-

- - <i class="flaticon-bookmark"></i> -

-
- -
-

- - <i class="flaticon-broken-link"></i> -

-
- -
- - - -
- - - - - \ No newline at end of file diff --git a/frontend/src/style/funnel.scss b/frontend/src/style/funnel.scss deleted file mode 100644 index 91ffaf370a0..00000000000 --- a/frontend/src/style/funnel.scss +++ /dev/null @@ -1,179 +0,0 @@ -.funnel { - .funnel-success { - background-color: lighten($blue, 20%); - } - .funnel-dropped { - background-color: $gray-200; - } - table { - table-layout: fixed; - border-top: 0; - margin-bottom: 0; - td { - padding: 0 0.75rem; - } - } -} - -.svg-funnel-js { - .svg-funnel-js__container { - width: 100%; - height: 100%; - } - - .svg-funnel-js__labels { - width: 100%; - box-sizing: border-box; - - .svg-funnel-js__label { - flex: 1 1 0; - position: relative; - - .label__value { - display: none; - } - - .label__title { - margin-top: 1rem; - font-size: 12px; - font-weight: 300; - } - - .label__percentage { - font-size: 16px; - font-weight: bold; - } - - .label__segment-percentages { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 100%; - left: 0; - padding: 8px 24px; - box-sizing: border-box; - background-color: $percentage-hover; - margin-top: 24px; - opacity: 0; - transition: opacity 0.1s ease; - cursor: default; - - ul { - margin: 0; - padding: 0; - list-style-type: none; - - li { - font-size: 13px; - line-height: 16px; - color: $white; - margin: 18px 0; - - .percentage__list-label { - font-weight: bold; - color: $primary; - } - } - } - } - - &:hover { - .label__segment-percentages { - opacity: 1; - } - } - } - } - - &:not(.svg-funnel-js--vertical) { - padding-top: 64px; - padding-bottom: 16px; - - .svg-funnel-js__label { - padding-left: 24px; - - &:not(:first-child) { - border-left: 1px solid $gray-300; - } - } - } - - &.svg-funnel-js--vertical { - padding-left: 120px; - padding-right: 16px; - - .svg-funnel-js__label { - padding-top: 24px; - - &:not(:first-child) { - border-top: 1px solid $secondary; - } - - .label__segment-percentages { - margin-top: 0; - margin-left: 106px; - width: calc(100% - 106px); - - .segment-percentage__list { - display: flex; - justify-content: space-around; - } - } - } - } - - .svg-funnel-js__subLabels { - display: flex; - justify-content: center; - margin-top: 24px; - position: absolute; - width: 100%; - left: 0; - - .svg-funnel-js__subLabel { - display: flex; - font-size: 12px; - color: $white; - line-height: 16px; - - &:not(:first-child) { - margin-left: 16px; - } - - .svg-funnel-js__subLabel--color { - width: 12px; - height: 12px; - border-radius: 50%; - margin: 2px 8px 2px 0; - } - } - } -} - -.card-funnel { - min-width: 250px; -} - -.funnel-step { - .card { - flex-direction: row; - } - .funnel-step-side { - background: rgba(0, 0, 0, 0.03); - width: 40px; - padding: 11px 12px; - text-align: center; - } - .select-box { - display: flex; - flex-direction: row-reverse; - justify-content: flex-end; - } - .select-box > a { - margin-top: 3px; - } - .select-box-select { - width: 300px; - margin-right: 16px; - } -} diff --git a/frontend/src/style/toast.scss b/frontend/src/style/toast.scss deleted file mode 100644 index b7d366ee5db..00000000000 --- a/frontend/src/style/toast.scss +++ /dev/null @@ -1,13 +0,0 @@ -.Toastify__toast-container { - width: 530px; - opacity: 1; - transform: none; -} -.Toastify__toast { - padding: 1rem 2rem; - border-radius: 3px; - color: $body-color; -} -.Toastify__progress-bar--default { - background: $green; -} diff --git a/frontend/src/vars.scss b/frontend/src/vars.scss new file mode 100644 index 00000000000..13f143622a6 --- /dev/null +++ b/frontend/src/vars.scss @@ -0,0 +1,50 @@ +/* This file contains the main styling configuration for PostHog. We also use AntD which uses LESS, +when changing any base config here, please remember to update antd.less too */ + +// Main colors +$primary: #5375ff; +$primary_alt: #35416b; +$success: #77b96c; +$warning: #f7a501; +$danger: #f96132; + +// Text colors +$text_default: #2d2d2d; +$text_light: rgba(255, 255, 255, 0.88); +$text_muted: #d9d9d9; +$text_muted_alt: #35416b; + +// Background colors +$bg_navy: #35416b; +$bg_charcoal: #2d2d2d; +$bg_mid: #f2f2f2; +$bg_light: #ffffff; +$bg_depth: #0f0f0f; +$bg_menu: #2c3035; + +// Border colors +$border_light: #f0f0f0; +$border: #d9d9d9; +$border_dark: #bdbdbd; + +.bg-mid { + background-color: $bg_mid !important; +} + +// Blue Swatch +$blue_700: #35416b; +$blue_500: #597dce; +$blue_300: #8da9e7; +$blue_100: #b8cefd; + +// Purple Swatch +$purple_700: #7c4286; +$purple_500: #c278cf; +$purple_300: #dcb1e3; + +// Additional style configurations +.mixin-elevated { + box-shadow: 0px 80px 80px rgba(0, 0, 0, 0.075), 0px 10px 10px rgba(0, 0, 0, 0.035) !important; +} +$default_spacing: 16px; +$radius: 2px; diff --git a/package.json b/package.json index 846f7b0c631..dc8f83d4d56 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "@mariusandra/simmerjs": "0.7.1-posthog.1", "antd": "^4.1.1", "babel-preset-nano-react-app": "^0.1.0", - "bootstrap": "^4.4.1", "chart.js": "^2.9.3", "core-js": "3.6.5", "d3": "^5.15.0", @@ -103,6 +102,8 @@ "html-webpack-plugin": "^4.4.1", "husky": "~4.2.5", "kea-typegen": "^0.3.5", + "less": "^3.12.2", + "less-loader": "^7.0.2", "lint-staged": "~10.2.13", "mini-css-extract-plugin": "^0.11.0", "nodemon": "^2.0.4", diff --git a/posthog/templates/overlays.html b/posthog/templates/overlays.html index 46e25403c34..2780df4a4a9 100644 --- a/posthog/templates/overlays.html +++ b/posthog/templates/overlays.html @@ -1,50 +1,50 @@ {% if debug %} -
-
Current branch: {{ git_branch|default:"unknown" }}.
-
PostHog running in DEBUG mode!
-
Current revision: {{ git_rev|default:"unknown" }}.
- +
+
+ Current branch: {{ git_branch|default:"unknown" }}.
+
+ PostHog running in DEBUG mode! +
+
+ Current revision: {{ git_rev|default:"unknown" }}. +
+ +
{% else %} - - -{% endif %} + -{% if debug or request.get_host == 'app.posthog.com' %} - - +{% endif %} {% if not debug %} + + {% endif %} diff --git a/webpack.config.js b/webpack.config.js index dc24120b7d0..1301a55d0bb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -117,6 +117,27 @@ function createEntry(entry) { }, ].filter((a) => a), }, + { + // Apply rule for less files (used to import and override AntD) + test: /\.(less)$/, + use: [ + { + loader: 'style-loader', // creates style nodes from JS strings + }, + { + loader: 'css-loader', // translates CSS into CommonJS + }, + { + loader: 'less-loader', // compiles Less to CSS + options: { + lessOptions: { + javascriptEnabled: true, + }, + }, + }, + ], + }, + { // Now we apply rule for images test: /\.(png|jpe?g|gif|svg)$/, diff --git a/yarn.lock b/yarn.lock index 9e6e978726d..3e14ce2d430 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1043,7 +1043,7 @@ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5": +"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== @@ -1434,6 +1434,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -1866,11 +1876,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bootstrap@^4.4.1: - version "4.5.2" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.2.tgz#a85c4eda59155f0d71186b6e6ad9b875813779ab" - integrity sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A== - bowser@^1.7.3: version "1.9.4" resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" @@ -3547,7 +3552,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== -errno@^0.1.3, errno@~0.1.7: +errno@^0.1.1, errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== @@ -4731,6 +4736,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + import-cwd@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" @@ -5402,6 +5412,11 @@ klona@^2.0.3: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.3.tgz#98274552c513583ad7a01456a789a2a0b4a2a538" integrity sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg== +klona@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" + integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -5409,6 +5424,30 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" +less-loader@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-7.0.2.tgz#0d73a49ec32a9d3ff12614598e6e2b47fb2a35c4" + integrity sha512-7MKlgjnkCf63E3Lv6w2FvAEgLMx3d/tNBExITcanAq7ys5U8VPWT3F6xcRjYmdNfkoQ9udoVFb1r2azSiTnD6w== + dependencies: + klona "^2.0.4" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +less@^3.12.2: + version "3.12.2" + resolved "https://registry.yarnpkg.com/less/-/less-3.12.2.tgz#157e6dd32a68869df8859314ad38e70211af3ab4" + integrity sha512-+1V2PCMFkL+OIj2/HrtrvZw0BC0sYLMICJfbQjuj/K8CEnlrFX6R5cKKgzzttsZDHyxQNL1jqMREjKN3ja/E3Q== + dependencies: + tslib "^1.10.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + native-request "^1.0.5" + source-map "~0.6.0" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -5620,7 +5659,7 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" -make-dir@^2.0.0: +make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== @@ -5749,7 +5788,7 @@ mime-types@~2.1.17, mime-types@~2.1.24: dependencies: mime-db "1.44.0" -mime@1.6.0: +mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -5933,6 +5972,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +native-request@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/native-request/-/native-request-1.0.8.tgz#8f66bf606e0f7ea27c0e5995eb2f5d03e33ae6fb" + integrity sha512-vU2JojJVelUGp6jRcLwToPoWGxSx23z/0iX+I77J3Ht17rf2INGjrhOoQnjVo60nQd8wVsgzKkPfRXBiVdD2ag== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8094,6 +8138,15 @@ schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0, schema-utils@^2.7 ajv "^6.12.4" ajv-keywords "^3.5.2" +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + screenfull@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7"