change(ui): projects revamp - capture rate tab
This commit is contained in:
parent
9374c98669
commit
1e7948bcf7
10 changed files with 357 additions and 178 deletions
142
frontend/app/components/Client/Projects/ProjectCaptureRate.tsx
Normal file
142
frontend/app/components/Client/Projects/ProjectCaptureRate.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Space, Switch, Tooltip, Input, Typography } from 'antd';
|
||||
import { Icon, Loader } from 'UI';
|
||||
import cn from 'classnames';
|
||||
import ConditionalRecordingSettings from 'Shared/SessionSettings/components/ConditionalRecordingSettings';
|
||||
import { Conditions } from '@/mstore/types/FeatureFlag';
|
||||
import { useStore } from '@/mstore';
|
||||
import Project from '@/mstore/types/project';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
function ProjectCaptureRate(props: Props) {
|
||||
const [conditions, setConditions] = React.useState<Conditions[]>([]);
|
||||
const { projectId, platform } = props.project;
|
||||
const isMobile = platform !== 'web';
|
||||
const { settingsStore, userStore } = useStore();
|
||||
const isAdmin = userStore.account.admin || userStore.account.superAdmin;
|
||||
const isEnterprise = userStore.isEnterprise;
|
||||
const [changed, setChanged] = useState(false);
|
||||
const {
|
||||
sessionSettings: {
|
||||
captureRate,
|
||||
changeCaptureRate,
|
||||
conditionalCapture,
|
||||
changeConditionalCapture,
|
||||
captureConditions
|
||||
},
|
||||
loadingCaptureRate,
|
||||
updateCaptureConditions,
|
||||
fetchCaptureConditions
|
||||
} = settingsStore;
|
||||
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
void fetchCaptureConditions(projectId);
|
||||
}
|
||||
}, [projectId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setConditions(captureConditions.map((condition: any) => new Conditions(condition, true, isMobile)));
|
||||
}, [captureConditions]);
|
||||
|
||||
const onCaptureRateChange = (input: string) => {
|
||||
setChanged(true);
|
||||
changeCaptureRate(input);
|
||||
};
|
||||
|
||||
const toggleRate = () => {
|
||||
setChanged(true);
|
||||
const newValue = !conditionalCapture;
|
||||
changeConditionalCapture(newValue);
|
||||
if (newValue) {
|
||||
changeCaptureRate('100');
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = () => {
|
||||
updateCaptureConditions(projectId!, {
|
||||
rate: parseInt(captureRate, 10),
|
||||
conditionalCapture: conditionalCapture,
|
||||
conditions: isEnterprise ? conditions.map((c) => c.toCaptureCondition()) : []
|
||||
});
|
||||
setChanged(false);
|
||||
};
|
||||
|
||||
const updateDisabled = !changed || !isAdmin || (isEnterprise && (conditionalCapture && conditions.length === 0));
|
||||
|
||||
return (
|
||||
<Loader loading={loadingCaptureRate || !projectId}>
|
||||
<Tooltip title={isAdmin ? '' : 'You don\'t have permission to change.'}>
|
||||
<div className="flex flex-col gap-4 border-b pb-4">
|
||||
<Space>
|
||||
<Typography.Text>Define percentage of sessions you want to capture</Typography.Text>
|
||||
<Tooltip
|
||||
title={
|
||||
'Define the percentage of user sessions to be recorded for detailed replay and analysis.' +
|
||||
'\nSessions exceeding this specified limit will not be captured or stored.'
|
||||
}
|
||||
>
|
||||
<Icon size={16} color={'black'} name={'info-circle'} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
||||
<Space className="flex items-center gap-6 h-6">
|
||||
<Switch
|
||||
checked={conditionalCapture}
|
||||
onChange={toggleRate}
|
||||
checkedChildren={!isEnterprise ? '100%' : 'Conditional'}
|
||||
disabled={!isAdmin}
|
||||
unCheckedChildren={!isEnterprise ? 'Custom' : 'Capture Rate'}
|
||||
/>
|
||||
|
||||
{!conditionalCapture ? (
|
||||
<div className={cn('relative', { disabled: !isAdmin })}>
|
||||
<Input
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (/^\d+$/.test(e.target.value) || e.target.value === '') {
|
||||
onCaptureRateChange(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={captureRate.toString()}
|
||||
style={{ height: '26px', width: '70px' }}
|
||||
disabled={conditionalCapture}
|
||||
min={0}
|
||||
max={100}
|
||||
/>
|
||||
<Icon
|
||||
className="absolute right-0 mr-2 top-0 bottom-0 m-auto"
|
||||
name="percent"
|
||||
color="gray-medium"
|
||||
size="18"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={onUpdate}
|
||||
disabled={updateDisabled}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
{conditionalCapture && isEnterprise ? (
|
||||
<ConditionalRecordingSettings
|
||||
setChanged={setChanged}
|
||||
conditions={conditions}
|
||||
setConditions={setConditions}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(ProjectCaptureRate);
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { ChangeEvent, FormEvent, useEffect } from 'react';
|
||||
import styles from 'Components/Client/Sites/siteForm.module.css';
|
||||
import { Icon } from 'UI';
|
||||
import Project from '@/mstore/types/project';
|
||||
import { projectStore, useStore } from '@/mstore';
|
||||
|
|
@ -13,12 +12,14 @@ interface Props {
|
|||
}
|
||||
|
||||
function ProjectForm(props: Props) {
|
||||
const [form] = Form.useForm();
|
||||
const { onClose } = props;
|
||||
const { projectsStore } = useStore();
|
||||
const project = projectsStore.instance as Project;
|
||||
const loading = projectsStore.loading;
|
||||
const canDelete = projectsStore.list.length > 1;
|
||||
const pathname = window.location.pathname;
|
||||
const mstore = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (props.project && props.project.id) {
|
||||
|
|
@ -33,7 +34,6 @@ function ProjectForm(props: Props) {
|
|||
};
|
||||
|
||||
const onSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!projectsStore.instance) return;
|
||||
if (projectsStore.instance.id && projectsStore.instance.exists()) {
|
||||
projectsStore
|
||||
|
|
@ -52,19 +52,19 @@ function ProjectForm(props: Props) {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
projectsStore.save(projectsStore.instance!).then((response: any) => {
|
||||
if (!response || !response.errors || response.errors.size === 0) {
|
||||
if (onClose) {
|
||||
onClose(null);
|
||||
}
|
||||
// searchStore.clearSearch();
|
||||
// mstore.searchStoreLive.clearSearch();
|
||||
// mstore.initClient();
|
||||
toast.success('Project added successfully');
|
||||
} else {
|
||||
toast.error(response.errors[0]);
|
||||
}
|
||||
});
|
||||
projectsStore
|
||||
.save(projectsStore.instance!)
|
||||
.then(() => {
|
||||
toast.success('Project created successfully');
|
||||
onClose?.(null);
|
||||
|
||||
mstore.searchStore.clearSearch();
|
||||
mstore.searchStoreLive.clearSearch();
|
||||
mstore.initClient();
|
||||
})
|
||||
.catch((error: string) => {
|
||||
toast.error(error || 'An error occurred while creating the project');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -86,60 +86,65 @@ function ProjectForm(props: Props) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Form onFinish={project.validate ? onSubmit : () => null}>
|
||||
<div className={styles.content}>
|
||||
<Form.Item>
|
||||
<label>{'Name'}</label>
|
||||
<Input
|
||||
placeholder="Ex. OpenReplay"
|
||||
name="name"
|
||||
maxLength={40}
|
||||
value={project.name}
|
||||
onChange={handleEdit}
|
||||
className={styles.input}
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
requiredMark={false}
|
||||
onFinish={onSubmit}
|
||||
>
|
||||
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name="name"
|
||||
rules={[{ required: true, message: 'Please enter a name' }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="Ex. OpenReplay"
|
||||
name="name"
|
||||
maxLength={40}
|
||||
value={project.name}
|
||||
onChange={handleEdit}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="Project Type">
|
||||
<div>
|
||||
<Segmented
|
||||
options={[
|
||||
{
|
||||
value: 'web',
|
||||
label: 'Web'
|
||||
},
|
||||
{
|
||||
value: 'ios',
|
||||
label: 'Mobile'
|
||||
}
|
||||
]}
|
||||
value={project.platform}
|
||||
onChange={(value) => {
|
||||
projectsStore.editInstance({ platform: value });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<label>Project Type</label>
|
||||
<div>
|
||||
<Segmented
|
||||
options={[
|
||||
{
|
||||
value: 'web',
|
||||
label: 'Web'
|
||||
},
|
||||
{
|
||||
value: 'ios',
|
||||
label: 'Mobile'
|
||||
}
|
||||
]}
|
||||
value={project.platform}
|
||||
onChange={(value) => {
|
||||
projectsStore.editInstance({ platform: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<div className="mt-6 flex justify-between">
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
className="float-left mr-2"
|
||||
loading={loading}
|
||||
disabled={!project.validate}
|
||||
>
|
||||
{project.exists() ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
{project.exists() && (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={handleRemove}
|
||||
disabled={!canDelete}
|
||||
>
|
||||
<Icon name="trash" size="16" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
<div className="mt-6 flex justify-between">
|
||||
<Button
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
className="float-left mr-2"
|
||||
loading={loading}
|
||||
// disabled={!project.validate}
|
||||
>
|
||||
{project.exists() ? 'Update' : 'Add'}
|
||||
</Button>
|
||||
{project.exists() && (
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={handleRemove}
|
||||
disabled={!canDelete}
|
||||
>
|
||||
<Icon name="trash" size="16" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Avatar, Input, List, Typography } from 'antd';
|
||||
import { Avatar, Input, Menu, List, Typography } from 'antd';
|
||||
import { useStore } from '@/mstore';
|
||||
import Project from '@/mstore/types/project';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
|
@ -20,34 +20,28 @@ function ProjectList() {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input.Search
|
||||
placeholder="Search"
|
||||
onSearch={onSearch}
|
||||
onClear={() => setSearch('')}
|
||||
allowClear
|
||||
/>
|
||||
<List
|
||||
dataSource={list.filter((item) => item.name.toLowerCase().includes(search.toLowerCase()))}
|
||||
renderItem={(item: Project) => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
onClick={() => onProjectClick(item)}
|
||||
className={`!py-2 mb-2 rounded-lg cursor-pointer !border-b-0 ${config.project?.projectId === item.projectId ? 'bg-teal-light' : 'bg-white'}`}
|
||||
>
|
||||
<List.Item.Meta
|
||||
className="flex !items-center px-2 overflow-hidden"
|
||||
avatar={
|
||||
<Avatar
|
||||
className="bg-tealx-light"
|
||||
icon={item.platform === 'web' ? <AppWindowMac size={18} color="teal" /> :
|
||||
<Smartphone size={18} color="teal" />}
|
||||
/>
|
||||
}
|
||||
title={<Typography.Text className="capitalize truncate text-ellipsis">{item.name}</Typography.Text>}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
<div className="h-full flex flex-col gap-4">
|
||||
<div className="p-4">
|
||||
<Input.Search
|
||||
placeholder="Search"
|
||||
onSearch={onSearch}
|
||||
onClear={() => setSearch('')}
|
||||
allowClear
|
||||
// className="m-4"
|
||||
/>
|
||||
</div>
|
||||
<Menu
|
||||
mode="inline"
|
||||
selectedKeys={[config.pid + '']}
|
||||
className="h-full w-full ml-0 pl-0 !bg-white !border-r-0"
|
||||
items={list.filter((item: Project) => item.name.toLowerCase().includes(search.toLowerCase())).map((project) => ({
|
||||
key: project.id,
|
||||
label: project.name,
|
||||
onClick: () => onProjectClick(project),
|
||||
icon: <Avatar className="bg-tealx-light"
|
||||
icon={project.platform === 'web' ? <AppWindowMac size={18} color="teal" /> :
|
||||
<Smartphone size={18} color="teal" />} />
|
||||
})) as any}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,27 +4,37 @@ import { observer } from 'mobx-react-lite';
|
|||
import ProjectTabTracking from 'Components/Client/Projects/ProjectTabTracking';
|
||||
import CustomFields from 'Components/Client/CustomFields';
|
||||
import ProjectTags from 'Components/Client/Projects/ProjectTags';
|
||||
import ProjectCaptureRate from 'Components/Client/Projects/ProjectCaptureRate';
|
||||
import { Empty } from 'antd';
|
||||
|
||||
function ProjectTabContent() {
|
||||
const ProjectTabContent: React.FC = () => {
|
||||
const { projectsStore } = useStore();
|
||||
const { pid, tab } = projectsStore.config;
|
||||
|
||||
const tabContent: Record<string, React.ReactNode> = React.useMemo(() => {
|
||||
const project = projectsStore.list.find((p) => p.projectId == pid);
|
||||
return {
|
||||
installation: <ProjectTabTracking project={project!} />,
|
||||
captureRate: <div>Capture Rate Content</div>,
|
||||
const project = React.useMemo(
|
||||
() => projectsStore.list.find((p) => p.projectId === pid),
|
||||
[pid, projectsStore.list]
|
||||
);
|
||||
|
||||
if (!project) {
|
||||
return <Empty description="Project not found" />;
|
||||
}
|
||||
|
||||
const tabContent: Record<string, React.ReactNode> = React.useMemo(
|
||||
() => ({
|
||||
installation: <ProjectTabTracking project={project} />,
|
||||
captureRate: <ProjectCaptureRate project={project} />,
|
||||
metadata: <CustomFields />,
|
||||
tags: <ProjectTags />,
|
||||
groupKeys: <div>Group Keys Content</div>
|
||||
};
|
||||
}, [pid, projectsStore.list]);
|
||||
tags: <ProjectTags />
|
||||
}),
|
||||
[project]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tabContent[tab]}
|
||||
{tabContent[tab] || <Empty description="Tab not found" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default observer(ProjectTabContent);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function ProjectTabs() {
|
|||
{ key: 'captureRate', label: 'Capture Rate', content: <div>Capture Rate Content</div> },
|
||||
{ key: 'metadata', label: 'Metadata', content: <div>Metadata Content</div> },
|
||||
{ key: 'tags', label: 'Tags', content: <div>Tags Content</div> },
|
||||
{ key: 'groupKeys', label: 'Group Keys', content: <div>Group Keys Content</div> }
|
||||
// { key: 'groupKeys', label: 'Group Keys', content: <div>Group Keys Content</div> }
|
||||
];
|
||||
|
||||
const onTabChange = (key: string) => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React from 'react';
|
||||
import { Button, Card, Col, Divider, Row, Space, Typography } from 'antd';
|
||||
import { App, Button, Card, Layout, Space, Typography } from 'antd';
|
||||
import ProjectList from 'Components/Client/Projects/ProjectList';
|
||||
import ProjectTabs from 'Components/Client/Projects/ProjectTabs';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useStore } from '@/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
import { KeyIcon, PlusIcon } from 'lucide-react';
|
||||
import ProjectTabContent from 'Components/Client/Projects/ProjectTabContent';
|
||||
import { useModal } from 'Components/ModalContext';
|
||||
import ProjectForm from 'Components/Client/Projects/ProjectForm';
|
||||
|
|
@ -45,39 +45,65 @@ function Projects() {
|
|||
|
||||
return (
|
||||
<Card
|
||||
title="Projects"
|
||||
style={{ height: 'calc(100vh - 130px)' }}
|
||||
classNames={{
|
||||
cover: '!rounded-lg border shadow-sm',
|
||||
body: '!p-0'
|
||||
header: '!border-b',
|
||||
body: '!p-0 !border-t'
|
||||
}}
|
||||
style={{ height: 'calc(100vh - 140px)' }}
|
||||
extra={
|
||||
<Space>
|
||||
<Button onClick={createProject} icon={<PlusIcon />}>
|
||||
Create Project
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
title="Projects"
|
||||
extra={[
|
||||
<Button key="1" onClick={createProject} icon={<PlusIcon />}>
|
||||
Create Project
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
<Row className="h-full">
|
||||
<Col span={6} className="border-r !p-4 flex flex-col">
|
||||
<div className="flex-1 !overflow-y-auto">
|
||||
<ProjectList />
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={18} className="!p-4 flex flex-col">
|
||||
<Space className="flex justify-between">
|
||||
<Typography.Title level={5} className="capitalize !m-0">{project?.name}</Typography.Title>
|
||||
<Layout>
|
||||
<Layout.Sider width={300} trigger={null} className="!bg-white border-r">
|
||||
<ProjectList />
|
||||
</Layout.Sider>
|
||||
|
||||
<Layout>
|
||||
<Layout.Header className="flex justify-between items-center p-4 !bg-white border-b"
|
||||
style={{ height: 46 }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Typography.Title level={5}
|
||||
className="capitalize !m-0 whitespace-nowrap truncate">{project?.name}</Typography.Title>
|
||||
<ProjectKeyButton />
|
||||
</div>
|
||||
<ProjectTabs />
|
||||
</Space>
|
||||
<Divider style={{ margin: '0px' }} />
|
||||
<div className="flex-1 !overflow-y-auto my-4">
|
||||
</Layout.Header>
|
||||
<Layout.Content
|
||||
style={{
|
||||
padding: 24,
|
||||
height: 'calc(100vh - 260px)'
|
||||
}}
|
||||
className="bg-white overflow-y-auto"
|
||||
>
|
||||
{project && <ProjectTabContent />}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(Projects);
|
||||
|
||||
function ProjectKeyButton() {
|
||||
const { projectsStore } = useStore();
|
||||
const { project } = projectsStore.config;
|
||||
const { message } = App.useApp();
|
||||
|
||||
const copyKey = () => {
|
||||
if (!project || !project.projectKey) {
|
||||
void message.error('Project key not found');
|
||||
return;
|
||||
}
|
||||
void navigator.clipboard.writeText(project?.projectKey || '');
|
||||
void message.success('Project key copied to clipboard');
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={copyKey} icon={<KeyIcon size={14} />} size="small" />
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
function CaptureRateSettings() {
|
||||
return (
|
||||
<div></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CaptureRateSettings;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import './styles/index.css';
|
||||
import './styles/global.css'
|
||||
import './styles/global.css';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './init';
|
||||
|
|
@ -7,19 +7,19 @@ import Router from './Router';
|
|||
import { StoreProvider, RootStore } from './mstore';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndProvider } from 'react-dnd';
|
||||
import { ConfigProvider, theme, ThemeConfig } from 'antd';
|
||||
import { ConfigProvider, App, theme, ThemeConfig } from 'antd';
|
||||
import colors from 'App/theme/colors';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Notification, MountPoint } from 'UI';
|
||||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from '@tanstack/react-query'
|
||||
QueryClientProvider
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
// @ts-ignore
|
||||
window.getCommitHash = () => console.log(window.env.COMMIT_HASH);
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
const queryClient = new QueryClient();
|
||||
const customTheme: ThemeConfig = {
|
||||
// algorithm: theme.compactAlgorithm,
|
||||
components: {
|
||||
|
|
@ -29,7 +29,7 @@ const customTheme: ThemeConfig = {
|
|||
},
|
||||
Segmented: {
|
||||
itemSelectedBg: '#FFFFFF',
|
||||
itemSelectedColor: colors['main'],
|
||||
itemSelectedColor: colors['main']
|
||||
},
|
||||
Menu: {
|
||||
colorPrimary: colors.teal,
|
||||
|
|
@ -48,7 +48,7 @@ const customTheme: ThemeConfig = {
|
|||
itemMarginBlock: 0,
|
||||
itemPaddingInline: 50,
|
||||
iconMarginInlineEnd: 14,
|
||||
collapsedWidth: 180,
|
||||
collapsedWidth: 180
|
||||
},
|
||||
Button: {
|
||||
colorPrimary: colors.teal
|
||||
|
|
@ -73,21 +73,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
// @ts-ignore
|
||||
const root = createRoot(container);
|
||||
|
||||
|
||||
// const theme = window.localStorage.getItem('theme');
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ConfigProvider theme={customTheme}>
|
||||
<StoreProvider store={new RootStore()}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<BrowserRouter>
|
||||
<Notification />
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
</DndProvider>
|
||||
<MountPoint />
|
||||
</StoreProvider>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider theme={customTheme}>
|
||||
<App>
|
||||
<StoreProvider store={new RootStore()}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<BrowserRouter>
|
||||
<Notification />
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
</DndProvider>
|
||||
<MountPoint />
|
||||
</StoreProvider>
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Project from './types/project';
|
|||
import GDPR from './types/gdpr';
|
||||
import { GLOBAL_HAS_NO_RECORDINGS, SITE_ID_STORAGE_KEY } from 'App/constants/storageKeys';
|
||||
import { projectsService } from 'App/services';
|
||||
import { toast } from '.store/react-toastify-virtual-9dd0f3eae1/package';
|
||||
|
||||
interface Config {
|
||||
project: Project | null;
|
||||
|
|
@ -143,8 +144,9 @@ export default class ProjectsStore {
|
|||
this.setSiteId(newSite.id);
|
||||
this.active = newSite;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save site:', error);
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
throw error || 'An error occurred while saving the project.';
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,47 @@
|
|||
import BaseService from "./BaseService";
|
||||
import BaseService from './BaseService';
|
||||
|
||||
export default class ProjectsService extends BaseService {
|
||||
fetchGDPR = async (siteId: string) => {
|
||||
const r = await this.client.get(`/${siteId}/gdpr`);
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
};
|
||||
|
||||
saveGDPR = async (siteId: string, gdprData: any) => {
|
||||
const r = await this.client.post(`/${siteId}/gdpr`, gdprData);
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
};
|
||||
|
||||
fetchList = async () => {
|
||||
const r = await this.client.get('/projects');
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
};
|
||||
|
||||
saveProject = async (projectData: any) => {
|
||||
const r = await this.client.post('/projects', projectData);
|
||||
saveProject = async (projectData: any): Promise<any> => {
|
||||
try {
|
||||
const response = await this.client.post('/projects', projectData);
|
||||
return response.json();
|
||||
} catch (error: any) {
|
||||
if (error.response) {
|
||||
const errorData = await error.response.json();
|
||||
throw errorData.errors?.[0] || 'An error occurred while saving the project.';
|
||||
}
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
throw 'An unexpected error occurred.';
|
||||
}
|
||||
};
|
||||
|
||||
removeProject = async (projectId: string) => {
|
||||
const r = await this.client.delete(`/projects/${projectId}`)
|
||||
const r = await this.client.delete(`/projects/${projectId}`);
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
};
|
||||
|
||||
updateProject = async (projectId: string, projectData: any) => {
|
||||
const r = await this.client.put(`/projects/${projectId}`, projectData);
|
||||
|
||||
return await r.json();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue