Remove antd (#3754)

* remove antd as a dependency!

* update CTAs in blog posts

* create tab component to replace antd version

* remove references to antd (wip)

* rebuild compensation calculator

* rebuild contributors page

* replace spinner on slack

* remove antd from APIEndpoint

* redo yc-onboarding

* add dark mode styles

* fix missing angle bracket

* Reset Fonts.scss

* update yarn.lock

* redirect /startups to /pricing

* use pointer on dropdowns

* overflow tabs instead of wrap

Co-authored-by: Eli Kinsey <eli@ekinsey.dev>
This commit is contained in:
Paul Hultgren
2022-07-21 14:02:19 -07:00
committed by GitHub
parent dbdb6d780b
commit ec1e907b35
33 changed files with 865 additions and 1533 deletions

View File

@@ -3,3 +3,5 @@
*.lock
.cache/
public/
node_modules/
static/

View File

@@ -1 +1 @@
--add.ignore-workspace-root-check=true
--add.ignore-workspace-root-check true

View File

@@ -5,13 +5,12 @@ rootPage: /blog
sidebar: Blog
showTitle: true
hideAnchor: true
categories: ["Release notes", "Product updates"]
categories: ['Release notes', 'Product updates']
featuredImage: ../images/blog/array/default.png
featuredImageType: standard
---
import { Link } from 'gatsby'
import { Button } from 'antd'
Release 1.21 is a big one, on top of exciting new features and improvements, we put extra time into the overall stability of PostHog squashing dozens of issues. Some highlights of this release:
@@ -25,11 +24,13 @@ We received a lot of great feedback and issue reports and over this release cycl
Make sure to upgrade to get the new features, improvements and fixes.
<Link to="/docs/deployment">
<Button className="center" type="primary" size="large">
<br />
<div class="flex items-center justify-center">
<CallToAction to="/docs/deployment">
Install PostHog
</Button>
</Link>
</CallToAction>
</div>
<br />
@@ -383,4 +384,3 @@ In addition to the highlights listed above, we also merged a bunch of PRs improv
- Enable SSL PostgreSQL configuration through env variables [\#2967](https://github.com/PostHog/posthog/pull/2967) ([tmilicic](https://github.com/tmilicic))
<ArrayCTA />

View File

@@ -11,7 +11,6 @@ featuredImageType: standard
---
import { Link } from 'gatsby'
import { Button } from 'antd'
PostHog 1.22 is out with awesome new features, usability and performance improvements, and the usual bug squashing.
@@ -34,11 +33,13 @@ Thank you oshura3, we look forward to collaborating with you more!
- **Improvement:** Better tooling for updating data in dashboards
- **Improvement:** A whole new UX for individual person pages
<Link to="https://app.posthog.com/signup">
<Button className="center" type="primary" size="large">
<br />
<div class="flex items-center justify-center">
<CallToAction to="https://app.posthog.com/signup">
Try PostHog Cloud Now
</Button>
</Link>
</CallToAction>
</div>
<br />

View File

@@ -12,8 +12,6 @@ This page refers to our public endpoints, which use the same API key as the [Pos
> **Note:** For this API, you should use your 'Project API Key' from the 'Project' page in PostHog. This is the same key used in your frontend snippet.
# Sending events
To send events to PostHog, you can use any of [our libraries](/docs/integrate/overview) **or** any Mixpanel library by changing the `api_host` setting to the address of your instance.
@@ -169,12 +167,10 @@ curl -v -L --header "Content-Type: application/json" -d '{
**Meaning:** A `200: OK` response means we have successfully received the payload, it is in the correct format, and the project API key (token) is valid. It **does not** imply that events are valid and will be ingested. As mentioned under [Invalid events](#invalid-events), certain event validation errors may cause an event not to be ingested.
#### Status code: 400
##### Responses
```js
{
type: 'validation_error',
@@ -186,12 +182,10 @@ curl -v -L --header "Content-Type: application/json" -d '{
**Meaning:** We were unable to determine the project to associate the events with.
#### Status code: 401
##### Responses
```js
{
type: 'authentication_error',
@@ -202,15 +196,8 @@ curl -v -L --header "Content-Type: application/json" -d '{
**Meaning:** The token/API key you provided is invalid.
import { Divider } from 'antd'
<br />
<Divider dashed style={{ background: '#eeeeee', height: 1 }} />
<br />
```js
{
type: 'authentication_error',
@@ -225,7 +212,6 @@ import { Divider } from 'antd'
##### Responses
```js
{
type: 'server_error',
@@ -250,7 +236,6 @@ The three cases above will cause the event to not be ingested, but you will stil
This approach allows us to process events asynchronously if necessary, ensuring reliability and low latency for our event ingestion endpoints.
# Feature flags
PostHog offers support for [feature flags](/docs/user-guides/feature-flags), and you can use our APIs to create and make use of feature flags. However, it is important to note that while creating a feature flag is a private action that only your team should be able to perform, checking if a feature flag is active is not.

View File

@@ -4,9 +4,6 @@ sidebar: Docs
showTitle: true
---
import { Tabs } from 'antd'
export const TabPane = Tabs.TabPane
Historical data ingestion (or importing data), opposed to [live data ingestion](/docs/integrate/ingest-live-data), is the process of transporting data from external sources into PostHog so you can benefit from PostHog product analytics on historical data. It may be that you have historical data that you want to analyze along with new live data or that you have a requirement to periodically import data from third-party sources to augment your live data.
Whatever the reason for the historical data ingestion, this guide covers what to consider during that process.
@@ -87,34 +84,46 @@ import GoCapture from './server/go/snippets/capture.mdx'
import RubyCapture from './server/ruby/snippets/capture.mdx'
import CURLCapture from './server/curl/snippets/capture.mdx'
import Tab from "components/Tab"
> The server libraries handle batching capture requests. If you decide to use the API directly you will need to manage this yourself.
<Tabs defaultActiveKey="1" type="card" size="large" style={{marginBottom: "20px"}}>
<TabPane tab="Node.js" key="1">
<Tab.Group>
<Tab.List>
<Tab>Node.js</Tab>
<Tab>Python</Tab>
<Tab>PHP</Tab>
<Tab>Go</Tab>
<Tab>Ruby</Tab>
<Tab>cURL</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<NodeCapture />
<p>For more information see the <a href="/docs/integrate/server/node">Node.js docs</a>.</p>
</TabPane>
<TabPane tab="Python" key="2">
</Tab.Panel>
<Tab.Panel>
<PythonCapture />
<p>For more information see the <a href="/docs/integrate/server/python">python docs</a>.</p>
</TabPane>
<TabPane tab="PHP" key="3">
</Tab.Panel>
<Tab.Panel>
<PHPCapture />
<p>For more information see the <a href="/docs/integrate/server/php">PHP docs</a>.</p>
</TabPane>
<TabPane tab="Go" key="4">
</Tab.Panel>
<Tab.Panel>
<GoCapture />
<p>For more information see the <a href="/docs/integrate/server/go">Go docs</a>.</p>
</TabPane>
<TabPane tab="Ruby" key="5">
</Tab.Panel>
<Tab.Panel>
<RubyCapture />
<p>For more information see the <a href="/docs/integrate/server/ruby">Ruby docs</a>.</p>
</TabPane>
<TabPane tab="cURL" key="6">
</Tab.Panel>
<Tab.Panel>
<CURLCapture />
<p>For more information see the <a href="/docs/api">API docs</a>.</p>
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
## User identification
@@ -127,29 +136,39 @@ import GoIdentify from './server/go/snippets/identify.mdx'
import RubyIdentify from './server/ruby/snippets/identify.mdx'
import CURLIdentify from './server/curl/snippets/identify.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{marginBottom: "20px"}}>
<TabPane tab="Node.js" key="1">
<Tab.Group>
<Tab.List>
<Tab>Node.js</Tab>
<Tab>Python</Tab>
<Tab>PHP</Tab>
<Tab>Go</Tab>
<Tab>Ruby</Tab>
<Tab>cURL</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<NodeIdentify />
<p>For more information see the <a href="/docs/integrate/server/node">Node.js docs</a>.</p>
</TabPane>
<TabPane tab="Python" key="2">
</Tab.Panel>
<Tab.Panel>
<PythonIdentify />
<p>For more information see the <a href="/docs/integrate/server/python">Python docs</a>.</p>
</TabPane>
<TabPane tab="PHP" key="3">
</Tab.Panel>
<Tab.Panel>
<PHPIdentify />
<p>For more information see the <a href="/docs/integrate/server/php">PHP docs</a>.</p>
</TabPane>
<TabPane tab="Go" key="4">
</Tab.Panel>
<Tab.Panel>
<GoIdentify />
<p>For more information see the <a href="/docs/integrate/server/go">Go docs</a>.</p>
</TabPane>
<TabPane tab="Ruby" key="5">
</Tab.Panel>
<Tab.Panel>
<RubyIdentify />
<p>For more information see the <a href="/docs/integrate/server/ruby">Ruby docs</a>.</p>
</TabPane>
<TabPane tab="cURL" key="6">
</Tab.Panel>
<Tab.Panel>
<CURLIdentify />
<p>For more information see the <a href="/docs/api">API docs</a>.</p>
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>

View File

@@ -4,9 +4,6 @@ sidebar: Docs
showTitle: true
---
import { Tabs } from 'antd'
export const TabPane = Tabs.TabPane
PostHog enables you to analyze data in real-time, as events come in. Make full use of this power by ingesting live data with our analytics integrations: [client libraries](/docs/integrate/overview#client-libraries), [server libraries](/docs/integrate/overview#server-libraries), as well as [third-party platforms](/docs/integrate/overview#integrations).
The purpose of this guide is to help you understand some key concepts with a goal of ingesting live data into PostHog. For simplicity, we'll focus on _client_ libraries as a means of data ingestion.
@@ -37,27 +34,39 @@ import FlutterInstall from './client/flutter/snippets/install.mdx'
import ReactNativeInstall from './client/react-native/snippets/install.mdx'
import ReactNativeConfigure from './client/react-native/snippets/configure.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{ marginBottom: '20px' }}>
<TabPane tab="JavaScript" key="1">
import Tab from "components/Tab"
<Tab.Group>
<Tab.List>
<Tab>JavaScript</Tab>
<Tab>Android</Tab>
<Tab>iOS</Tab>
<Tab>Flutter</Tab>
<Tab>React Native</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<JSInstall />
</TabPane>
<TabPane tab="Android" key="2">
</Tab.Panel>
<Tab.Panel>
<AndroidInstall />
<AndroidConfigure />
</TabPane>
<TabPane tab="iOS" key="3">
</Tab.Panel>
<Tab.Panel>
<IOSInstall />
<IOSConfigure />
</TabPane>
<TabPane tab="Flutter" key="4">
</Tab.Panel>
<Tab.Panel>
<FlutterPackage />
<FlutterInstall />
</TabPane>
<TabPane tab="React Native" key="5">
</Tab.Panel>
<Tab.Panel>
<ReactNativeInstall />
<ReactNativeConfigure />
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
# Use autocapture
@@ -76,26 +85,36 @@ import AndroidCapture from './client/android/snippets/capture.mdx'
import IOSCapture from './client/ios/snippets/capture.mdx'
import ReactNativeCapture from './client/react-native/snippets/capture.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{marginBottom: "20px"}}>
<TabPane tab="JavaScript" key="1">
<Tab.Group>
<Tab.List>
<Tab>JavaScript</Tab>
<Tab>Android</Tab>
<Tab>iOS</Tab>
<Tab>Flutter</Tab>
<Tab>React Native</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<JSCapture />
</TabPane>
<TabPane tab="Android" key="2">
</Tab.Panel>
<Tab.Panel>
<AndroidCapture />
<p>For more information see the <a href="/docs/integrate/client/android#capture">Android capture docs</a>.</p>
</TabPane>
<TabPane tab="iOS" key="3">
</Tab.Panel>
<Tab.Panel>
<IOSCapture />
<p>For more information see the <a href="/docs/integrate/client/ios#capture">iOS capture docs</a>.</p>
</TabPane>
<TabPane tab="Flutter" key="4">
</Tab.Panel>
<Tab.Panel>
<p>See the <a href="/docs/integrate/client/flutter">Flutter library docs</a> for more information.</p>
</TabPane>
<TabPane tab="React Native" key="5">
</Tab.Panel>
<Tab.Panel>
<ReactNativeCapture />
<p>For more information see the <a href="/docs/integrate/client/react-native#capture">React Native capture docs</a>.</p>
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
# Identify users
@@ -104,25 +123,33 @@ import AndroidIdentify from './client/android/snippets/identify.mdx'
import IOSIdentify from './client/ios/snippets/identify.mdx'
import ReactNativeIdentify from './client/react-native/snippets/identify.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{ marginBottom: '20px' }}>
<TabPane tab="JavaScript" key="1">
<Tab.Group>
<Tab.List>
<Tab>JavaScript</Tab>
<Tab>Android</Tab>
<Tab>iOS</Tab>
<Tab>Flutter</Tab>
<Tab>React Native</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<JSIdentify />
</TabPane>
<TabPane tab="Android" key="2">
</Tab.Panel>
<Tab.Panel>
<AndroidIdentify />
</TabPane>
<TabPane tab="iOS" key="3">
</Tab.Panel>
<Tab.Panel>
<IOSIdentify />
</TabPane>
<TabPane tab="Flutter" key="4">
<p>
See the <a href="/docs/integrate/client/flutter">Flutter library docs</a> for more information.
</p>
</TabPane>
<TabPane tab="React Native" key="5">
</Tab.Panel>
<Tab.Panel>
<p> See the <a href="/docs/integrate/client/flutter">Flutter library docs</a> for more information.</p>
</Tab.Panel>
<Tab.Panel>
<ReactNativeIdentify />
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
# Event ingestion nuances

View File

@@ -85,9 +85,6 @@ The following examples use `company` as a group type and `id:5` as the group key
> **Tip:** Use a singular form of the group type throughout ingestion
import { Tabs } from 'antd'
export const TabPane = Tabs.TabPane
import GroupsIngestionPosthogJs from './snippets/groups-ingestion-posthog-js.mdx'
import GroupsIngestionPython from './snippets/groups-ingestion-python.mdx'
import GroupsIngestionPHP from './snippets/groups-ingestion-php.mdx'
@@ -96,29 +93,43 @@ import GroupsIngestionNode from './snippets/groups-ingestion-node.mdx'
import GroupsIngestionSegment from './snippets/groups-ingestion-segment.mdx'
import GroupsIngestionOther from './snippets/groups-ingestion-other.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{ marginBottom: '20px' }}>
<TabPane tab="JavaScript" key="1">
import Tab from "components/Tab"
<Tab.Group>
<Tab.List>
<Tab>JavaScript</Tab>
<Tab>Python</Tab>
<Tab>PHP</Tab>
<Tab>Go</Tab>
<Tab>Node.js</Tab>
<Tab>Segment</Tab>
<Tab>Other libraries</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<GroupsIngestionPosthogJs />
</TabPane>
<TabPane tab="Python" key="2">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionPython />
</TabPane>
<TabPane tab="PHP" key="3">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionPHP />
</TabPane>
<TabPane tab="Golang" key="4">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionGo />
</TabPane>
<TabPane tab="NodeJS" key="5">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionNode />
</TabPane>
<TabPane tab="Segment" key="6">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionSegment />
</TabPane>
<TabPane tab="Other libraries" key="7">
</Tab.Panel>
<Tab.Panel>
<GroupsIngestionOther />
</TabPane>
</Tabs>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
## Analysing group insights
@@ -174,23 +185,22 @@ import GroupsFeatureFlagsPHP from './snippets/groups-flags-php.mdx'
import GroupsFeatureFlagsGo from './snippets/groups-flags-go.mdx'
import GroupsFeatureFlagsNode from './snippets/groups-flags-node.mdx'
<Tabs defaultActiveKey="1" type="card" size="large" style={{ marginBottom: '20px' }}>
<TabPane tab="JavaScript" key="1">
<GroupsFeatureFlagsPosthogJs />
</TabPane>
<TabPane tab="Python" key="2">
<GroupsFeatureFlagsPython />
</TabPane>
<TabPane tab="PHP" key="3">
<GroupsFeatureFlagsPHP />
</TabPane>
<TabPane tab="Golang" key="4">
<GroupsFeatureFlagsGo />
</TabPane>
<TabPane tab="NodeJS" key="5">
<GroupsFeatureFlagsNode />
</TabPane>
</Tabs>
<Tab.Group>
<Tab.List>
<Tab>JavaScript</Tab>
<Tab>Python</Tab>
<Tab>PHP</Tab>
<Tab>Go</Tab>
<Tab>Node.js</Tab>
</Tab.List>
<Tab.Panels>
<Tab.Panel><GroupsFeatureFlagsPosthogJs /></Tab.Panel>
<Tab.Panel><GroupsFeatureFlagsPython /></Tab.Panel>
<Tab.Panel><GroupsFeatureFlagsPHP /></Tab.Panel>
<Tab.Panel><GroupsFeatureFlagsGo /></Tab.Panel>
<Tab.Panel><GroupsFeatureFlagsNode /></Tab.Panel>
</Tab.Panels>
</Tab.Group>
## Renaming group types

View File

@@ -43,15 +43,15 @@ In this case, we pass references to components that can then be used without imp
Because of the components passed to `MDXProvider`, I can include this hedgehog by just adding `<BasicHedgehogImage />` in my
MDX file - no import needed.
However, if I want to include something from a module, I can also do so. Here's how one would insert a spinner component from AntD:
However, if I want to include something from a module, I can also do so. Here's how one would insert a Transition component from Headless UI:
```js
import { Spin } from 'antd'
import { Transition } from '@headlessui/react'
## Some Markdown
<Spin />
<Transition>{/* ... */}</Transition>
```

View File

@@ -1481,14 +1481,23 @@
# Added: 2022-07-08
[[redirects]]
from = "/startups"
to = "/pricing#startup-plan"
to = "/pricing"
# Added: 2022-07-11
[[redirects]]
from = "/trial"
to = "/pricing"
# Added: 2022-07-12
[[redirects]]
from = "/schedule-demo"
to = "/book-a-demo"
# Added: 2022-07-14
[[redirects]]
from = "/apps/airbyte-export/docs"
to = "/docs/apps/airbyte-export"
# Added: 2022-07-14
[[redirects]]
from = "/apps/amazon-kinesis/docs"

View File

@@ -28,7 +28,8 @@
"@ant-design/icons": "^4.1.0",
"@docsearch/react": "^3.0.0-alpha.42",
"@fontsource/source-code-pro": "^4.5.4",
"@headlessui/react": "^1.4.0",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@mdx-js/mdx": "^1.6.19",
"@mdx-js/react": "^1.6.22",
"@popperjs/core": "^2.11.2",
@@ -36,7 +37,6 @@
"@supabase/supabase-js": "^1.29.4",
"@typescript-eslint/eslint-plugin": "^4.20.0",
"@uiw/react-md-editor": "^3.9.5",
"antd": "^3.23.2",
"chart.js": "^2.9.4",
"chrome-aws-lambda": "^10.1.0",
"cntl": "^1.0.0",
@@ -149,6 +149,7 @@
"@types/chart.js": "^2.9.31",
"@types/gatsby-plugin-breakpoints": "^1.3.2",
"@types/mdx-js__react": "^1.5.3",
"@types/react": "^16.0.0",
"@types/react-burger-menu": "^2.8.3",
"@types/react-dom": "^18.0.6",
"@types/react-helmet": "^6.1.0",

View File

@@ -1,16 +0,0 @@
import React from 'react'
import { MethodTags, method } from '../MethodTags'
interface EndpointProps {
endpoint: string
allowedMethods: method[]
}
export const Endpoint = ({ endpoint, allowedMethods }: EndpointProps) => {
return (
<div className="docs-endpoint-wrapper">
<MethodTags allowedMethods={allowedMethods} />
<h6 className="endpoint-text">{endpoint}</h6>
</div>
)
}

View File

@@ -1,30 +0,0 @@
import React from 'react'
import { Tag } from 'antd'
export type method = 'post' | 'put' | 'patch' | 'get' | 'delete'
interface MethodTagsProps {
allowedMethods: method[]
}
const methodToColor: Record<method, string> = {
post: 'green',
patch: 'purple',
get: 'blue',
delete: 'red',
put: 'gold',
}
const MethodTag = ({ method }: { method: method }) => {
return <Tag color={methodToColor[method]}>{method.toUpperCase()}</Tag>
}
export const MethodTags = ({ allowedMethods }: MethodTagsProps) => {
return (
<>
{allowedMethods.map((method) => (
<MethodTag method={method.toLowerCase() as method} key={method} />
))}
</>
)
}

View File

@@ -0,0 +1,125 @@
import React from 'react'
import { Combobox as HeadlessCombobox, Transition } from '@headlessui/react'
import { SelectorIcon, CheckIcon } from '@heroicons/react/outline'
import { classNames } from 'lib/utils'
type ComboboxProps = {
label: string
placeholder?: string
options: any[]
value: any | undefined
onChange: (option: any | undefined) => void
display?: (option: any) => string
}
export const Combobox = (props: ComboboxProps) => {
const [query, setQuery] = React.useState<string>('')
const [focused, setFocused] = React.useState<boolean>(false)
const filteredOptions =
query === ''
? props.options
: props.options.filter((option) =>
option.toLowerCase().replace(/\s+/g, '').includes(query.replace(/\s+/g, '').toLowerCase())
)
const currentValue = props.display ? props.display(props.value) : props.value
return (
<HeadlessCombobox
as="div"
className="relative focus:outline-none"
value={props.value}
onChange={(value) => {
props.onChange(value)
setQuery('')
}}
nullable
>
{({ open }) => (
<>
<HeadlessCombobox.Label className="text-sm">{props.label}</HeadlessCombobox.Label>
<HeadlessCombobox.Button
as="div"
className="flex items-center relative w-full max-w-md focus:outline-none shadow-sm mt-1.5"
>
<HeadlessCombobox.Input
onBlur={() => setFocused(false)}
onFocus={(event: React.FocusEvent<HTMLInputElement>) => {
event.target.value = ''
setFocused(true)
}}
onClick={(event: React.MouseEvent<HTMLInputElement>) =>
((event.target as HTMLInputElement).value = '')
}
onChange={(event) => setQuery(event.target.value)}
displayValue={props.display}
placeholder={currentValue || props.placeholder || 'Select a value'}
className={`relative block w-full text-left bg-white dark:bg-gray-accent-dark px-2.5 py-1.5 rounded border border-black/10 text-xs select-none focus-visible:outline-none focus:ring-1 focus:ring-orange focus:border-orange placeholder:text-gray-600 ${
focused ? '' : 'cursor-pointer'
}`}
/>
<span className="ml-3 absolute right-0 pr-2 pointer-events-none">
<SelectorIcon className="h-4 w-4 text-gray-accent-light" aria-hidden="true" />
</span>
</HeadlessCombobox.Button>
<Transition
show={open}
as={React.Fragment}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<HeadlessCombobox.Options className="absolute top-full mt-1 w-full max-w-lg bg-white dark:bg-gray-accent-dark rounded p-0 z-50 text-xs max-h-[12rem] overflow-y-scroll py-1 focus:outline-none space-y-1 shadow-xl border border-black/10">
{filteredOptions.length === 0 && query !== '' ? (
<div className="px-2.5 py-1 text-xs text-gray">No results</div>
) : (
filteredOptions.map((option) => (
<HeadlessCombobox.Option
value={option}
key={option}
className={({ active }) => `
list-none px-2.5 cursor-pointer focus:outline-none text-xs py-1
${active ? 'bg-orange text-white' : ''}
`}
>
{({ selected, active }) => (
<div className="flex justify-between items-center">
<span
className={classNames(
selected ? 'font-semibold' : 'font-normal',
'ml-1 block truncate text-xs'
)}
>
{props.display ? props.display(option) : option}
</span>
{selected && (
<span
className={classNames(
'flex items-center',
active ? 'text-white' : 'text-orange'
)}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
)}
</div>
)}
</HeadlessCombobox.Option>
))
)}
</HeadlessCombobox.Options>
</Transition>
</>
)}
</HeadlessCombobox>
)
}
export default Combobox

View File

@@ -1,166 +1,187 @@
import React, { useState, useEffect } from 'react'
import React from 'react'
import { RadioGroup } from '@headlessui/react'
import Combobox from './Combobox'
import { locationFactor } from './compensation_data/location_factor'
import { sfBenchmark } from './compensation_data/sf_benchmark'
import { Select, Statistic, Tag, Radio } from 'antd'
import { levelModifier } from './compensation_data/level_modifier'
import { stepModifier } from './compensation_data/step_modifier'
import { currencyData } from './compensation_data/currency'
import 'antd/lib/select/style/css'
import 'antd/lib/statistic/style/css'
import 'antd/lib/tag/style/css'
import 'antd/lib/radio/style/css'
import './style.scss'
const formatCur = (val: number, currency: string) => {
const formatCur = (val: number, currency = 'USD') => {
currency = currencyData[currency] ? currency : 'USD'
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
})
return formatter.format(Math.round(val * (currencyData[currency] || 1))).replace('.00', '')
}
export const CompensationCalculator = () => {
const [job, setJob] = useState('Full Stack Engineer')
const [country, setCountry] = useState('United States')
const [region, setRegion] = useState('San Francisco, California')
const [level, setLevel] = useState('Senior')
const [step, setStep] = useState('Thriving')
const Factor: React.FC = (props) => {
return (
<div className="px-1.5 bg-white dark:bg-gray-accent-dark rounded border border-black/10 text-gray-accent-dark dark:text-gray whitespace-nowrap text-2xs my-1">
{props.children}
</div>
)
}
useEffect(() => {
if (window) {
if (localStorage.getItem('job') && sfBenchmark[localStorage.getItem('job')])
setJob(localStorage.getItem('job'))
if (localStorage.getItem('country')) setCountry(localStorage.getItem('country') || 'United States')
if (localStorage.getItem('region')) setRegion(localStorage.getItem('region') || 'San Francisco, California')
if (localStorage.getItem('level') && levelModifier[localStorage.getItem('level')])
setLevel(localStorage.getItem('level') || 'Senior')
if (localStorage.getItem('step') && stepModifier[localStorage.getItem('step')])
setStep(localStorage.getItem('step') || 'Thriving')
export const CompensationCalculator = () => {
const [job, setJob] = React.useState<string | null>('Full Stack Engineer')
const [country, setCountry] = React.useState<string | null>('United States')
const [region, setRegion] = React.useState<string | null>('San Francisco, California')
const [level, setLevel] = React.useState<string | null>('Senior')
const [step, setStep] = React.useState<string | null>('Thriving')
React.useEffect(() => {
if (typeof window !== undefined) {
const savedState: {
job?: string | null
country?: string | null
region?: string | null
level?: string | null
step?: string | null
} = JSON.parse(localStorage.getItem('posthog-saved-compensation') || '{}')
if (savedState?.job && sfBenchmark[savedState.job]) {
setJob(savedState?.job || null)
}
if (savedState?.country) {
setCountry(savedState?.country || null)
if (savedState?.region) {
setRegion(savedState?.region || null)
}
}
if (savedState?.level && levelModifier[savedState.level]) {
setLevel(savedState?.level || null)
}
if (savedState?.step && stepModifier[savedState.step]) {
setRegion(savedState?.region || null)
}
}
}, [])
const unique = (arr: string[]) => Array.from(new Set(arr))
const setItem = (type: string) => {
return (value: string | boolean) => {
if (type === 'job' && typeof value === 'string') setJob(value)
if (type === 'country' && typeof value === 'string') {
return (value: any) => {
if (type === 'job') setJob(value)
if (type === 'country') {
setCountry(value)
setItem('region')(false)
setRegion(null)
}
if (type === 'region') setRegion(String(value))
if (type === 'level' && typeof value === 'string') {
setLevel(value)
if (type === 'region') setRegion(value)
if (type === 'level') setLevel(value)
if (type === 'step') setStep(value)
const state = {
job,
country,
region,
level,
step,
[type]: value,
}
if (type === 'step' && typeof value === 'string') setStep(value)
localStorage.setItem(type, String(value))
if (type === 'country') {
state.region = null
}
localStorage.setItem('posthog-saved-compensation', JSON.stringify(state))
}
}
const location =
country &&
region &&
region !== 'false' &&
locationFactor.filter((location) => location.country === country && location.area === region)[0]
const calculatedLocationFactor = location.locationFactor
const findLocation = (country: string | null, region: string | null) => {
return region && country
? locationFactor.find((location) => location.country === country && location.area === region)
: null
}
const countries = unique(locationFactor.map((l) => l.country))
const currentLocation = findLocation(country, region)
const countries = Array.from(new Set(locationFactor.map((l) => l.country)))
return (
<div style={{ fontSize: '0.85rem' }} className="compensation-calculator ph-no-capture">
<p>Select a role</p>
<Select style={{ width: '100%', marginBottom: '0.75rem' }} value={job} onChange={setItem('job')}>
{Object.keys(sfBenchmark).map((job) => (
<Select.Option value={job} key={job}>
{job}
</Select.Option>
))}
</Select>
<p>Country</p>
<Select
showSearch
style={{ width: '100%', marginBottom: '0.75rem' }}
value={country}
onChange={setItem('country')}
>
{countries.map((country) => (
<Select.Option value={country} key={country}>
{country}
</Select.Option>
))}
</Select>
<p>Region</p>
<Select
showSearch
style={{ width: '100%', marginBottom: '0.75rem' }}
value={region === 'false' ? '' : region}
<div className="ph-no-capture space-y-4 py-4">
<Combobox label="Select a role" value={job} onChange={setItem('job')} options={Object.keys(sfBenchmark)} />
<Combobox label="Country" value={country} onChange={setItem('country')} options={countries} />
<Combobox
label="Region"
value={region}
onChange={setItem('region')}
>
{locationFactor
options={locationFactor
.filter((location) => location.country === country)
.map((countryRegion) => (
<Select.Option value={countryRegion.area} key={countryRegion.area}>
{countryRegion.area} <span>{countryRegion.locationFactor}</span>
</Select.Option>
))}
</Select>
<p>Level</p>
<Radio.Group
style={{ width: '100%', marginBottom: '0.75rem' }}
value={level}
buttonStyle="solid"
onChange={(e) => setItem('level')(e.target.value)}
>
.map((location) => location.area)}
display={(area) => (area ? `${area} ${findLocation(country, area)?.locationFactor}` : '')}
/>
<RadioGroup as="div" className="block" value={level} onChange={setItem('level')}>
<RadioGroup.Label className="block text-sm">Level</RadioGroup.Label>
<div className="w-full max-w-xs md:max-w-full md:w-auto inline-flex flex-col items-stretch md:flex-row md:items-center bg-white dark:bg-gray-accent-dark rounded divide-y md:divide-y-0 md:divide-x divide-black/10 overflow-hidden shadow-sm border border-black/10 text-xs mt-1.5">
{Object.entries(levelModifier).map(([level, modifier]) => (
<Radio.Button value={level} key={level}>
<RadioGroup.Option
as="button"
key={level}
value={level}
className={({ checked }) => `
px-4 py-1.5 whitespace-nowrap text-left md:text-center
${checked ? 'bg-orange text-white' : 'hover:bg-black/10'}
`}
>
{level} <span>{modifier}</span>
</Radio.Button>
</RadioGroup.Option>
))}
</Radio.Group>
<p>Step</p>
<Select style={{ width: '100%', marginBottom: '0.75rem' }} value={step} onChange={setItem('step')}>
{Object.entries(stepModifier).map(([step, modifier]) => (
<Select.Option value={step} key={step}>
{step} {modifier[0]} - {modifier[1]}
</Select.Option>
))}
</Select>
<Statistic
title={<p>Base salary</p>}
value={
job && country && region && location && typeof calculatedLocationFactor === 'number'
</div>
</RadioGroup>
<Combobox
label="Step"
value={step}
onChange={setItem('step')}
options={Object.keys(stepModifier)}
display={(step: string) => `${step} ${stepModifier[step]?.[0]} - ${stepModifier[step]?.[1]}`}
/>
<div>
<label className="text-sm" htmlFor="compensation">
Base salary
</label>
<div className="text-2xl mt-1" id="compensation">
{job && country && region && currentLocation && level && step
? formatCur(
sfBenchmark[job] *
calculatedLocationFactor *
currentLocation.locationFactor *
levelModifier[level] *
stepModifier[step][0],
location?.currency
currentLocation.currency
) +
' - ' +
formatCur(
sfBenchmark[job] *
calculatedLocationFactor *
currentLocation.locationFactor *
levelModifier[level] *
stepModifier[step][1],
location?.currency
currentLocation.currency
) +
' + equity'
: '--'
}
/>
{job && country && region && (
<div>
<Tag>SF Benchmark: {formatCur(sfBenchmark[job])}</Tag> x{' '}
<Tag>Location factor: {calculatedLocationFactor}</Tag> x{' '}
<Tag>Level modifier: {levelModifier[level]}</Tag>x{' '}
<Tag>
: '--'}
</div>
{job && country && currentLocation && level && step && (
<div className="flex items-center flex-wrap space-x-2 text-gray">
<Factor>SF Benchmark: {formatCur(sfBenchmark[job], currentLocation?.currency)}</Factor>&nbsp;
<span>&times;</span>
<Factor>Location factor: {currentLocation?.locationFactor}</Factor>&nbsp;<span>&times;</span>
<Factor>Level modifier: {levelModifier[level]}</Factor>&nbsp;<span>&times;</span>
<Factor>
Step modifier: {stepModifier[step][0]} - {stepModifier[step][1]}
</Tag>
</Factor>
</div>
)}
<br />
</div>
</div>
)
}

View File

@@ -1,31 +0,0 @@
.compensation-calculator {
p {
padding: 0 !important;
margin-bottom: 0.25rem !important;
font-weight: 500 !important;
color: #000;
}
.ant-tag {
margin-right: 0;
}
}
body.dark {
.compensation-calculator {
span {
color: rgb(58, 58, 58);
}
span.ant-statistic-content-value {
color: #d4d4d4;
}
.ant-radio-button-wrapper-checked {
background: #d7beff !important;
border-color: #d7beff !important;
box-shadow: none;
color: white;
}
p {
color: #fff;
}
}
}

View File

@@ -1,31 +1,26 @@
import { Card, Col, Progress, Tag } from 'antd'
import { Spacer } from 'components/Spacer'
import { Link } from 'gatsby'
import React from 'react'
import Tooltip from '../Tooltip'
import { emojiKey } from './emojiKey'
import Tooltip from 'components/Tooltip'
import Link from 'components/Link'
import tooltipIcon from './images/tooltip.svg'
interface ContributorCardStructureMeta {
type ContributorCardProps = {
name: string
link: string
imageSrc: string
contributions: string[]
mvpWins: number
contributorLevel: number
}
interface ContributorCardMeta extends ContributorCardStructureMeta {
link: string
onClick?: () => void | undefined
}
const ContributorCardStructure = ({
export const ContributorCard = ({
name,
link,
imageSrc,
contributions,
mvpWins,
contributorLevel,
}: ContributorCardStructureMeta) => {
}: ContributorCardProps) => {
const handleTooltipContentClick = (e: React.MouseEvent, pageKey = '') => {
if (window) {
e.preventDefault()
@@ -47,78 +42,65 @@ const ContributorCardStructure = ({
pageKey: string
}) => (
<Tooltip title={title}>
<span onClick={(e) => handleTooltipContentClick(e, pageKey)} className="tooltip-content">
{children}
</span>
<span onClick={(e) => handleTooltipContentClick(e, pageKey)}>{children}</span>
</Tooltip>
)
return (
<Col sm={12} md={12} lg={8} xl={6} style={{ marginBottom: 20 }}>
<Card
style={{ height: 450, display: 'flex', marginBottom: 20 }}
bodyStyle={{ display: 'flex', flexDirection: 'column', flexGrow: 1 }}
className="card-elevated hover:shadow-none"
>
<Link to={link} className="relative">
{mvpWins > 0 ? (
<Tag color="transparent" style={{ maxWidth: '30%', position: 'absolute', right: 15, top: 15 }}>
<h4>
<div className="absolute top-4 right-4 text-xl">
<ContributorCardTooltip title={`Community MVP ${mvpWins}x`} pageKey="community-mvps">
{Array.from({ length: mvpWins }).map((_: any, i: number) => (
<span key={`trophy_${i}`}>🏆</span>
))}
</ContributorCardTooltip>
</h4>
</Tag>
</div>
) : null}
<div className="bg-white rounded shadow-lg flex flex-col items-center pt-6 pb-12 px-6 space-y-6">
<div className="space-y-1">
<img
src={imageSrc}
style={{ maxWidth: 60, maxHeight: 60, marginBottom: 0 }}
className="center rounded-full overflow-hidden"
className="mx-auto w-12 h-12 rounded-full overflow-hidden"
alt="contributor image"
/>
<h4 className="centered">{name}</h4>
<h4 className="text-black/80">{name}</h4>
</div>
<h6>
<div className="w-full space-y-1.5">
<div className="text-black/80">
Level {contributorLevel}
<ContributorCardTooltip title="Number of PRs merged" pageKey="level">
<img
src={tooltipIcon}
width="18"
height="18"
alt="More info"
className="inline-block opacity-25 hover:opacity-50 ml-1"
className="w-3.5 h-3.5 inline-block opacity-25 hover:opacity-50 ml-1"
/>
</ContributorCardTooltip>
</h6>
<Progress
strokeColor={{
'0%': '#F1A82C',
'100%': '#F54E00',
}}
percent={contributorLevel >= 50 ? 50 : (100 * contributorLevel) / 50}
className="progress-bar rounded-full overflow-hidden bg-gray-accent-light"
showInfo={false}
/>
<Spacer height={40} />
</div>
<h6>
<div className="relative w-full rounded-full overflow-hidden bg-gray-accent-light h-1.5">
<div
style={{ width: `${contributorLevel >= 50 ? 50 : (100 * contributorLevel) / 50}%` }}
className="absolute left-0 inset-y-0 h-full bg-gradient-to-r from-[#F1A82C] to-orange"
/>
</div>
</div>
<div className="space-y-1.5">
<div className="text-black/80">
Powers
<ContributorCardTooltip title="Types of contributions made" pageKey="powers">
<img
src={tooltipIcon}
width="18"
height="18"
alt="More info"
className="inline-block opacity-25 hover:opacity-50 ml-1"
className="w-3.5 h-3.5 inline-block opacity-25 hover:opacity-50 ml-1"
/>
</ContributorCardTooltip>
</h6>
</div>
<Spacer height={20} />
<h2>
<div className="text-xl">
{contributions.map((key) => (
<span key={key}>
<ContributorCardTooltip title={emojiKey[key].description} pageKey="powers">
@@ -126,46 +108,11 @@ const ContributorCardStructure = ({
</ContributorCardTooltip>{' '}
</span>
))}
</h2>
</Card>
</Col>
)
}
export const ContributorCard = ({
name,
link,
imageSrc,
onClick,
contributions,
mvpWins,
contributorLevel,
}: ContributorCardMeta) => {
const ContributorDetails = () => (
<ContributorCardStructure
name={name}
imageSrc={imageSrc}
contributions={contributions}
mvpWins={mvpWins}
contributorLevel={contributorLevel}
/>
)
return (
<div className="contributor-card-wrapper">
{onClick ? (
<span onClick={onClick}>
<ContributorDetails />
</span>
) : link.includes('.') ? (
<a href={link}>
<ContributorDetails />
</a>
) : (
<Link to={link}>
<ContributorDetails />
</Link>
)}
</div>
</div>
</div>
</Link>
)
}
export default ContributorCard

View File

@@ -1,17 +1,38 @@
import React from 'react'
import { Input } from 'antd'
import { useActions } from 'kea'
import { contributorsLogic } from 'logic/contributorsLogic'
export const ContributorSearch = () => {
const { processSearchInput } = useActions(contributorsLogic)
return (
<div className="max-w-xs mx-auto">
<Input.Search
className="contributor-search"
size="large"
onChange={(e) => processSearchInput(e.target.value)}
<div className="flex flex-col justify-center relative mx-auto mb-0 w-full max-w-lg">
<div className="absolute left-4 w-4 h-4">
<svg className="opacity-50" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
<g opacity="1" clipPath="url(#a)">
<path
d="m18 15.964-4.794-4.793A7.2 7.2 0 1 0 .001 7.2a7.2 7.2 0 0 0 11.17 6.006L15.963 18 18 15.964ZM2.04 7.2A5.16 5.16 0 0 1 7.2 2.043 5.16 5.16 0 1 1 2.04 7.2Z"
fill="#90794B"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h18v18H0z" />
</clipPath>
</defs>
</svg>
</div>
<input
onChange={(e) => processSearchInput(e.target.value)}
name="contributor-search"
placeholder="Search contributors..."
autoFocus={true}
className="pl-10 py-3 text-base bg-white dark:bg-gray-accent-dark rounded-full w-full ring-red shadow-lg shadow-[0_100px_80px_0_rgba(0,0,0,0.07),0px_14.5036px_24.1177px_rgba(0,0,0,0.0395839),0_6.68266px_10.0172px_rgba(0,0,0,0.0291065),0_4.88627px_3.62304px_rgba(0,0,0,0.0214061)]"
/>
<button className="hidden px-6 py-3 bg-red text-base shadow-md rounded-sm text-white font-bold">
Search
</button>
</div>
)
}

View File

@@ -1,4 +1,3 @@
import 'antd/lib/card/style/css'
import Byline from 'components/Blog/BlogAuthor/Byline'
import { graphql, Link, useStaticQuery } from 'gatsby'
import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image'

View File

@@ -0,0 +1,23 @@
import React from 'react'
type SpinnerProps = {
className?: string
}
export const Spinner = ({ className }: SpinnerProps) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
version="1.1"
className={`w-8 h-8 animate-spin text-gray-accent-light dark:text-gray-accent-dark ${className}`}
>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g fill="currentColor" fillRule="evenodd">
<path d="M10,3.5 C6.41015,3.5 3.5,6.41015 3.5,10 C3.5,10.4142 3.16421,10.75 2.75,10.75 C2.33579,10.75 2,10.4142 2,10 C2,5.58172 5.58172,2 10,2 C14.4183,2 18,5.58172 18,10 C18,14.4183 14.4183,18 10,18 C9.58579,18 9.25,17.6642 9.25,17.25 C9.25,16.8358 9.58579,16.5 10,16.5 C13.5899,16.5 16.5,13.5899 16.5,10 C16.5,6.41015 13.5899,3.5 10,3.5 Z" />
</g>
</g>
</svg>
)
}
export default Spinner

View File

@@ -0,0 +1,56 @@
import React from 'react'
import { Tab as HeadlessTab } from '@headlessui/react'
import { classNames } from 'lib/utils'
export const Tab: React.FC & {
Group: typeof HeadlessTab.Group
List: typeof HeadlessTab.List
Panels: typeof HeadlessTab.Panels
Panel: typeof HeadlessTab.Panel
} = ({ children }) => {
return (
<HeadlessTab
className={({ selected }) =>
classNames(
selected ? 'bg-red text-white' : 'bg-white dark:bg-gray-accent-dark',
'px-4 py-1.5 rounded shadow-sm text-sm font-medium whitespace-nowrap'
)
}
>
{children}
</HeadlessTab>
)
}
const TabGroup: typeof HeadlessTab.Group = ({ children }) => {
return (
<HeadlessTab.Group as="div" className="my-6">
{children}
</HeadlessTab.Group>
)
}
TabGroup.displayName = 'TabGroup'
const TabList: typeof HeadlessTab.List = ({ children, className, ...props }) => {
return (
<HeadlessTab.List {...props} className={`space-x-3 flex items-center overflow-x-auto ${className}`}>
{children}
</HeadlessTab.List>
)
}
TabList.displayName = 'TabList'
const TabPanels: typeof HeadlessTab.Panels = ({ children }) => {
return <HeadlessTab.Panels className="mt-4">{children}</HeadlessTab.Panels>
}
TabPanels.displayName = 'TabPanels'
Tab.Group = TabGroup
Tab.List = TabList
Tab.Panel = HeadlessTab.Panel
Tab.Panels = TabPanels
export default Tab

View File

@@ -13,7 +13,7 @@ export default function Tooltip({ children, title }: { children: JSX.Element; ti
})}
<span
role="tooltip"
className="bg-primary text-white rounded-md px-2 py-1 group-hover:visible invisible text-xs"
className="bg-primary text-white rounded-md px-2 py-1 group-hover:visible invisible text-xs z-50"
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}

View File

@@ -15,6 +15,10 @@ export const unsafeHash = (str: string) => {
return String(a)
}
export const classNames = (...classes: (string | null | undefined)[]) => {
return classes.filter(Boolean).join(' ')
}
export const getPluginImageSrc = (plugin: LibraryPluginType) =>
plugin.imageLink
? plugin.imageLink
@@ -72,6 +76,7 @@ export const scrollWithOffset = (id: string, offset: number) => {
// tests email address for RFC 5322 compliance
export function isValidEmailAddress(email: string): boolean {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const re =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(String(email).toLowerCase())
}

View File

@@ -1,7 +1,5 @@
// AUTO GENERATED FILE
import { Endpoint } from './components/APIDocs/Endpoint'
import { MethodTags } from './components/APIDocs/MethodTags'
import { Accordion } from './components/Accordion'
import { AnchorScrollNavbar } from './components/AnchorScrollNavbar'
import { AnimateIntoView } from './components/AnimateIntoView'
@@ -96,10 +94,12 @@ import { FeaturedSectionTextRight } from './components/Sections/FeaturedSectionT
import { FeaturedSectionTripleImage } from './components/Sections/FeaturedSectionTripleImage'
import { SliderNav } from './components/SliderNav'
import { Spacer } from './components/Spacer'
import { Spinner } from './components/Spinner'
import { StarRepoButton } from './components/StarRepoButton'
import { StarUsBanner } from './components/StarUsBanner'
import { Structure } from './components/Structure'
import { Subscribe } from './components/Subscribe'
import { Tab } from './components/Tab'
import { TeamQuote } from './components/TeamQuote'
import { Tooltip } from './components/Tooltip'
import { TotalCountries } from './components/TotalCountries'
@@ -112,8 +112,6 @@ import { WorkableSnippet } from './components/WorkableSnippet'
import { ZoomImage } from './components/ZoomImage'
export const shortcodes = {
Endpoint,
MethodTags,
Accordion,
AnchorScrollNavbar,
AnimateIntoView,
@@ -208,10 +206,12 @@ export const shortcodes = {
FeaturedSectionTripleImage,
SliderNav,
Spacer,
Spinner,
StarRepoButton,
StarUsBanner,
Structure,
Subscribe,
Tab,
TeamQuote,
Tooltip,
TotalCountries,

View File

@@ -1,31 +1,19 @@
import React, { useState } from 'react'
import Layout from '../components/Layout'
import { Spacer } from '../components/Spacer'
import { Row, Tabs, Spin } from 'antd'
import { useActions, useValues } from 'kea'
import React from 'react'
import Layout from 'components/Layout'
import { Spacer } from 'components/Spacer'
import { useValues } from 'kea'
import { contributorsLogic } from '../logic/contributorsLogic'
import { SEO } from '../components/seo'
import { SEO } from 'components/seo'
import pluginLibraryOgImage from '../images/posthog-plugins.png'
import { ContributorCard } from 'components/ContributorCard'
import { Contributor } from 'types'
import { ContributorSearch } from 'components/ContributorSearch'
import { ContributorsChart } from 'components/ContributorsChart'
import 'antd/lib/input/style/css'
const { TabPane } = Tabs
import Tab from 'components/Tab'
import Spinner from 'components/Spinner'
export const ContributorsPage = () => {
const { setSearchQuery } = useActions(contributorsLogic)
const { filteredContributors, contributorsLoading } = useValues(contributorsLogic)
const [activeTab, setActiveTab] = useState('list')
const handleTabClick = (newTab: string) => {
setActiveTab(newTab)
if (newTab === 'list') {
setSearchQuery('')
}
}
return (
<div className="contributors-page-wrapper">
@@ -38,21 +26,21 @@ export const ContributorsPage = () => {
<div className="centered" style={{ margin: 'auto' }}>
<Spacer />
<h1 className="center">Contributors</h1>
<Tabs activeKey={activeTab} onChange={(key) => handleTabClick(key)}>
<TabPane tab="List" key="list" />
<TabPane tab="Stats" key="stats" />
</Tabs>
<Spacer height={20} />
<Tab.Group>
<Tab.List className="justify-center my-8">
<Tab>List</Tab>
<Tab>Stats</Tab>
</Tab.List>
{activeTab === 'list' ? (
<>
<Tab.Panels>
<Tab.Panel>
<div className="flex flex-col items-center space-y-8 px-6">
<ContributorSearch />
<Spacer height={20} />
<Row gutter={16} style={{ marginTop: 16, marginRight: 10, marginLeft: 10, minHeight: 600 }}>
{contributorsLoading ? (
<Spin size="large" style={{ position: 'fixed', top: '50%', left: '50%' }} />
<Spinner />
) : (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full max-w-6xl mx-auto">
{filteredContributors.map((contributor: Contributor) => (
<ContributorCard
key={contributor.login}
@@ -64,13 +52,18 @@ export const ContributorsPage = () => {
contributorLevel={contributor.level}
/>
))}
</>
</div>
)}
</Row>
</>
) : (
</div>
</Tab.Panel>
<Tab.Panel>
<ContributorsChart />
)}
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<Spacer height={20} />
</div>
<Spacer />
</Layout>

View File

@@ -1,19 +0,0 @@
import React from 'react'
import Layout from '../components/Layout'
import { Spacer } from '../components/Spacer'
import './styles/yc-onboarding.scss'
import { SEO } from '../components/seo'
import Contact from 'components/Contact'
export const ScheduleDemo = () => (
<Layout>
<SEO title="Schedule Demo • PostHog" />
<div className="get-in-touch-wrapper">
<Spacer />
<h1 className="centered">Get in touch</h1>
<Contact />
</div>
</Layout>
)
export default ScheduleDemo

View File

@@ -1,16 +1,16 @@
import React, { useEffect, useState } from 'react'
import { Spin } from 'antd'
import queryString from 'query-string'
import { SEO } from '../components/seo'
import Spinner from 'components/Spinner'
import { SEO } from 'components/seo'
function Slack() {
/* This component will redirect the user to the Slack users group. */
const [source, setSource] = useState(null)
const [source, setSource] = useState<string | null>(null)
const slackUrl = 'https://join.slack.com/t/posthogusers/shared_invite/zt-1chf8vjjr-uyu88Xvsu1cSEi3ILFqSqw'
useEffect(() => {
const { s } = queryString.parse(location.search)
const s = new URLSearchParams(window.location.search).get('s')
setSource(s)
/* Wait for any UTM tags to be registered in a $pageview,
we wait a few more seconds if the user is coming from app so
they can read the additional instructions.
@@ -22,22 +22,17 @@ function Slack() {
return (
<>
<SEO title="PostHog Community Slack" />
<div
style={{ display: 'flex', alignItems: 'center', marginTop: 48, fontSize: 16, flexDirection: 'column' }}
>
<h1 style={{ marginBottom: '1rem' }}>We're redirecting you to Slack.</h1>
<div className="flex flex-col items-center mt-6 space-y-6">
<h1 className="mb-0">We're redirecting you to Slack.</h1>
{source === 'app' && (
<div style={{ fontSize: '1.1rem', color: 'var(--muted)' }}>
Remember to use the{' '}
<b>
<span style={{ color: 'var(--danger)' }}>same email</span> you used to sign up
</b>{' '}
<div className="text-gray">
Remember to use the <span className="text-red font-bold">same email</span> you used to sign up
in the PostHog app.
</div>
)}
<div style={{ marginTop: '2rem' }}>
<Spin size="large" />
</div>
<Spinner />
</div>
</>
)

View File

@@ -1,15 +0,0 @@
.get-in-touch-wrapper {
p {
margin: 0 auto 1em auto;
font-size: 15px !important;
max-width: 36rem;
display: block;
text-align: center;
}
@media screen and (max-width: 767px) {
p {
max-width: 90%;
}
}
}

View File

@@ -1,60 +0,0 @@
import React from 'react'
import { Link } from 'gatsby'
import Layout from '../components/Layout'
import { Row, Col, Button, Icon } from 'antd'
import { SEO } from '../components/seo'
import './styles/trial.scss'
const TrialPage = () => (
<Layout>
<div className="trial-page-wrapper">
<div className="trial-page-container">
<SEO title="PostHog Trial" description="Get started, for free." />
<Row gutter={[24, 24]}>
<Col span={24} align="middle">
<h1>Try PostHog - free for 30 days</h1>
</Col>
</Row>
<Row gutter={[16, 96]} className="card-row">
<Col xs={24} sm={24} md={12} lg={12} xl={12} className="card-col">
<h2>
<Icon type="cloud" theme="filled" /> Cloud
</h2>
<h3>Just create an account.</h3>
<p>
Select this option if you want to quickly try the PostHog features and don't want to worry
about installing it yourself.
</p>
<p>
<a href="https://app.posthog.com/signup">
<Button type="primary" size="large">
Sign Up
</Button>
</a>
</p>
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={12} className="card-col">
<h2>
<Icon type="hdd" theme="filled" /> Open Source
</h2>
<h3>Host your own instance.</h3>
<p>
Select this option if you want to install our open source platform on your own
infrastructure.
</p>
<p>
<Link to="/docs/self-host">
<Button type="primary" size="large">
Self Deploy
</Button>
</Link>
</p>
</Col>
</Row>
<Row className="spacer-row"></Row>
</div>
</div>
</Layout>
)
export default TrialPage

View File

@@ -3,8 +3,6 @@ import Layout from '../components/Layout'
import { DemoScheduler } from '../components/DemoScheduler'
import { Spacer } from '../components/Spacer'
import { Link } from 'gatsby'
import { Button } from 'antd'
import './styles/yc-onboarding.scss'
const DemoCallInfo = () => (
<>
@@ -38,17 +36,12 @@ export const YCOnboarding = () => {
const [showInfo, setShowInfo] = useState(false)
return (
<Layout>
<div className="get-in-touch-wrapper">
<div className="flex flex-col items-stretch w-full max-w-4xl mx-auto">
<Spacer />
<h1 className="centered">PostHog YC Onboarding</h1>
<Button
className="centered"
style={{ margin: 'auto' }}
type="primary"
onClick={() => setShowInfo(!showInfo)}
>
<button onClick={() => setShowInfo(!showInfo)} className="text-orange font-semibold w-32 mx-auto">
{showInfo ? 'Hide Info' : 'Show Info'}
</Button>
</button>
<Spacer height={25} />
{showInfo ? <DemoCallInfo /> : null}
<DemoScheduler iframeSrc="https://calendly.com/d/dsb-3y3-9v9" />

View File

@@ -1,4 +1,3 @@
import { Button, Select } from 'antd'
import { Link } from 'react-scroll'
import Scrollspy from 'react-scrollspy'
import '@fontsource/source-code-pro'
@@ -6,7 +5,6 @@ import CodeBlock from 'components/Home/CodeBlock'
import Layout from 'components/Layout'
import { SEO } from 'components/seo'
import 'core-js/features/array/at'
import 'core-js/features/string/replace-all'
import { graphql } from 'gatsby'
import { getCookie, setCookie } from 'lib/utils'
import * as OpenAPISampler from 'openapi-sampler'
@@ -15,6 +13,8 @@ import React, { useEffect, useRef, useState } from 'react'
import { push as Menu } from 'react-burger-menu'
import ReactMarkdown from 'react-markdown'
import '../styles/api-docs.scss'
import { Listbox } from '@headlessui/react'
import { SelectorIcon } from '@heroicons/react/outline'
import MainSidebar from 'components/Docs/MainSidebar'
import Navigation from 'components/Docs/Navigation'
import SectionLinks from 'components/SectionLinks'
@@ -285,9 +285,9 @@ function ResponseBody({ item, objects }) {
<>
<h4>Response</h4>
<div className="response-wrapper">
<Button type="link" style={{ padding: 0 }} onClick={() => setShowResponse(!showResponse)}>
<button className="mt-2 text-sm" onClick={() => setShowResponse(!showResponse)}>
{showResponse ? 'Hide' : 'Show'} response
</Button>
</button>
<br />
{showResponse && (
<Params
@@ -338,24 +338,36 @@ function RequestExample({ item, objects, exampleLanguage, setExampleLanguage })
let queryParams = item.parameters?.filter((param) => param.in === 'query')
return (
<>
<div className="code-example justify-between flex">
<div className="code-example justify-between flex my-1.5">
<div className="text-gray">
<code className={`text-${mapVerbsColor[item.httpVerb]}`}>{item.httpVerb.toUpperCase()} </code>
<code>{path}</code>
</div>
<Select
value={exampleLanguage}
onChange={(key) => setExampleLanguage(key)}
bordered={false}
style={{ border: 0, background: 'transparent', width: 90 }}
<Listbox as="div" className="relative" value={exampleLanguage} onChange={setExampleLanguage}>
<Listbox.Button className="bg-white pl-2 pr-10 py-1 rounded-sm text-xs flex items-center ">
<span className="text-gray-accent-dark font-normal">{exampleLanguage}</span>
<SelectorIcon className="w-3 h-3 text-gray absolute right-1.5" />
</Listbox.Button>
<Listbox.Options
as="ul"
className="absolute right-0 top-full mt-1 bg-white list-none px-0 py-1 rounded-sm shadow focus:outline-none z-50"
>
<Select.Option key="curl" value="curl">
curl
</Select.Option>
<Select.Option key="python" value="python">
python
</Select.Option>
</Select>
{['curl', 'python'].map((option) => (
<Listbox.Option
key={option}
value={option}
className={({ active, selected }) =>
`${selected ? 'font-semibold' : ''} ${
active ? 'bg-orange text-white' : 'text-gray-accent-dark'
} w-full pl-3 pr-6 cursor-pointer`
}
>
<span className="text-xs">{option}</span>
</Listbox.Option>
))}
</Listbox.Options>
</Listbox>
</div>
{exampleLanguage === 'curl' && (

807
yarn.lock

File diff suppressed because it is too large Load Diff