change(ui): integration changes
This commit is contained in:
parent
ee1a775379
commit
3f1473e653
9 changed files with 310 additions and 168 deletions
|
|
@ -157,7 +157,7 @@ def create_feature_flag(project_id: int, user_id: int, feature_flag_data: schema
|
|||
"""
|
||||
|
||||
if variants_len > 0:
|
||||
variants_query = f""",
|
||||
variants_query = f"""{conditions_len > 0 and "," or ""}
|
||||
inserted_variants AS (
|
||||
INSERT INTO feature_flags_variants(feature_flag_id, value, description, rollout_percentage, payload)
|
||||
VALUES {",".join([f"((SELECT feature_flag_id FROM inserted_flag),"
|
||||
|
|
|
|||
|
|
@ -1433,7 +1433,7 @@ class FeatureFlagSchema(BaseModel):
|
|||
flag_type: FeatureFlagType = Field(default=FeatureFlagType.single_variant)
|
||||
is_persist: Optional[bool] = Field(default=False)
|
||||
is_active: Optional[bool] = Field(default=True)
|
||||
conditions: List[FeatureFlagCondition] = Field(default=[], min_items=1)
|
||||
conditions: List[FeatureFlagCondition] = Field(default=[])
|
||||
variants: List[FeatureFlagVariant] = Field(default=[])
|
||||
|
||||
class Config:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import { Switch, Route, Redirect } from 'react-router';
|
||||
import { CLIENT_TABS, client as clientRoute } from 'App/routes';
|
||||
|
||||
|
|
@ -17,17 +17,15 @@ import Notifications from './Notifications';
|
|||
import Roles from './Roles';
|
||||
import SessionsListingSettings from 'Components/Client/SessionsListingSettings';
|
||||
|
||||
@withRouter
|
||||
export default class Client extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
interface MatchParams {
|
||||
activeTab?: string;
|
||||
}
|
||||
|
||||
setTab = (tab) => {
|
||||
this.props.history.push(clientRoute(tab));
|
||||
};
|
||||
const Client: React.FC<RouteComponentProps<MatchParams>> = ({ match }) => {
|
||||
const { activeTab } = match.params;
|
||||
const isIntegrations = activeTab === CLIENT_TABS.INTEGRATIONS;
|
||||
|
||||
renderActiveTab = () => (
|
||||
const renderActiveTab = () => (
|
||||
<Switch>
|
||||
<Route exact strict path={clientRoute(CLIENT_TABS.PROFILE)} component={ProfileSettings} />
|
||||
<Route exact strict path={clientRoute(CLIENT_TABS.SESSIONS_LISTING)} component={SessionsListingSettings} />
|
||||
|
|
@ -43,23 +41,18 @@ export default class Client extends React.PureComponent {
|
|||
</Switch>
|
||||
);
|
||||
|
||||
render() {
|
||||
const {
|
||||
match: {
|
||||
params: { activeTab },
|
||||
},
|
||||
} = this.props;
|
||||
return (
|
||||
<div className={cn('page-margin container-90 flex relative')}>
|
||||
<div className={styles.tabMenu}>
|
||||
<PreferencesMenu activeTab={activeTab} />
|
||||
<PreferencesMenu activeTab={activeTab!} />
|
||||
</div>
|
||||
<div className={cn('side-menu-margined w-full')}>
|
||||
<div className="bg-white w-full rounded-lg mx-auto mb-8 border" style={{ maxWidth: '1300px'}}>
|
||||
{activeTab && this.renderActiveTab()}
|
||||
<div className={cn('side-menu-margined w-full', { 'bg-white rounded-lg border' : !isIntegrations })}>
|
||||
<div className='w-full mx-auto mb-8' style={{ maxWidth: '1300px' }}>
|
||||
{activeTab && renderActiveTab()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default withRouter(Client);
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import { Icon } from 'UI';
|
||||
import cn from 'classnames';
|
||||
|
||||
|
||||
// const FILTERS = [
|
||||
// { key: 'all', name: 'All', icon: 'play' },
|
||||
// { key: 'issue-reporting', name: 'Issue Reporting', icon: 'play' },
|
||||
// { key: 'backend-logging', name: 'Backend Logging', icon: 'play' },
|
||||
// { key: 'stack-tracing', name: 'Stack Tracing', icon: 'play' },
|
||||
// { key: 'state-management', name: 'State Management', icon: 'play' },
|
||||
// { key: 'collaboration', name: 'Collaboration', icon: 'play' },
|
||||
// { key: 'plugins', name: 'Plugins', icon: 'play' }
|
||||
// ];
|
||||
|
||||
interface Props {
|
||||
onChange: any;
|
||||
activeItem: string;
|
||||
filters: any;
|
||||
}
|
||||
|
||||
const allItem = { key: 'all', title: 'All', icon: 'play' };
|
||||
|
||||
function FilterButton(props: { activeItem: string, item: any, onClick: () => any }) {
|
||||
return <div
|
||||
className={cn('cursor-pointer transition group rounded px-2 py-1 flex items-center uppercase text-sm hover:bg-active-blue hover:text-teal', {
|
||||
'bg-active-blue text-teal': props.activeItem === props.item.key
|
||||
})}
|
||||
style={{ height: '36px' }}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.item.icon && <Icon name={props.item.icon} className='mr-2' />}
|
||||
<span>{props.item.title}</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function IntegrationFilters(props: Props) {
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-4'>
|
||||
<FilterButton
|
||||
activeItem={props.activeItem}
|
||||
item={allItem}
|
||||
onClick={() => props.onChange(allItem.key)}
|
||||
/>
|
||||
{props.filters.map((item: any) => (
|
||||
<FilterButton activeItem={props.activeItem} item={item} onClick={() => props.onChange(item.key)} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default IntegrationFilters;
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { Icon, Tooltip } from 'UI';
|
||||
import { Icon } from 'UI';
|
||||
import stl from './integrationItem.module.css';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
interface Props {
|
||||
integration: any;
|
||||
|
|
@ -13,21 +14,29 @@ interface Props {
|
|||
const IntegrationItem = (props: Props) => {
|
||||
const { integration, integrated, hide = false } = props;
|
||||
return hide ? <></> : (
|
||||
<div className={cn(stl.wrapper, { [stl.integrated]: integrated })} onClick={(e) => props.onClick(e)}>
|
||||
<div
|
||||
className={cn('flex flex-col border p-3 bg-white relative justify-between cursor-pointer hover:bg-active-blue')}
|
||||
onClick={(e) => props.onClick(e)}
|
||||
style={{ height: '126px' }}
|
||||
>
|
||||
<div className='flex gap-3'>
|
||||
{integration.icon.length ?
|
||||
<img className='h-10 w-10' src={'/assets/' + integration.icon + '.svg'} alt='integration' /> :
|
||||
(<span style={{ fontSize: '3rem', lineHeight: '3rem' }}>{integration.header}</span>)}
|
||||
<div className='flex flex-col'>
|
||||
<h4 className='text-lg'>{integration.title}</h4>
|
||||
<p className='text-sm color-gray-medium m-0 p-0 h-3'>{integration.subtitle && integration.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{integrated && (
|
||||
<div className="m-2 absolute right-0 top-0 h-4 w-4 rounded-full bg-teal flex items-center justify-center">
|
||||
<Tooltip title="Integrated" delay={0}>
|
||||
<Icon name="check" size="14" color="white" />
|
||||
<Tooltip title='Integrated' delay={0}>
|
||||
<div className='ml-12 p-1 flex items-center justify-center color-tealx border rounded w-fit'>
|
||||
<Icon name='check-circle-fill' size='14' color='tealx' className="mr-2" />
|
||||
<span>Installed</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{integration.icon.length ? <img className="h-12 w-12" src={'/assets/' + integration.icon + '.svg'} alt="integration" /> : (
|
||||
<span style={{ fontSize: '3rem', lineHeight: '3rem' }}>{integration.header}</span>
|
||||
)}
|
||||
<div className="text-center mt-2">
|
||||
<h4 className="">{integration.title}</h4>
|
||||
{/* <p className="text-sm color-gray-medium m-0 p-0 h-3">{integration.subtitle && integration.subtitle}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,25 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import React, { useEffect } from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
import { fetch, init } from 'Duck/integrations/actions';
|
||||
import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations';
|
||||
import SiteDropdown from 'Shared/SiteDropdown';
|
||||
import ReduxDoc from './ReduxDoc';
|
||||
import VueDoc from './VueDoc';
|
||||
import GraphQLDoc from './GraphQLDoc';
|
||||
import NgRxDoc from './NgRxDoc';
|
||||
import MobxDoc from './MobxDoc';
|
||||
import ProfilerDoc from './ProfilerDoc';
|
||||
import AssistDoc from './AssistDoc';
|
||||
import PiniaDoc from './PiniaDoc';
|
||||
import ZustandDoc from './ZustandDoc';
|
||||
import MSTeams from './Teams';
|
||||
import DocCard from 'Shared/DocCard/DocCard';
|
||||
import { PageTitle, Tooltip } from 'UI';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
import BugsnagForm from './BugsnagForm';
|
||||
import CloudwatchForm from './CloudwatchForm';
|
||||
import DatadogForm from './DatadogForm';
|
||||
|
|
@ -13,25 +33,7 @@ import SentryForm from './SentryForm';
|
|||
import SlackForm from './SlackForm';
|
||||
import StackdriverForm from './StackdriverForm';
|
||||
import SumoLogicForm from './SumoLogicForm';
|
||||
import { fetch, init } from 'Duck/integrations/actions';
|
||||
import { fetchIntegrationList, setSiteId } from 'Duck/integrations/integrations';
|
||||
import { connect } from 'react-redux';
|
||||
import SiteDropdown from 'Shared/SiteDropdown';
|
||||
import ReduxDoc from './ReduxDoc';
|
||||
import VueDoc from './VueDoc';
|
||||
import GraphQLDoc from './GraphQLDoc';
|
||||
import NgRxDoc from './NgRxDoc';
|
||||
import MobxDoc from './MobxDoc';
|
||||
import ProfilerDoc from './ProfilerDoc';
|
||||
import AssistDoc from './AssistDoc';
|
||||
import { PageTitle, Tooltip } from 'UI';
|
||||
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import PiniaDoc from './PiniaDoc';
|
||||
import ZustandDoc from './ZustandDoc';
|
||||
import MSTeams from './Teams';
|
||||
import DocCard from 'Shared/DocCard/DocCard';
|
||||
import cn from 'classnames';
|
||||
import IntegrationFilters from 'Components/Client/Integrations/IntegrationFilters';
|
||||
|
||||
interface Props {
|
||||
fetch: (name: string, siteId: string) => void;
|
||||
|
|
@ -44,10 +46,12 @@ interface Props {
|
|||
hideHeader?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
function Integrations(props: Props) {
|
||||
const { initialSiteId, hideHeader = false, loading = false } = props;
|
||||
const { showModal } = useModal();
|
||||
const [integratedList, setIntegratedList] = React.useState<any>([]);
|
||||
const [integratedList, setIntegratedList] = useState<string[]>([]);
|
||||
const [activeFilter, setActiveFilter] = useState<string>('all');
|
||||
|
||||
useEffect(() => {
|
||||
const list = props.integratedList
|
||||
|
|
@ -68,7 +72,7 @@ function Integrations(props: Props) {
|
|||
|
||||
showModal(
|
||||
React.cloneElement(integration.component, {
|
||||
integrated: integratedList.includes(integration.slug),
|
||||
integrated: integratedList.includes(integration.slug)
|
||||
}),
|
||||
{ right: true, width }
|
||||
);
|
||||
|
|
@ -79,55 +83,55 @@ function Integrations(props: Props) {
|
|||
props.fetchIntegrationList(value.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-4 p-5">
|
||||
{!hideHeader && <PageTitle title={<div>Integrations</div>} />}
|
||||
{integrations.map((cat: any) => (
|
||||
<div className="grid grid-cols-6 border-b last:border-none gap-4">
|
||||
<div
|
||||
className={cn('col-span-4 mb-2 py-3', cat.docs ? 'col-span-4' : 'col-span-6')}
|
||||
key={cat.key}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<h2 className="font-medium text-lg">{cat.title}</h2>
|
||||
{cat.isProject && (
|
||||
<div className="flex items-center">
|
||||
<div className="flex flex-wrap mx-4">
|
||||
<SiteDropdown value={props.siteId} onChange={onChangeSelect} />
|
||||
</div>
|
||||
{loading && cat.isProject && <AnimatedSVG name={ICONS.LOADER} size={20} />}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="">{cat.description}</div>
|
||||
const onChange = (key: string) => {
|
||||
setActiveFilter(key);
|
||||
};
|
||||
|
||||
<div className="flex flex-wrap mt-4 gap-3">
|
||||
const filteredIntegrations = integrations.filter((cat: any) => {
|
||||
if (activeFilter === 'all') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return cat.key === activeFilter;
|
||||
});
|
||||
|
||||
const filters = integrations.map((cat: any) => ({
|
||||
key: cat.key,
|
||||
title: cat.title,
|
||||
icon: cat.icon,
|
||||
}));
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mb-4 p-5 bg-white rounded-lg border'>
|
||||
{!hideHeader && <PageTitle title={<div>Integrations</div>} />}
|
||||
|
||||
<IntegrationFilters onChange={onChange} activeItem={activeFilter} filters={filters}/>
|
||||
</div>
|
||||
|
||||
<div className='mb-4' />
|
||||
|
||||
{filteredIntegrations.map((cat: any) => (
|
||||
<div className='grid grid-cols-3 mt-4 gap-3 auto-cols-max'>
|
||||
{cat.integrations.map((integration: any) => (
|
||||
<React.Fragment key={integration.slug}>
|
||||
<Tooltip
|
||||
delay={50}
|
||||
title="Global configuration, available to all team members."
|
||||
disabled={!integration.shared}
|
||||
placement={'bottom'}
|
||||
>
|
||||
<IntegrationItem
|
||||
integrated={integratedList.includes(integration.slug)}
|
||||
integration={integration}
|
||||
onClick={() => onClick(integration, cat.title === 'Plugins' ? 500 : 350)}
|
||||
onClick={() =>
|
||||
onClick(integration, cat.title === 'Plugins' ? 500 : 350)
|
||||
}
|
||||
hide={
|
||||
(integration.slug === 'github' && integratedList.includes('jira')) ||
|
||||
(integration.slug === 'jira' && integratedList.includes('github'))
|
||||
(integration.slug === 'github' &&
|
||||
integratedList.includes('jira')) ||
|
||||
(integration.slug === 'jira' &&
|
||||
integratedList.includes('github'))
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{cat.docs && <div className="col-span-2 py-6">{cat.docs()}</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -136,62 +140,68 @@ export default connect(
|
|||
initialSiteId: state.getIn(['site', 'siteId']),
|
||||
integratedList: state.getIn(['integrations', 'list']) || [],
|
||||
loading: state.getIn(['integrations', 'fetchRequest', 'loading']),
|
||||
siteId: state.getIn(['integrations', 'siteId']),
|
||||
siteId: state.getIn(['integrations', 'siteId'])
|
||||
}),
|
||||
{ fetch, init, fetchIntegrationList, setSiteId }
|
||||
)(withPageTitle('Integrations - OpenReplay Preferences')(Integrations));
|
||||
|
||||
|
||||
const integrations = [
|
||||
{
|
||||
title: 'Issue Reporting and Collaborations',
|
||||
key: 1,
|
||||
title: 'Issue Reporting',
|
||||
key: 'issue-reporting',
|
||||
description: 'Seamlessly report issues or share issues with your team right from OpenReplay.',
|
||||
isProject: false,
|
||||
icon: 'integrations/issue-reporting',
|
||||
integrations: [
|
||||
{
|
||||
title: 'Jira',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'jira',
|
||||
category: 'Errors',
|
||||
icon: 'integrations/jira',
|
||||
component: <JiraForm />,
|
||||
component: <JiraForm />
|
||||
},
|
||||
{
|
||||
title: 'Github',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'github',
|
||||
category: 'Errors',
|
||||
icon: 'integrations/github',
|
||||
component: <GithubForm />,
|
||||
component: <GithubForm />
|
||||
},
|
||||
{
|
||||
title: 'Slack',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'slack',
|
||||
category: 'Errors',
|
||||
icon: 'integrations/slack',
|
||||
component: <SlackForm />,
|
||||
shared: true,
|
||||
shared: true
|
||||
},
|
||||
{
|
||||
title: 'MS Teams',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'msteams',
|
||||
category: 'Errors',
|
||||
icon: 'integrations/teams',
|
||||
component: <MSTeams />,
|
||||
shared: true,
|
||||
},
|
||||
],
|
||||
shared: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Backend Logging',
|
||||
key: 2,
|
||||
key: 'backend-logging',
|
||||
isProject: true,
|
||||
description:
|
||||
'Sync your backend errors with sessions replays and see what happened front-to-back.',
|
||||
docs: () => (
|
||||
<DocCard
|
||||
title="Why use integrations?"
|
||||
icon="question-lg"
|
||||
iconBgColor="bg-red-lightest"
|
||||
iconColor="red"
|
||||
title='Why use integrations?'
|
||||
icon='question-lg'
|
||||
iconBgColor='bg-red-lightest'
|
||||
iconColor='red'
|
||||
>
|
||||
Sync your backend errors with sessions replays and see what happened front-to-back.
|
||||
</DocCard>
|
||||
|
|
@ -200,81 +210,148 @@ const integrations = [
|
|||
{ title: 'Sentry', slug: 'sentry', icon: 'integrations/sentry', component: <SentryForm /> },
|
||||
{
|
||||
title: 'Bugsnag',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'bugsnag',
|
||||
icon: 'integrations/bugsnag',
|
||||
component: <BugsnagForm />,
|
||||
component: <BugsnagForm />
|
||||
},
|
||||
{
|
||||
title: 'Rollbar',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'rollbar',
|
||||
icon: 'integrations/rollbar',
|
||||
component: <RollbarForm />,
|
||||
component: <RollbarForm />
|
||||
},
|
||||
{
|
||||
title: 'Elasticsearch',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'elasticsearch',
|
||||
icon: 'integrations/elasticsearch',
|
||||
component: <ElasticsearchForm />,
|
||||
component: <ElasticsearchForm />
|
||||
},
|
||||
{
|
||||
title: 'Datadog',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'datadog',
|
||||
icon: 'integrations/datadog',
|
||||
component: <DatadogForm />,
|
||||
component: <DatadogForm />
|
||||
},
|
||||
{
|
||||
title: 'Sumo Logic',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'sumologic',
|
||||
icon: 'integrations/sumologic',
|
||||
component: <SumoLogicForm />,
|
||||
component: <SumoLogicForm />
|
||||
},
|
||||
{
|
||||
title: 'Stackdriver',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'stackdriver',
|
||||
icon: 'integrations/google-cloud',
|
||||
component: <StackdriverForm />,
|
||||
component: <StackdriverForm />
|
||||
},
|
||||
{
|
||||
title: 'CloudWatch',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'cloudwatch',
|
||||
icon: 'integrations/aws',
|
||||
component: <CloudwatchForm />,
|
||||
component: <CloudwatchForm />
|
||||
},
|
||||
{
|
||||
title: 'Newrelic',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
slug: 'newrelic',
|
||||
icon: 'integrations/newrelic',
|
||||
component: <NewrelicForm />,
|
||||
component: <NewrelicForm />
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
{
|
||||
title: 'Collaboration',
|
||||
key: 'collaboration',
|
||||
isProject: false,
|
||||
description: 'Share your sessions with your team and collaborate on issues.',
|
||||
integrations: []
|
||||
},
|
||||
{
|
||||
title: 'State Management',
|
||||
key: 'state-management',
|
||||
isProject: true,
|
||||
description: 'Sync your Redux or VueX store with sessions replays and see what happened front-to-back.',
|
||||
integrations: []
|
||||
},
|
||||
{
|
||||
title: 'Plugins',
|
||||
key: 3,
|
||||
key: 'plugins',
|
||||
isProject: true,
|
||||
docs: () => (
|
||||
<DocCard
|
||||
title="What are plugins?"
|
||||
icon="question-lg"
|
||||
iconBgColor="bg-red-lightest"
|
||||
iconColor="red"
|
||||
title='What are plugins?'
|
||||
icon='question-lg'
|
||||
iconBgColor='bg-red-lightest'
|
||||
iconColor='red'
|
||||
>
|
||||
Plugins capture your application’s store, monitor queries, track performance issues and even
|
||||
assist your end user through live sessions.
|
||||
</DocCard>
|
||||
),
|
||||
description:
|
||||
"Reproduce issues as if they happened in your own browser. Plugins help capture your application's store, HTTP requeets, GraphQL queries, and more.",
|
||||
'Reproduce issues as if they happened in your own browser. Plugins help capture your application\'s store, HTTP requeets, GraphQL queries, and more.',
|
||||
integrations: [
|
||||
{ title: 'Redux', icon: 'integrations/redux', component: <ReduxDoc /> },
|
||||
{ title: 'VueX', icon: 'integrations/vuejs', component: <VueDoc /> },
|
||||
{ title: 'Pinia', icon: 'integrations/pinia', component: <PiniaDoc /> },
|
||||
{ title: 'GraphQL', icon: 'integrations/graphql', component: <GraphQLDoc /> },
|
||||
{ title: 'NgRx', icon: 'integrations/ngrx', component: <NgRxDoc /> },
|
||||
{ title: 'MobX', icon: 'integrations/mobx', component: <MobxDoc /> },
|
||||
{ title: 'Profiler', icon: 'integrations/openreplay', component: <ProfilerDoc /> },
|
||||
{ title: 'Assist', icon: 'integrations/openreplay', component: <AssistDoc /> },
|
||||
{ title: 'Zustand', icon: '', header: '🐻', component: <ZustandDoc /> },
|
||||
],
|
||||
{
|
||||
title: 'Redux',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/redux', component: <ReduxDoc />
|
||||
},
|
||||
{
|
||||
title: 'VueX',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/vuejs',
|
||||
component: <VueDoc />
|
||||
},
|
||||
{
|
||||
title: 'Pinia',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/pinia',
|
||||
component: <PiniaDoc />
|
||||
},
|
||||
{
|
||||
title: 'GraphQL',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/graphql',
|
||||
component: <GraphQLDoc />
|
||||
},
|
||||
{
|
||||
title: 'NgRx',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/ngrx',
|
||||
component: <NgRxDoc />
|
||||
},
|
||||
{
|
||||
title: 'MobX',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/mobx',
|
||||
component: <MobxDoc />
|
||||
},
|
||||
{
|
||||
title: 'Profiler',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/openreplay',
|
||||
component: <ProfilerDoc />
|
||||
},
|
||||
{
|
||||
title: 'Assist',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: 'integrations/openreplay',
|
||||
component: <AssistDoc />
|
||||
},
|
||||
{
|
||||
title: 'Zustand',
|
||||
subtitle: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.',
|
||||
icon: '',
|
||||
header: '🐻',
|
||||
component: <ZustandDoc />
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default (props: Props) => {
|
|||
let iconColor = variant === 'text' || variant === 'default' ? 'gray-dark' : 'teal';
|
||||
|
||||
const variantClasses = {
|
||||
default: 'bg-white hover:bg-gray-light border border-gray-light',
|
||||
default: 'bg-white hover:!bg-gray-light border border-gray-light',
|
||||
primary: 'bg-teal color-white hover:bg-teal-dark',
|
||||
green: 'bg-green color-white hover:bg-green-dark',
|
||||
text: 'bg-transparent text-black hover:bg-active-blue hover:!text-teal hover-fill-teal',
|
||||
|
|
@ -49,7 +49,8 @@ export default (props: Props) => {
|
|||
variantClasses[variant],
|
||||
{ 'opacity-40 pointer-events-none': disabled },
|
||||
{ '!rounded-full h-10 w-10 justify-center': rounded },
|
||||
className
|
||||
className,
|
||||
'btn'
|
||||
);
|
||||
|
||||
if (variant === 'primary') {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ input.no-focus:focus {
|
|||
}
|
||||
}
|
||||
|
||||
.border-b {
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
background-color: none; /* 2 */
|
||||
background-image: none; /* 2 */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ module.exports = {
|
|||
'border-main': `0 0 0 1px ${colors['main']}`,
|
||||
'border-gray': '0 0 0 1px #999'
|
||||
},
|
||||
button: {
|
||||
'background-color': 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
|
|
@ -42,6 +45,6 @@ module.exports = {
|
|||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false
|
||||
// preflight: false
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue