change(ui): split stuff into components, add store
This commit is contained in:
parent
2f0b5359b7
commit
61829d8260
15 changed files with 184 additions and 96 deletions
|
|
@ -1,17 +1,19 @@
|
|||
import React from 'react';
|
||||
import PlayLink from 'Shared/SessionItem/PlayLink';
|
||||
import { connect } from 'react-redux';
|
||||
import { countries } from 'App/constants';
|
||||
import { session as sessionRoute } from 'App/routes';
|
||||
import Session from './components/Session'
|
||||
import MetaInfo from './components/MetaInfo'
|
||||
import Title from './components/Title'
|
||||
|
||||
interface Props {
|
||||
hideModal: () => void;
|
||||
session: Record<string, any>;
|
||||
account: Record<string, any>;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function BugReportModal({ hideModal, session, width, height }: Props) {
|
||||
function BugReportModal({ hideModal, session, width, height, account }: Props) {
|
||||
const {
|
||||
userBrowser,
|
||||
userDevice,
|
||||
|
|
@ -26,7 +28,8 @@ function BugReportModal({ hideModal, session, width, height }: Props) {
|
|||
sessionId,
|
||||
} = session;
|
||||
|
||||
console.log(session.toJS())
|
||||
console.log(session.toJS(), account.toJS?.())
|
||||
|
||||
const envObject = {
|
||||
Device: `${userDevice}${userDeviceType !== userDevice ? ` ${userDeviceType}` : ''}`,
|
||||
Resolution: `${width}x${height}`,
|
||||
|
|
@ -43,88 +46,15 @@ function BugReportModal({ hideModal, session, width, height }: Props) {
|
|||
className="flex flex-col p-4 gap-4 bg-white"
|
||||
style={{ maxWidth: '70vw', width: 620, height: '100vh' }}
|
||||
>
|
||||
<Title />
|
||||
<Title userName={account.name} />
|
||||
<MetaInfo envObject={envObject} metadata={metadata} />
|
||||
<Session user={userDisplayName} sessionId={sessionId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Title() {
|
||||
return (
|
||||
<div className="flex items-center py-2 px-3 justify-between bg-gray-lightest rounded">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>Title</div>
|
||||
<div className="text-gray-medium">By author</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Severity</div>
|
||||
<div>select here</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface EnvObj {
|
||||
Device: string;
|
||||
Resolution: string;
|
||||
Browser: string;
|
||||
OS: string;
|
||||
Country: string;
|
||||
Rev?: string;
|
||||
}
|
||||
|
||||
function MetaInfo({ envObject, metadata }: { envObject: EnvObj, metadata: Record<string, any> }) {
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Environment</SectionTitle>
|
||||
{Object.keys(envObject).map((envTag) => (
|
||||
<div className="flex items-center">
|
||||
<div className="py-1 px-2">{envTag}</div>
|
||||
<div className="py-1 px-2 text-gray-medium bg-light-blue-bg rounded">
|
||||
{/* @ts-ignore */}
|
||||
{envObject[envTag]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Metadata</SectionTitle>
|
||||
{Object.keys(metadata).map((meta) => (
|
||||
<div className="flex items-center rounded overflow-hidden bg-gray-lightest">
|
||||
<div className="bg-gray-light-shade py-1 px-2">{meta}</div>
|
||||
<div className="py-1 px-2 text-gray-medium">{metadata[meta]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Session({ user, sessionId }: { user: string, sessionId: string }) {
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Session recording</SectionTitle>
|
||||
<div className="border rounded flex items-center justify-between p-2">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-lg">{user}</div>
|
||||
<div className="text-disabled-text">
|
||||
{`${window.location.origin}/${window.location.pathname.split('/')[1]}${sessionRoute(sessionId)}`}
|
||||
</div>
|
||||
</div>
|
||||
<PlayLink isAssist={false} viewed={false} sessionId={sessionId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
return <div className="text-xl font-semibold mb-2">{children}</div>;
|
||||
}
|
||||
|
||||
const WithUIState = connect((state) => ({ session: state.getIn(['sessions', 'current']) }))(
|
||||
// @ts-ignore
|
||||
const WithUIState = connect((state) => ({ session: state.getIn(['sessions', 'current']), account: state.getIn(['user', 'account']), }))(
|
||||
BugReportModal
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import React from 'react'
|
||||
import SectionTitle from './SectionTitle'
|
||||
|
||||
interface EnvObj {
|
||||
Device: string;
|
||||
Resolution: string;
|
||||
Browser: string;
|
||||
OS: string;
|
||||
Country: string;
|
||||
Rev?: string;
|
||||
}
|
||||
|
||||
export default function MetaInfo({ envObject, metadata }: { envObject: EnvObj, metadata: Record<string, any> }) {
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Environment</SectionTitle>
|
||||
{Object.keys(envObject).map((envTag) => (
|
||||
<div className="flex items-center">
|
||||
<div className="py-1 px-2">{envTag}</div>
|
||||
<div className="py-1 px-2 text-gray-medium bg-light-blue-bg rounded">
|
||||
{/* @ts-ignore */}
|
||||
{envObject[envTag]}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<SectionTitle>Metadata</SectionTitle>
|
||||
{Object.keys(metadata).map((meta) => (
|
||||
<div className="flex items-center rounded overflow-hidden bg-gray-lightest">
|
||||
<div className="bg-gray-light-shade py-1 px-2">{meta}</div>
|
||||
<div className="py-1 px-2 text-gray-medium">{metadata[meta]}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import React from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
|
||||
function ReportTitle() {
|
||||
const { bugReportStore } = useStore();
|
||||
const inputRef = React.createRef<HTMLInputElement>();
|
||||
|
||||
const toggleEdit = () => {
|
||||
bugReportStore.toggleTitleEdit(true);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (inputRef.current && bugReportStore.isTitleEdit) {
|
||||
inputRef.current?.focus();
|
||||
}
|
||||
}, [bugReportStore.isTitleEdit])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{bugReportStore.isTitleEdit ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
name="name"
|
||||
className="rounded fluid border-0 -mx-2 px-2 h-8 text-2xl"
|
||||
value={bugReportStore.reportTitle}
|
||||
onChange={(e) => bugReportStore.setTitle(e.target.value)}
|
||||
onBlur={() => bugReportStore.toggleTitleEdit(false)}
|
||||
onFocus={() => bugReportStore.toggleTitleEdit(true)}
|
||||
/>
|
||||
) : (
|
||||
// @ts-ignore
|
||||
<Tooltip delay={100} arrow title="Double click to rename">
|
||||
<div
|
||||
onDoubleClick={toggleEdit}
|
||||
className={cn(
|
||||
'text-blue text-2xl h-8 flex items-center border-transparent',
|
||||
'cursor-pointer select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium'
|
||||
)}
|
||||
>
|
||||
{bugReportStore.reportTitle}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(ReportTitle);
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import React from 'react'
|
||||
|
||||
export default function SectionTitle({ children }: { children: React.ReactNode }) {
|
||||
return <div className="text-xl font-semibold mb-2">{children}</div>;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react'
|
||||
import SectionTitle from './SectionTitle';
|
||||
import { session as sessionRoute } from 'App/routes';
|
||||
import PlayLink from 'Shared/SessionItem/PlayLink';
|
||||
|
||||
export default function Session({ user, sessionId }: { user: string, sessionId: string }) {
|
||||
return (
|
||||
<div>
|
||||
<SectionTitle>Session recording</SectionTitle>
|
||||
<div className="border rounded flex items-center justify-between p-2">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-lg">{user}</div>
|
||||
<div className="text-disabled-text">
|
||||
{`${window.location.origin}/${window.location.pathname.split('/')[1]}${sessionRoute(sessionId)}`}
|
||||
</div>
|
||||
</div>
|
||||
<PlayLink isAssist={false} viewed={false} sessionId={sessionId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react'
|
||||
import ReportTitle from './ReportTitle';
|
||||
|
||||
export default function Title({ userName }: { userName: string }) {
|
||||
return (
|
||||
<div className="flex items-center py-2 px-3 justify-between bg-gray-lightest rounded">
|
||||
<div className="flex flex-col gap-2">
|
||||
<ReportTitle />
|
||||
<div className="text-gray-medium">By {userName}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Severity</div>
|
||||
<div>select here</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -69,7 +69,7 @@ class Issues extends React.Component {
|
|||
const provider = issuesIntegration.provider;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="relative h-full w-full p-3">
|
||||
<div className={stl.buttonWrapper}>
|
||||
<OutsideClickDetectingDiv onClickOutside={this.closeModal}>
|
||||
<Tooltip
|
||||
|
|
@ -91,16 +91,10 @@ class Issues extends React.Component {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="flex items-center"
|
||||
onClick={this.handleOpen}
|
||||
disabled={
|
||||
!isModalDisplayed && (metaLoading || fetchIssuesLoading || projectsLoading)
|
||||
}
|
||||
>
|
||||
<Icon name={`integrations/${provider === 'jira' ? 'jira' : 'github'}`} size="16" />
|
||||
<span className="ml-2">Create Issue</span>
|
||||
</div>
|
||||
<div className="flex items-center" onClick={this.handleOpen} disabled={!isModalDisplayed && (metaLoading || fetchIssuesLoading || projectsLoading)}>
|
||||
<Icon name={ `integrations/${ provider === 'jira' ? 'jira' : 'github'}` } size="16" />
|
||||
<span className="ml-2">Create Issue</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</OutsideClickDetectingDiv>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ function SubHeader(props) {
|
|||
id={props.sessionId}
|
||||
showCopyLink={true}
|
||||
trigger={
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center h-full w-full">
|
||||
<Icon
|
||||
className="mr-2"
|
||||
disabled={props.disabled}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export default class ItemMenu extends React.PureComponent<Props> {
|
|||
<div
|
||||
key={item.key}
|
||||
role="menuitem"
|
||||
className="w-full h-full p-3 hover:bg-gray-light-shade cursor-pointer flex items-center"
|
||||
className="hover:bg-gray-light-shade cursor-pointer flex items-center"
|
||||
>
|
||||
{item.component}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ function NotePopup({
|
|||
<div
|
||||
onClick={toggleNotePopup}
|
||||
className={cn(
|
||||
'h-full w-full p-3',
|
||||
'mr-4 hover:bg-gray-light-shade rounded-md flex items-center',
|
||||
tooltipActive ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ function Bookmark(props : Props ) {
|
|||
distance={20}
|
||||
>
|
||||
{noMargin ? (
|
||||
<div onClick={ toggleFavorite } className="flex items-center cursor-pointer">
|
||||
<div onClick={ toggleFavorite } className="flex items-center cursor-pointer h-full w-full p-3">
|
||||
<Icon name={ isFavorite ? ACTIVE_ICON : INACTIVE_ICON } color={isFavorite ? "teal" : undefined} size="16" />
|
||||
<span className="ml-2">{isEnterprise ? 'Vault' : 'Bookmark'}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ export default class SharePopup extends React.PureComponent {
|
|||
arrow
|
||||
trigger="click"
|
||||
shown={this.handleOpen}
|
||||
// beforeHidden={this.handleClose}
|
||||
className="h-full w-full p-3"
|
||||
// beforeHidden={this.handleClose}
|
||||
html={
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.header}>
|
||||
|
|
|
|||
25
frontend/app/mstore/bugReportStore.ts
Normal file
25
frontend/app/mstore/bugReportStore.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { makeAutoObservable } from "mobx"
|
||||
|
||||
enum SeverityLevels {
|
||||
Low,
|
||||
Medium,
|
||||
High
|
||||
}
|
||||
|
||||
export default class BugReportStore {
|
||||
reportTitle = 'Untitled Report'
|
||||
isTitleEdit = false
|
||||
severity = SeverityLevels.High
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
toggleTitleEdit(isEdit: boolean) {
|
||||
this.isTitleEdit = isEdit
|
||||
}
|
||||
|
||||
setTitle(title: string) {
|
||||
this.reportTitle = title
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import NotificationStore from './notificationStore';
|
|||
import ErrorStore from './errorStore';
|
||||
import SessionStore from './sessionStore';
|
||||
import NotesStore from './notesStore';
|
||||
import BugReportStore from './bugReportStore'
|
||||
|
||||
export class RootStore {
|
||||
dashboardStore: DashboardStore;
|
||||
|
|
@ -34,6 +35,7 @@ export class RootStore {
|
|||
notificationStore: NotificationStore;
|
||||
sessionStore: SessionStore;
|
||||
notesStore: NotesStore;
|
||||
bugReportStore: BugReportStore
|
||||
|
||||
constructor() {
|
||||
this.dashboardStore = new DashboardStore();
|
||||
|
|
@ -47,6 +49,7 @@ export class RootStore {
|
|||
this.notificationStore = new NotificationStore();
|
||||
this.sessionStore = new SessionStore();
|
||||
this.notesStore = new NotesStore();
|
||||
this.bugReportStore = new BugReportStore();
|
||||
}
|
||||
|
||||
initClient() {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export default class NotesService {
|
|||
})
|
||||
}
|
||||
|
||||
sendSlackNotification(noteId: number, webhook: string) {
|
||||
sendSlackNotification(noteId: string, webhook: string) {
|
||||
return this.client.get(`/notes/${noteId}/slack/${webhook}`)
|
||||
.then(r => {
|
||||
if (r.ok) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue