diff --git a/frontend/app/components/Client/Projects/ProjectCaptureRate.tsx b/frontend/app/components/Client/Projects/ProjectCaptureRate.tsx new file mode 100644 index 000000000..d9450ba60 --- /dev/null +++ b/frontend/app/components/Client/Projects/ProjectCaptureRate.tsx @@ -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([]); + 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 ( + + +
+ + Define percentage of sessions you want to capture + + + + + + + + + {!conditionalCapture ? ( +
+ ) => { + 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} + /> + +
+ ) : null} + + +
+
+ {conditionalCapture && isEnterprise ? ( + + ) : null} +
+
+ ); +} + +export default observer(ProjectCaptureRate); diff --git a/frontend/app/components/Client/Projects/ProjectForm.tsx b/frontend/app/components/Client/Projects/ProjectForm.tsx index 42c85bed5..2c940728d 100644 --- a/frontend/app/components/Client/Projects/ProjectForm.tsx +++ b/frontend/app/components/Client/Projects/ProjectForm.tsx @@ -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 ( -
null}> -
- - - + + + + + +
+ { + projectsStore.editInstance({ platform: value }); + }} /> - - - -
- { - projectsStore.editInstance({ platform: value }); - }} - /> -
-
-
- - {project.exists() && ( - - )}
+ +
+ + {project.exists() && ( + + )}
); diff --git a/frontend/app/components/Client/Projects/ProjectList.tsx b/frontend/app/components/Client/Projects/ProjectList.tsx index 3832ecfdb..5de3166f1 100644 --- a/frontend/app/components/Client/Projects/ProjectList.tsx +++ b/frontend/app/components/Client/Projects/ProjectList.tsx @@ -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 ( -
- setSearch('')} - allowClear - /> - item.name.toLowerCase().includes(search.toLowerCase()))} - renderItem={(item: Project) => ( - onProjectClick(item)} - className={`!py-2 mb-2 rounded-lg cursor-pointer !border-b-0 ${config.project?.projectId === item.projectId ? 'bg-teal-light' : 'bg-white'}`} - > - : - } - /> - } - title={{item.name}} - /> - - )} +
+
+ setSearch('')} + allowClear + // className="m-4" + /> +
+ item.name.toLowerCase().includes(search.toLowerCase())).map((project) => ({ + key: project.id, + label: project.name, + onClick: () => onProjectClick(project), + icon: : + } /> + })) as any} />
); diff --git a/frontend/app/components/Client/Projects/ProjectTabContent.tsx b/frontend/app/components/Client/Projects/ProjectTabContent.tsx index 34588a879..ead60f76f 100644 --- a/frontend/app/components/Client/Projects/ProjectTabContent.tsx +++ b/frontend/app/components/Client/Projects/ProjectTabContent.tsx @@ -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 = React.useMemo(() => { - const project = projectsStore.list.find((p) => p.projectId == pid); - return { - installation: , - captureRate:
Capture Rate Content
, + const project = React.useMemo( + () => projectsStore.list.find((p) => p.projectId === pid), + [pid, projectsStore.list] + ); + + if (!project) { + return ; + } + + const tabContent: Record = React.useMemo( + () => ({ + installation: , + captureRate: , metadata: , - tags: , - groupKeys:
Group Keys Content
- }; - }, [pid, projectsStore.list]); + tags: + }), + [project] + ); return (
- {tabContent[tab]} + {tabContent[tab] || }
); -} +}; export default observer(ProjectTabContent); diff --git a/frontend/app/components/Client/Projects/ProjectTabs.tsx b/frontend/app/components/Client/Projects/ProjectTabs.tsx index f84e46a98..802b581d4 100644 --- a/frontend/app/components/Client/Projects/ProjectTabs.tsx +++ b/frontend/app/components/Client/Projects/ProjectTabs.tsx @@ -16,7 +16,7 @@ function ProjectTabs() { { key: 'captureRate', label: 'Capture Rate', content:
Capture Rate Content
}, { key: 'metadata', label: 'Metadata', content:
Metadata Content
}, { key: 'tags', label: 'Tags', content:
Tags Content
}, - { key: 'groupKeys', label: 'Group Keys', content:
Group Keys Content
} + // { key: 'groupKeys', label: 'Group Keys', content:
Group Keys Content
} ]; const onTabChange = (key: string) => { diff --git a/frontend/app/components/Client/Projects/Projects.tsx b/frontend/app/components/Client/Projects/Projects.tsx index 50f371409..6f1ba03f3 100644 --- a/frontend/app/components/Client/Projects/Projects.tsx +++ b/frontend/app/components/Client/Projects/Projects.tsx @@ -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 ( - - - } + title="Projects" + extra={[ + + ]} > - - -
- -
- - - - {project?.name} + + + + + + + +
+ {project?.name} + +
-
- -
+ + {project && } -
- -
+ + +
); } 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 ( +