openreplay/frontend/app/components/Client/Sites/Sites.tsx
Delirium 35461acaf3
[WIP] Mobile replayer (#1452)
* fix(ui): fix up mobile recordings display

* fix(ui): some messages

* fix(ui): some messages

* fix(player): fix msg generation for ios messages

* feat(player): add generic mmanager interface for ios player impl

* feat(player): mobile player and message manager; touch manager; videoplayer

* feat(player): add iphone shells, add log panel,

* feat(player): detect ios sessions and inject correct player

* feat(player): move screen mapper to utils

* feat(player): events panel for mobile, map shell sizes to device type data,

* feat(player): added network tab to mobile player; unify network message (ios and web)

* feat(player): resize canvas up to phone screen size, fix capitalize util

* feat(player): some general changes to support mobile events and network entries

* feat(player): remove swipes from timeline

* feat(player): more stuff for list walker

* fix(ui): performance tab, mobile project typings and form

* fix(ui):some ui touches for ios replayer shell

* fix(ui): more fixes for ui, new onboarding screen for mobile projects

* feat(ui): mobile overview panel (xray)

* feat(ui): fixes for phone shell and tap events

* fix(tracker): change phone shells and sizes

* fix(tracker): fix border on replay screen

* feat(ui): use crashes from db to show in session

* feat(ui): use event name for xray

* feat(ui): some overall ui fixes

* feat(ui): IOS -> iOS

* feat(ui): change tags to ant d

* fix(ui): fast fix

* fix(ui): fix for capitalizer

* fix(ui): fix for browser display

* fix(ui): fix for note popup

* fix(ui): change exceptions display

* fix(ui): add click rage to ios xray

* fix(ui): some icons and resizing

* fix(ui): fix ios context menu overlay, fix console logs creation for ios

* feat(ui): added icons

* feat(ui): performance warnings

* feat(ui): performance warnings

* feat(ui): different styles

* feat(ui): rm debug true

* feat(ui): fix warnings display

* feat(ui): some styles for animation

* feat(ui): add some animations to warnings

* feat(ui): move perf warnings to performance graph

* feat(ui): hide/show warns dynamically

* feat(ui): new mobile touch animation

* fix(tracker): update msg for ios

* fix(ui): taprage fixes

* fix(ui): regenerate icons and messages

* fix(ui): fix msgs

* fix(backend): fix events gen

* fix(backend): fix userid msg
2023-10-27 12:12:09 +02:00

221 lines
6.6 KiB
TypeScript

import React, { useState } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { Drawer, 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';
import SiteSearch from './SiteSearch';
import AddProjectButton from './AddProjectButton';
import InstallButton from './InstallButton';
import ProjectKey from './ProjectKey';
import { getInitials, sliceListPerPage } from 'App/utils';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import { useModal } from 'App/components/Modal';
import CaptureRate from 'Shared/SessionSettings/components/CaptureRate';
type Project = {
id: number;
name: string;
platform: 'web' | 'mobile';
host: string;
projectKey: string;
sampleRate: number;
};
type PropsFromRedux = ConnectedProps<typeof connector>;
const Sites = ({
loading,
sites,
user,
init
}: PropsFromRedux) => {
const [searchQuery, setSearchQuery] = useState('');
const [showCaptureRate, setShowCaptureRate] = useState(true);
const [activeProject, setActiveProject] = useState<Project | null>(null);
const [page, setPage] = useState(1);
const pageSize: number = 10;
const isAdmin = user.admin || user.superAdmin;
const filteredSites = sites.filter((site: { name: string }) =>
site.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const { showModal, hideModal } = useModal();
const EditButton = ({
isAdmin,
onClick
}: {
isAdmin: boolean;
onClick: () => void;
}) => {
const _onClick = () => {
onClick();
showModal(<NewSiteForm onClose={hideModal} />, { right: true });
};
return (
<Button
icon='edit'
variant='text-primary'
disabled={!isAdmin}
onClick={_onClick}
/>
);
};
const captureRateClickHandler = (project: Project) => {
setActiveProject(project);
setShowCaptureRate(true);
};
const updatePage = (page: number) => {
setPage(page);
};
const ProjectItem = ({ project }: { project: Project }) => (
<div
key={project.id}
className="grid grid-cols-12 gap-2 w-full group hover:bg-active-blue items-center px-5 py-3"
>
<div className="col-span-4">
<div className="flex items-center">
<div className="relative flex items-center justify-center w-10 h-10 rounded-full bg-tealx-light">
<Icon color={'tealx'} size={18} name={project.platform === 'web' ? 'browser/browser' : 'mobile'} />
</div>
<span className="ml-2">{project.host}</span>
<div className={'ml-4 flex items-center gap-2'}>
{project.platform === 'web' ? null : <Tag color="error">iOS BETA</Tag>}
</div>
</div>
</div>
<div className="col-span-3">
<ProjectKey value={project.projectKey} tooltip="Project key copied to clipboard" />
</div>
<div className="col-span-2">
<Button variant="text-primary" onClick={() => captureRateClickHandler(project)}>
{project.sampleRate}%
</Button>
</div>
<div className="col-span-3 justify-self-end flex items-center">
<div className="mr-4">
<InstallButton site={project} />
</div>
<div className="invisible group-hover:visible">
<EditButton isAdmin={isAdmin} onClick={() => init(project)} />
</div>
</div>
</div>
);
return (
<Loader loading={loading}>
<div className="bg-white rounded-lg">
<div className={cn(stl.tabHeader, 'px-5 pt-5')}>
<PageTitle
title={<div className='mr-4'>Projects</div>}
actionButton={
<TextLink
icon='book'
href='https://docs.openreplay.com/installation'
label='Installation Docs'
/>
}
/>
<div className='flex ml-auto items-center'>
<AddProjectButton isAdmin={isAdmin} />
<div className='mx-2' />
<SiteSearch onChange={(value) => setSearchQuery(value)} />
</div>
</div>
<div className={stl.list}>
<NoContent
title={
<div className='flex flex-col items-center justify-center'>
<AnimatedSVG name={ICONS.NO_PROJECTS} size={170} />
<div className='text-center text-gray-600 my-4'>
No matching results
</div>
</div>
}
size='small'
show={!loading && filteredSites.size === 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>
<div className='col-span-3'>Key</div>
<div className='col-span-2'>Capture Rate</div>
<div className='col-span-3'></div>
</div>
<Divider className='m-0' />
{sliceListPerPage(filteredSites, page - 1, pageSize).map(
(project: Project) => (
<>
<ProjectItem project={project} />
<Divider className='m-0' />
</>
)
)}
<div className='w-full flex items-center justify-center py-10'>
<Pagination
page={page}
totalPages={Math.ceil(filteredSites.size / pageSize)}
onPageChange={(page) => updatePage(page)}
limit={pageSize}
/>
</div>
</NoContent>
</div>
</div>
<Drawer
open={showCaptureRate && !!activeProject}
onClose={() => setShowCaptureRate(!showCaptureRate)}
title='Capture Rate'
closable={false}
destroyOnClose
>
{activeProject && <CaptureRate projectId={activeProject.id} />}
</Drawer>
</Loader>
);
};
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
});
export default connector(withPageTitle('Projects - OpenReplay Preferences')(Sites));