move api and "few" files to new project store

This commit is contained in:
nick-delirium 2024-09-18 15:39:33 +02:00
parent 82586d23b2
commit df20cd5333
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
18 changed files with 255 additions and 331 deletions

View file

@ -3,8 +3,8 @@ import { Map } from 'immutable';
import React, { Suspense, lazy } from 'react';
import { connect } from 'react-redux';
import { Redirect, Route, Switch } from 'react-router-dom';
import AdditionalRoutes from 'App/AdditionalRoutes';
import { observer } from 'mobx-react-lite'
import { useStore } from "./mstore";
import { GLOBAL_HAS_NO_RECORDINGS } from 'App/constants/storageKeys';
import { OB_DEFAULT_TAB } from 'App/routes';
import { Loader } from 'UI';
@ -110,20 +110,20 @@ const SCOPE_SETUP = routes.scopeSetup();
interface Props {
tenantId: string;
siteId: string;
sites: Map<string, any>;
onboarding: boolean;
scope: number;
}
function PrivateRoutes(props: Props) {
const { onboarding, sites, siteId } = props;
const { projectsStore } = useStore();
const sites = projectsStore.list;
const siteId = projectsStore.siteId;
const { onboarding } = props;
const hasRecordings = sites.some(s => s.recorded);
const redirectToSetup = props.scope === 0;
const redirectToOnboarding =
!onboarding && (localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true' || !hasRecordings) && props.scope > 0;
const siteIdList: any = sites.map(({ id }) => id).toJS();
!onboarding && (localStorage.getItem(GLOBAL_HAS_NO_RECORDINGS) === 'true' || (sites.length > 0 && !hasRecordings)) && props.scope > 0;
const siteIdList: any = sites.map(({ id }) => id);
return (
<Suspense fallback={<Loader loading={true} className="flex-1" />}>
<Switch key="content">
@ -292,7 +292,5 @@ function PrivateRoutes(props: Props) {
export default connect((state: any) => ({
onboarding: state.getIn(['user', 'onboarding']),
scope: getScope(state),
sites: state.getIn(['site', 'list']),
siteId: state.getIn(['site', 'siteId']),
tenantId: state.getIn(['user', 'account', 'tenantId']),
}))(PrivateRoutes);
}))(observer(PrivateRoutes));

View file

@ -13,56 +13,55 @@ import {
SPOT_ONBOARDING
} from 'App/constants/storageKeys';
import Layout from 'App/layout/Layout';
import { useStore, withStore } from 'App/mstore';
import { useStore } from 'App/mstore';
import { checkParam, handleSpotJWT, isTokenExpired } from 'App/utils';
import { ModalProvider } from 'Components/Modal';
import { ModalProvider as NewModalProvider } from 'Components/ModalContext';
import { setSessionPath } from 'Duck/sessions';
import { fetchList as fetchSiteList } from 'Duck/site';
import { init as initSite } from 'Duck/site';
import { fetchUserInfo, getScope, logout, setJwt } from 'Duck/user';
import { Loader } from 'UI';
import * as routes from './routes';
import { observer } from 'mobx-react-lite'
interface RouterProps
extends RouteComponentProps,
ConnectedProps<typeof connector> {
isLoggedIn: boolean;
sites: Map<string, any>;
loading: boolean;
changePassword: boolean;
isEnterprise: boolean;
fetchUserInfo: () => any;
setSessionPath: (path: any) => any;
fetchSiteList: (siteId?: number) => any;
match: {
params: {
siteId: string;
};
};
mstore: any;
setJwt: (params: { jwt: string; spotJwt: string | null }) => any;
initSite: (site: any) => void;
scopeSetup: boolean;
localSpotJwt: string | null;
}
const Router: React.FC<RouterProps> = (props) => {
const {
isLoggedIn,
siteId,
sites,
loading,
userInfoLoading,
location,
fetchUserInfo,
fetchSiteList,
history,
setSessionPath,
scopeSetup,
localSpotJwt,
logout
logout,
scopeSetup,
setJwt,
} = props;
const { customFieldStore } = useStore();
const mstore = useStore();
const { customFieldStore, projectsStore } = mstore;
const siteId = projectsStore.siteId;
const sitesLoading = projectsStore.sitesLoading;
const sites = projectsStore.list;
const loading = Boolean(userInfoLoading || (!scopeSetup && !siteId) || sitesLoading);
const initSite = projectsStore.initProject;
const fetchSiteList = projectsStore.fetchList;
const params = new URLSearchParams(location.search);
const spotCb = params.get('spotCallback');
@ -80,7 +79,7 @@ const Router: React.FC<RouterProps> = (props) => {
handleSpotLogin(spotJwt);
}
if (urlJWT) {
props.setJwt({ jwt: urlJWT, spotJwt: spotJwt ?? null });
setJwt({ jwt: urlJWT, spotJwt: spotJwt ?? null });
}
};
@ -108,9 +107,9 @@ const Router: React.FC<RouterProps> = (props) => {
localStorage.setItem(SPOT_ONBOARDING, 'true');
}
await fetchUserInfo();
const siteIdFromPath = parseInt(location.pathname.split('/')[1]);
const siteIdFromPath = location.pathname.split('/')[1];
await fetchSiteList(siteIdFromPath);
props.mstore.initClient();
mstore.initClient();
if (localSpotJwt && !isTokenExpired(localSpotJwt)) {
handleSpotLogin(localSpotJwt);
@ -177,13 +176,13 @@ const Router: React.FC<RouterProps> = (props) => {
const fetchData = async () => {
if (siteId && siteId !== lastFetchedSiteIdRef.current) {
const activeSite = sites.find((s) => s.id == siteId);
props.initSite(activeSite);
lastFetchedSiteIdRef.current = activeSite.id;
initSite(activeSite ?? {});
lastFetchedSiteIdRef.current = activeSite?.id;
await customFieldStore.fetchListActive(siteId + '');
}
};
fetchData();
void fetchData();
}, [siteId]);
const lastFetchedSiteIdRef = useRef<any>(null);
@ -229,7 +228,6 @@ const Router: React.FC<RouterProps> = (props) => {
};
const mapStateToProps = (state: Map<string, any>) => {
const siteId = state.getIn(['site', 'siteId']);
const jwt = state.getIn(['user', 'jwt']);
const changePassword = state.getIn(['user', 'account', 'changePassword']);
const userInfoLoading = state.getIn([
@ -237,21 +235,14 @@ const mapStateToProps = (state: Map<string, any>) => {
'fetchUserInfoRequest',
'loading'
]);
const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']);
const scopeSetup = getScope(state) === 0;
const loading =
Boolean(userInfoLoading) ||
Boolean(sitesLoading) ||
(!scopeSetup && !siteId);
return {
siteId,
changePassword,
sites: state.getIn(['site', 'list']),
jwt,
scopeSetup,
localSpotJwt: state.getIn(['user', 'spotJwt']),
isLoggedIn: jwt !== null && !changePassword,
scopeSetup,
loading,
userInfoLoading,
email: state.getIn(['user', 'account', 'email']),
account: state.getIn(['user', 'account']),
organisation: state.getIn(['user', 'account', 'name']),
@ -266,12 +257,10 @@ const mapStateToProps = (state: Map<string, any>) => {
const mapDispatchToProps = {
fetchUserInfo,
setSessionPath,
fetchSiteList,
setJwt,
initSite,
logout
};
const connector = connect(mapStateToProps, mapDispatchToProps);
export default withStore(withRouter(connector(Router)));
export default withRouter(connector(observer(Router)));

View file

@ -1,7 +1,6 @@
import store from 'App/store';
import { queried } from './routes';
import { setJwt } from 'Duck/user';
import { projectStore } from 'App/mstore';
const siteIdRequiredPaths: string[] = [
'/dashboard',
@ -55,12 +54,12 @@ export const clean = (obj: any, forbiddenValues: any[] = [undefined, '']): any =
export default class APIClient {
private init: RequestInit;
private readonly siteId: string | undefined;
private siteId: string | undefined;
private siteIdCheck: (() => { siteId: string | null }) | undefined;
private refreshingTokenPromise: Promise<string> | null = null;
constructor() {
const jwt = store.getState().getIn(['user', 'jwt']);
const { siteId } = projectStore.getSiteId();
this.init = {
headers: new Headers({
Accept: 'application/json',
@ -70,7 +69,10 @@ export default class APIClient {
if (jwt !== null) {
(this.init.headers as Headers).set('Authorization', `Bearer ${jwt}`);
}
this.siteId = siteId || undefined;
}
setSiteIdCheck(checker: () => { siteId: string | null }): void {
this.siteIdCheck = checker
}
private getInit(method: string = 'GET', params?: any, reqHeaders?: Record<string, any>): RequestInit {
@ -102,6 +104,7 @@ export default class APIClient {
delete init.body; // GET requests shouldn't have a body
}
this.siteId = this.siteIdCheck?.().siteId ?? undefined;
return init;
}

View file

@ -1,6 +1,5 @@
import React, { useEffect, useState } from 'react';
import cn from 'classnames';
import { connect } from 'react-redux';
import withPageTitle from 'HOCs/withPageTitle';
import { Button, Loader, NoContent, Icon, Tooltip, Divider } from 'UI';
import SiteDropdown from 'Shared/SiteDropdown';
@ -12,20 +11,17 @@ import { useModal } from 'App/components/Modal';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
interface CustomFieldsProps {
sites: any;
}
const CustomFields: React.FC<CustomFieldsProps> = (props) => {
const [currentSite, setCurrentSite] = useState(props.sites.get(0));
const CustomFields = () => {
const { customFieldStore: store, projectsStore } = useStore();
const sites = projectsStore.list;
const [currentSite, setCurrentSite] = useState(sites[0]);
const [deletingItem, setDeletingItem] = useState<number | null>(null);
const { showModal, hideModal } = useModal();
const { customFieldStore: store } = useStore();
const fields = store.list;
const [loading, setLoading] = useState(false);
useEffect(() => {
const activeSite = props.sites.get(0);
const activeSite = sites[0];
if (!activeSite) return;
setCurrentSite(activeSite);
@ -34,7 +30,7 @@ const CustomFields: React.FC<CustomFieldsProps> = (props) => {
store.fetchList(activeSite.id).finally(() => {
setLoading(false);
});
}, [props.sites]);
}, [sites]);
const handleInit = (field?: any) => {
console.log('field', field);
@ -45,7 +41,7 @@ const CustomFields: React.FC<CustomFieldsProps> = (props) => {
};
const onChangeSelect = ({ value }: { value: { value: number } }) => {
const site = props.sites.find((s: any) => s.id === value.value);
const site = sites.find((s: any) => s.id === value.value);
setCurrentSite(site);
setLoading(true);
@ -109,6 +105,4 @@ const CustomFields: React.FC<CustomFieldsProps> = (props) => {
);
};
export default connect((state: any) => ({
sites: state.getIn(['site', 'list'])
}))(withPageTitle('Metadata - OpenReplay Preferences')(observer(CustomFields)));
export default withPageTitle('Metadata - OpenReplay Preferences')(observer(CustomFields));

View file

@ -8,7 +8,9 @@ import { Button, Checkbox, Form, Input, Loader } from 'UI';
function IntegrationForm(props: any) {
const { formFields, name, integrated } = props;
const { integrationsStore } = useStore();
const { integrationsStore, projectsStore } = useStore();
const sites = projectsStore.list;
const initialSiteId = projectsStore.siteId;
const integrationStore = integrationsStore[name as unknown as namedStore];
const config = integrationStore.instance;
const loading = integrationStore.loading;
@ -18,7 +20,7 @@ function IntegrationForm(props: any) {
const fetchIntegrationList = integrationsStore.integrations.fetchIntegrations;
const fetchList = () => {
void fetchIntegrationList(props.initialSiteId);
void fetchIntegrationList(initialSiteId);
};
const write = ({ target: { value, name: key, type, checked } }) => {
@ -104,7 +106,4 @@ function IntegrationForm(props: any) {
);
}
export default connect((state: any) => ({
sites: state.getIn(['site', 'list']),
initialSiteId: state.getIn(['site', 'siteId']),
}))(observer(IntegrationForm));
export default observer(IntegrationForm);

View file

@ -41,11 +41,11 @@ interface Props {
}
function Integrations(props: Props) {
const { integrationsStore } = useStore();
const { integrationsStore, projectsStore } = useStore();
const siteId = projectsStore.siteId;
const fetchIntegrationList = integrationsStore.integrations.fetchIntegrations;
const storeIntegratedList = integrationsStore.integrations.list;
const { siteId, hideHeader = false } = props;
const { hideHeader = false } = props;
const { showModal } = useModal();
const [integratedList, setIntegratedList] = useState<string[]>([]);
const [activeFilter, setActiveFilter] = useState<string>('all');
@ -162,11 +162,7 @@ function Integrations(props: Props) {
);
}
export default connect((state: any) => ({
siteId: state.getIn(['site', 'siteId']),
}))(
withPageTitle('Integrations - OpenReplay Preferences')(observer(Integrations))
);
export default withPageTitle('Integrations - OpenReplay Preferences')(observer(Integrations))
const integrations = [
{

View file

@ -1,6 +1,5 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import { connect } from 'react-redux';
import { useStore } from 'App/mstore';
import ToggleContent from 'Components/shared/ToggleContent';
@ -8,9 +7,9 @@ import { CodeBlock } from 'UI';
import DocLink from 'Shared/DocLink/DocLink';
const PiniaDoc = (props) => {
const { integrationsStore } = useStore();
const sites = props.sites ? props.sites.toJS() : [];
const PiniaDoc = () => {
const { integrationsStore, projectsStore } = useStore();
const sites = projectsStore.list;
const siteId = integrationsStore.integrations.siteId;
const projectKey = siteId
? sites.find((site) => site.id === siteId)?.projectKey
@ -110,9 +109,4 @@ piniaStorePlugin(examplePiniaStore)
PiniaDoc.displayName = 'PiniaDoc';
export default connect((state: any) => {
const sites = state.getIn(['site', 'list']);
return {
sites,
};
})(observer(PiniaDoc));
export default observer(PiniaDoc);

View file

@ -1,8 +1,7 @@
import React from 'react';
import { Tooltip, Button } from 'UI';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import { init, remove, fetchGDPR } from 'Duck/site';
import { observer } from 'mobx-react-lite';
import { connect } from 'react-redux';
import { useModal } from 'App/components/Modal';
import NewSiteForm from '../NewSiteForm';
@ -10,16 +9,15 @@ import NewSiteForm from '../NewSiteForm';
const PERMISSION_WARNING = 'You dont have the permissions to perform this action.';
const LIMIT_WARNING = 'You have reached site limit.';
function AddProjectButton({ isAdmin = false, init = () => {} }: any) {
const { userStore } = useStore();
function AddProjectButton({ isAdmin = false }: any) {
const { userStore, projectsStore } = useStore();
const init = projectsStore.initProject;
const { showModal, hideModal } = useModal();
const limtis = useObserver(() => userStore.limits);
const canAddProject = useObserver(
() => isAdmin && (limtis.projects === -1 || limtis.projects > 0)
);
const limits = userStore.limits;
const canAddProject = isAdmin && (limits.projects === -1 || limits.projects > 0)
const onClick = () => {
init();
init({});
showModal(<NewSiteForm onClose={hideModal} />, { right: true });
};
return (
@ -34,4 +32,4 @@ function AddProjectButton({ isAdmin = false, init = () => {} }: any) {
);
}
export default connect(null, { init, remove, fetchGDPR })(AddProjectButton);
export default observer(AddProjectButton);

View file

@ -1,7 +1,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { observer } from 'mobx-react-lite';
import { useStore } from "App/mstore";
import { Form, Button, Input, Icon } from 'UI';
import { editGDPR, saveGDPR } from 'Duck/site';
import { validateNumber } from 'App/validate';
import styles from './siteForm.module.css';
import Select from 'Shared/Select';
@ -12,124 +12,118 @@ const inputModeOptions = [
{ label: 'Obscure all inputs', value: 'hidden' },
];
@connect(state => ({
site: state.getIn([ 'site', 'instance' ]),
gdpr: state.getIn([ 'site', 'instance', 'gdpr' ]),
saving: state.getIn([ 'site', 'saveGDPR', 'loading' ]),
}), {
editGDPR,
saveGDPR,
})
export default class GDPRForm extends React.PureComponent {
onChange = ({ target: { name, value } }) => {
function GDPRForm(props) {
const { projectsStore } = useStore();
const site = projectsStore.instance;
const gdpr = site.gdpr;
const saving = false //projectsStore.;
const editGDPR = projectsStore.editGDPR;
const saveGDPR = projectsStore.saveGDPR;
const onChange = ({ target: { name, value } }) => {
if (name === "sampleRate") {
if (!validateNumber(value, { min: 0, max: 100 })) return;
if (value.length > 1 && value[0] === "0") {
value = value.slice(1);
}
}
this.props.editGDPR({ [ name ]: value });
editGDPR({ [ name ]: value });
}
onSampleRateBlur = ({ target: { name, value } }) => { //TODO: editState hoc
const onSampleRateBlur = ({ target: { name, value } }) => { //TODO: editState hoc
if (value === ''){
this.props.editGDPR({ sampleRate: 100 });
editGDPR({ sampleRate: 100 });
}
}
onChangeSelect = ({ name, value }) => {
this.props.editGDPR({ [ name ]: value });
const onChangeSelect = ({ name, value }) => {
props.editGDPR({ [ name ]: value });
};
onChangeOption = ({ target: { checked, name } }) => {
this.props.editGDPR({ [ name ]: checked });
const onChangeOption = ({ target: { checked, name } }) => {
editGDPR({ [ name ]: checked });
}
onSubmit = (e) => {
const onSubmit = (e) => {
e.preventDefault();
const { site, gdpr } = this.props;
this.props.saveGDPR(site.id, gdpr);
void saveGDPR(site.id);
}
return (
<Form className={ styles.formWrapper } onSubmit={ onSubmit }>
<div className={ styles.content }>
<Form.Field>
<label>{ 'Name' }</label>
<div>{ site.host }</div>
</Form.Field>
<Form.Field>
<label>{ 'Session Capture Rate' }</label>
<Input
icon="percent"
name="sampleRate"
value={ gdpr.sampleRate }
onChange={ onChange }
onBlur={ onSampleRateBlur }
className={ styles.sampleRate }
/>
</Form.Field>
render() {
const {
site, onClose, saving, gdpr,
} = this.props;
<Form.Field>
<label htmlFor="defaultInputMode">{ 'Data Recording Options' }</label>
<Select
name="defaultInputMode"
options={ inputModeOptions }
onChange={ onChangeSelect }
placeholder="Default Input Mode"
value={ gdpr.defaultInputMode }
/>
</Form.Field>
return (
<Form className={ styles.formWrapper } onSubmit={ this.onSubmit }>
<div className={ styles.content }>
<Form.Field>
<label>{ 'Name' }</label>
<div>{ site.host }</div>
</Form.Field>
<Form.Field>
<label>{ 'Session Capture Rate' }</label>
<Input
icon="percent"
name="sampleRate"
value={ gdpr.sampleRate }
onChange={ this.onChange }
onBlur={ this.onSampleRateBlur }
className={ styles.sampleRate }
<Form.Field>
<label>
<input
name="maskNumbers"
type="checkbox"
checked={ gdpr.maskNumbers }
onChange={ onChangeOption }
/>
</Form.Field>
{ 'Do not record any numeric text' }
<div className={ styles.controlSubtext }>{ 'If enabled, OpenReplay will not record or store any numeric text for all sessions.' }</div>
</label>
</Form.Field>
<Form.Field>
<label htmlFor="defaultInputMode">{ 'Data Recording Options' }</label>
<Select
name="defaultInputMode"
options={ inputModeOptions }
onChange={ this.onChangeSelect }
placeholder="Default Input Mode"
value={ gdpr.defaultInputMode }
// className={ styles.dropdown }
<Form.Field>
<label>
<input
name="maskEmails"
type="checkbox"
checked={ gdpr.maskEmails }
onChange={ onChangeOption }
/>
</Form.Field>
{ 'Do not record email addresses ' }
<div className={ styles.controlSubtext }>{ 'If enabled, OpenReplay will not record or store any email address for all sessions.' }</div>
</label>
</Form.Field>
<Form.Field>
<label>
<input
name="maskNumbers"
type="checkbox"
checked={ gdpr.maskNumbers }
onChange={ this.onChangeOption }
/>
{ 'Do not record any numeric text' }
<div className={ styles.controlSubtext }>{ 'If enabled, OpenReplay will not record or store any numeric text for all sessions.' }</div>
</label>
</Form.Field>
<Form.Field>
<label>
<input
name="maskEmails"
type="checkbox"
checked={ gdpr.maskEmails }
onChange={ this.onChangeOption }
/>
{ 'Do not record email addresses ' }
<div className={ styles.controlSubtext }>{ 'If enabled, OpenReplay will not record or store any email address for all sessions.' }</div>
</label>
</Form.Field>
<div className={ styles.blockIpWarapper }>
<div className={ styles.button } onClick={ this.props.toggleBlockedIp }>
{ 'Block IP' } <Icon name="next1" size="18" />
</div>
<div className={ styles.blockIpWarapper }>
<div className={ styles.button } onClick={ props.toggleBlockedIp }>
{ 'Block IP' } <Icon name="next1" size="18" />
</div>
</div>
</div>
<div className={ styles.footer }>
<Button
variant="outline"
className="float-left mr-2"
loading={ saving }
content="Update"
/>
<Button onClick={ onClose } content="Cancel" />
</div>
</Form>
);
}
<div className={ styles.footer }>
<Button
variant="outline"
className="float-left mr-2"
loading={ saving }
content="Update"
/>
<Button onClick={ onClose } content="Cancel" />
</div>
</Form>
)
}
export default observer(GDPRForm);

View file

@ -3,7 +3,6 @@ import { connect, ConnectedProps } from 'react-redux';
import { Tag } from 'antd';
import cn from 'classnames';
import { Loader, Button, TextLink, NoContent, Pagination, PageTitle, Divider, Icon } from 'UI';
import { init, remove, fetchGDPR, setSiteId } from 'Duck/site';
import withPageTitle from 'HOCs/withPageTitle';
import stl from './sites.module.css';
import NewSiteForm from './NewSiteForm';
@ -16,9 +15,11 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { useModal } from 'App/components/Modal';
import CaptureRate from 'Shared/SessionSettings/components/CaptureRate';
import { BranchesOutlined } from '@ant-design/icons';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore'
type Project = {
id: number;
id: string;
name: string;
conditionsCount: number;
platform: 'web' | 'mobile';
@ -29,7 +30,11 @@ type Project = {
type PropsFromRedux = ConnectedProps<typeof connector>;
const Sites = ({ loading, sites, user, init }: PropsFromRedux) => {
const Sites = ({ user }: PropsFromRedux) => {
const { projectsStore } = useStore();
const sites = projectsStore.list;
const loading = projectsStore.sitesLoading;
const init = projectsStore.initProject
const [searchQuery, setSearchQuery] = useState('');
const [showCaptureRate, setShowCaptureRate] = useState(true);
const [activeProject, setActiveProject] = useState<Project | null>(null);
@ -140,7 +145,7 @@ const Sites = ({ loading, sites, user, init }: PropsFromRedux) => {
</div>
}
size="small"
show={!loading && filteredSites.size === 0}
show={!loading && filteredSites.length === 0}
>
<div className="grid grid-cols-12 gap-2 w-full items-center px-5 py-3 font-medium">
<div className="col-span-4">Project Name</div>
@ -160,7 +165,7 @@ const Sites = ({ loading, sites, user, init }: PropsFromRedux) => {
<div className="w-full flex items-center justify-center py-10">
<Pagination
page={page}
total={filteredSites.size}
total={filteredSites.length}
onPageChange={(page) => updatePage(page)}
limit={pageSize}
/>
@ -181,18 +186,10 @@ const Sites = ({ loading, sites, user, init }: PropsFromRedux) => {
};
const mapStateToProps = (state: any) => ({
site: state.getIn(['site', 'instance']),
sites: state.getIn(['site', 'list']),
loading: state.getIn(['site', 'loading']),
user: state.getIn(['user', 'account']),
account: state.getIn(['user', 'account']),
});
const connector = connect(mapStateToProps, {
init,
remove,
fetchGDPR,
setSiteId,
});
const connector = connect(mapStateToProps, null);
export default connector(withPageTitle('Projects - OpenReplay Preferences')(Sites));
export default connector(withPageTitle('Projects - OpenReplay Preferences')(observer(Sites)));

View file

@ -1,32 +1,21 @@
import React from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { withSiteId } from 'App/routes';
import { setSiteId } from 'Duck/site';
import { observer } from 'mobx-react-lite'
import { useStore } from "App/mstore";
export default BaseComponent => {
@withRouter
@connect((state, props) => ({
urlSiteId: props.match.params.siteId,
siteId: state.getIn(['site', 'siteId']),
}), {
setSiteId,
})
class WrappedClass extends React.PureComponent {
push = (location) => {
const { history, siteId } = this.props;
if (typeof location === 'string') {
history.push(withSiteId(location, siteId));
} else if (typeof location === 'object') {
history.push({ ...location, pathname: withSiteId(location.pathname, siteId) });
}
}
export default BaseComponent => withRouter(observer((props) => {
const { history, ...other } = props
const { projectsStore } = useStore();
const siteId = projectsStore.siteId
render() {
const { history, ...other } = this.props
return <BaseComponent {...other} history={{ ...history, push: this.push }} />
const push = (location) => {
if (typeof location === 'string') {
history.push(withSiteId(location, siteId));
} else if (typeof location === 'object') {
history.push({ ...location, pathname: withSiteId(location.pathname, siteId) });
}
}
return WrappedClass
}
return <BaseComponent {...other} history={{ ...history, push: push }} />
}))

View file

@ -1,40 +1,39 @@
import React from 'react';
import { connect } from 'react-redux';
import { setSiteId } from 'Duck/site';
import React, { useEffect, useRef } from 'react';
import { useStore } from "App/mstore";
import { observer } from 'mobx-react-lite'
export default (BaseComponent) => {
@connect((state, props) => ({
urlSiteId: props.match.params.siteId,
siteId: state.getIn(['site', 'siteId']),
}), {
setSiteId,
})
class WrapperClass extends React.PureComponent {
state = { load: false }
constructor(props) {
super(props);
if (props.urlSiteId && props.urlSiteId !== props.siteId) {
props.setSiteId(props.urlSiteId);
const withSiteIdUpdater = (BaseComponent) => {
const WrapperComponent = (props) => {
const { projectsStore } = useStore();
const siteId = projectsStore.siteId;
const setSiteId = projectsStore.setSiteId;
const urlSiteId = props.match.params.siteId
const prevSiteIdRef = useRef(props.siteId);
useEffect(() => {
if (urlSiteId && urlSiteId !== siteId) {
props.setSiteId(urlSiteId);
}
}
componentDidUpdate(prevProps) {
const { urlSiteId, siteId, location: { pathname }, history } = this.props;
}, []);
useEffect(() => {
const { location: { pathname }, history } = props;
const shouldUrlUpdate = urlSiteId && parseInt(urlSiteId, 10) !== parseInt(siteId, 10);
if (shouldUrlUpdate) {
const path = ['', siteId].concat(pathname.split('/').slice(2)).join('/');
history.push(path);
}
const shouldBaseComponentReload = shouldUrlUpdate || siteId !== prevProps.siteId;
if (shouldBaseComponentReload) {
this.setState({ load: true });
setTimeout(() => this.setState({ load: false }), 0);
}
}
prevSiteIdRef.current = siteId;
}, [urlSiteId, siteId, props.location.pathname, props.history]);
render() {
return this.state.load ? null : <BaseComponent {...this.props} />;
}
}
const key = props.siteId;
return WrapperClass
}
const passedProps = { ...props, siteId, setSiteId, urlSiteId };
return <BaseComponent key={key} {...passedProps} />;
};
return observer(WrapperComponent);
};
export default withSiteIdUpdater;

View file

@ -8,7 +8,8 @@ import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { useStore, withStore } from 'App/mstore';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { hasSiteId, siteChangeAvailable } from 'App/routes';
import NewSiteForm from 'Components/Client/Sites/NewSiteForm';
import { useModal } from 'Components/Modal';
@ -27,18 +28,19 @@ interface Site {
}
interface Props extends RouteComponentProps {
sites: Site[];
siteId: string;
setSiteId: (siteId: string) => void;
clearSearch: (isSession: boolean) => void;
clearSearchLive: () => void;
initProject: (data: any) => void;
mstore: any;
account: any;
}
function ProjectDropdown(props: Props) {
const { sites, siteId, location, account } = props;
const mstore = useStore();
const { projectsStore } = mstore;
const sites = projectsStore.list;
const siteId = projectsStore.siteId;
const setSiteId = projectsStore.setSiteId;
const initProject = projectsStore.initProject;
const { location, account } = props;
const isAdmin = account.admin || account.superAdmin;
const activeSite = sites.find((s) => s.id === siteId);
const showCurrent =
@ -47,21 +49,20 @@ function ProjectDropdown(props: Props) {
const { customFieldStore } = useStore();
const handleSiteChange = async (newSiteId: string) => {
props.setSiteId(newSiteId); // Fixed: should set the new siteId, not the existing one
setSiteId(newSiteId); // Fixed: should set the new siteId, not the existing one
await customFieldStore.fetchList(newSiteId)
props.clearSearch(location.pathname.includes('/sessions'));
props.clearSearchLive();
props.mstore.initClient();
mstore.initClient();
};
const addProjectClickHandler = () => {
props.initProject({});
initProject({});
showModal(<NewSiteForm onClose={hideModal} />, { right: true });
};
// @ts-ignore immutable
const menuItems = sites.toJS().map((site) => ({
const menuItems = sites.map((site) => ({
key: site.id,
label: (
<div
@ -142,16 +143,12 @@ function ProjectDropdown(props: Props) {
}
const mapStateToProps = (state: any) => ({
sites: state.getIn(['site', 'list']),
siteId: state.getIn(['site', 'siteId']),
account: state.getIn(['user', 'account']),
});
export default withRouter(
connect(mapStateToProps, {
setSiteId,
clearSearch,
clearSearchLive,
initProject,
})(withStore(ProjectDropdown))
})(observer(ProjectDropdown))
);

View file

@ -4,24 +4,19 @@ import SideMenu from 'App/layout/SideMenu';
import TopHeader from 'App/layout/TopHeader';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { init as initSite } from 'Duck/site';
import { connect } from 'react-redux';
const { Sider, Content } = AntLayout;
interface Props {
children: React.ReactNode;
hideHeader?: boolean;
siteId?: string;
initSite: (site: any) => void;
sites: any[];
}
function Layout(props: Props) {
const { hideHeader, siteId } = props;
const { hideHeader } = props;
const isPlayer = /\/(session|assist|view-spot)\//.test(window.location.pathname);
const { settingsStore } = useStore();
const { settingsStore, projectsStore } = useStore();
const siteId = projectsStore.siteId;
return (
<AntLayout style={{ minHeight: '100vh' }}>
@ -29,7 +24,7 @@ function Layout(props: Props) {
<TopHeader />
)}
<AntLayout>
{!hideHeader && !window.location.pathname.includes('/onboarding/') && (
{!hideHeader && !window.location.pathname.includes('/onboarding/') ? (
<Sider
style={{
position: 'sticky',
@ -41,9 +36,9 @@ function Layout(props: Props) {
collapsed={settingsStore.menuCollapsed}
width={250}
>
<SideMenu siteId={siteId} isCollapsed={settingsStore.menuCollapsed} />
<SideMenu siteId={siteId!} isCollapsed={settingsStore.menuCollapsed} />
</Sider>
)}
) : null}
<Content style={{ padding: isPlayer ? '0' : '20px', minHeight: 'calc(100vh - 60px)' }}>
{props.children}
</Content>
@ -52,7 +47,4 @@ function Layout(props: Props) {
);
}
export default connect((state: any) => ({
siteId: state.getIn(['site', 'siteId']),
sites: state.getIn(['site', 'list'])
}), { initSite })(observer(Layout));
export default observer(Layout);

View file

@ -1,28 +1,26 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import Logo from 'App/layout/Logo';
import TopRight from 'App/layout/TopRight';
import { Layout, Space, Tooltip } from 'antd';
import { useStore } from 'App/mstore';
import { Icon } from 'UI';
import { observer, useObserver } from 'mobx-react-lite';
import { observer } from 'mobx-react-lite';
import { INDEXES } from 'App/constants/zindex';
import { connect } from 'react-redux';
import { logout } from 'Duck/user';
import { init as initSite } from 'Duck/site';
const { Header } = Layout;
interface Props {
account: any;
siteId: string;
initSite: (site: any) => void;
}
function TopHeader(props: Props) {
const { settingsStore } = useStore();
const { account, siteId } = props;
const { userStore, notificationStore } = useStore();
const { account } = props;
const { userStore, notificationStore, projectsStore } = useStore();
const siteId = projectsStore.siteId;
const initialDataFetched = userStore.initialDataFetched;
useEffect(() => {
@ -74,12 +72,10 @@ function TopHeader(props: Props) {
const mapStateToProps = (state: any) => ({
account: state.getIn(['user', 'account']),
siteId: state.getIn(['site', 'siteId'])
});
const mapDispatchToProps = {
onLogoutClick: logout,
initSite
};
export default connect(

View file

@ -10,17 +10,17 @@ import UserMenu from 'Components/Header/UserMenu/UserMenu';
import GettingStartedProgress from 'Shared/GettingStarted/GettingStartedProgress';
import ProjectDropdown from 'Shared/ProjectDropdown';
import { getScope } from "../duck/user";
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
interface Props {
account: any;
siteId: any;
sites: any;
spotOnly?: boolean;
}
function TopRight(props: Props) {
const { projectsStore } = useStore();
const { account } = props;
// @ts-ignore
return (
<Space style={{ lineHeight: '0' }}>
{props.spotOnly ? null : (
@ -52,9 +52,7 @@ function mapStateToProps(state: any) {
return {
account: state.getIn(['user', 'account']),
spotOnly: getScope(state) === 1,
siteId: state.getIn(['site', 'siteId']),
sites: state.getIn(['site', 'list']),
};
}
export default connect(mapStateToProps)(TopRight);
export default connect(mapStateToProps)(observer(TopRight));

View file

@ -98,6 +98,7 @@ export class RootStore {
services.forEach((service) => {
service.initClient(client);
});
client.setSiteIdCheck(this.projectsStore.getSiteId)
}
}

View file

@ -1,22 +1,12 @@
import Dashboard from "App/mstore/types/dashboard";
import APIClient from 'App/api_client';
import BaseService from "./BaseService";
import Widget from "App/mstore/types/widget";
export default class DashboardService {
private client: APIClient;
constructor(client?: APIClient) {
this.client = client ? client : new APIClient();
}
initClient(client?: APIClient) {
this.client = client || new APIClient();
}
export default class DashboardService extends BaseService {
/**
* Get all widgets from a dashboard.
* @param dashboardId Required
* @returns
* @returns
*/
getWidgets(dashboardId: string): Promise<any> {
return this.client.get(`/dashboards/${dashboardId}/widgets`)
@ -34,10 +24,10 @@ export default class DashboardService {
.then(response => response.json())
.then(response => response.data || []);
}
/**
* Get a dashboard by dashboardId.
* @param dashboardId
* @param dashboardId
* @returns {Promise<any>}
*/
getDashboard(dashboardId: string): Promise<any> {
@ -66,9 +56,9 @@ export default class DashboardService {
/**
* Add a widget to a dashboard.
* @param dashboard
* @param metricIds
* @returns
* @param dashboard
* @param metricIds
* @returns
*/
addWidget(dashboard: Dashboard, metricIds: any): Promise<any> {
const data = dashboard.toJson()
@ -80,7 +70,7 @@ export default class DashboardService {
/**
* Delete a dashboard.
* @param dashboardId
* @param dashboardId
* @returns {Promise<any>}
*/
deleteDashboard(dashboardId: string): Promise<any> {
@ -89,7 +79,7 @@ export default class DashboardService {
/**
* Create a new Meitrc, if the dashboardId is not provided,
* Create a new Meitrc, if the dashboardId is not provided,
* it will add the metric to the dashboard.
* @param metric Required
* @param dashboardId Optional