fix(ui): uxt fixes
This commit is contained in:
parent
3a1fb49866
commit
8777026f69
7 changed files with 109 additions and 55 deletions
|
|
@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite'
|
|||
import { Typography, Switch, Button, Space } from "antd";
|
||||
import { ExportOutlined } from "@ant-design/icons";
|
||||
|
||||
const SidePanel = observer(({ onSave, onPreview }: any) => {
|
||||
const SidePanel = observer(({ onSave, onPreview, taskLen }: any) => {
|
||||
const { uxtestingStore } = useStore();
|
||||
return (
|
||||
<div className={'flex flex-col gap-2 col-span-1'}>
|
||||
|
|
@ -32,12 +32,12 @@ const SidePanel = observer(({ onSave, onPreview }: any) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Button onClick={onPreview}>
|
||||
<Button type={'primary'} ghost onClick={onPreview} disabled={taskLen === 0}>
|
||||
<Space align={'center'}>
|
||||
Preview <ExportOutlined rev={undefined} />
|
||||
</Space>
|
||||
</Button>
|
||||
<Button type={'primary'} onClick={onSave}>
|
||||
<Button type={'primary'} onClick={onSave} disabled={taskLen === 0}>
|
||||
Publish Test
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
import {
|
||||
Button,
|
||||
Input,
|
||||
Typography,
|
||||
Dropdown,
|
||||
Modal,
|
||||
} from 'antd';
|
||||
import { Button, Input, Typography, Dropdown, Modal } from 'antd';
|
||||
import React from 'react';
|
||||
import {
|
||||
withSiteId,
|
||||
|
|
@ -21,6 +15,7 @@ import { useStore } from 'App/mstore';
|
|||
import { confirm } from 'UI';
|
||||
import StepsModal from './StepsModal';
|
||||
import SidePanel from './SidePanel';
|
||||
import usePageTitle from 'App/hooks/usePageTitle';
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
|
|
@ -36,20 +31,28 @@ const menuItems = [
|
|||
];
|
||||
|
||||
function TestEdit() {
|
||||
const { uxtestingStore } = useStore();
|
||||
const [newTestTitle, setNewTestTitle] = React.useState('');
|
||||
const [newTestDescription, setNewTestDescription] = React.useState('');
|
||||
const [conclusion, setConclusion] = React.useState('');
|
||||
const [guidelines, setGuidelines] = React.useState('');
|
||||
const [isModalVisible, setIsModalVisible] = React.useState(false);
|
||||
const { uxtestingStore } = useStore();
|
||||
const [isConclusionEditing, setIsConclusionEditing] = React.useState(false);
|
||||
const [isOverviewEditing, setIsOverviewEditing] = React.useState(false);
|
||||
// @ts-ignore
|
||||
const { siteId, testId } = useParams();
|
||||
const { showModal, hideModal } = useModal();
|
||||
const history = useHistory();
|
||||
usePageTitle(`Usability Tests | ${uxtestingStore.instance ? 'Edit' : 'Create'}`);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (testId && testId !== 'new') {
|
||||
uxtestingStore.getTestData(testId);
|
||||
uxtestingStore.getTestData(testId).then((inst) => {
|
||||
if (inst) {
|
||||
setConclusion(inst.conclusionMessage || '');
|
||||
setGuidelines(inst.guidelines || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
if (!uxtestingStore.instance) {
|
||||
|
|
@ -105,7 +108,7 @@ function TestEdit() {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full mx-auto" style={{ maxWidth: '1360px' }}>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
|
|
@ -170,42 +173,52 @@ function TestEdit() {
|
|||
uxtestingStore.instance!.setProperty('startingPath', e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Typography.Text>Test will begin on this page</Typography.Text>
|
||||
<Typography.Text>Test will begin on this page.</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div className={'p-4 rounded bg-white border flex flex-col gap-2'}>
|
||||
<Typography.Text strong>Introduction & Guidelines</Typography.Text>
|
||||
<Typography.Text strong>Introduction and Guidelines for Participants</Typography.Text>
|
||||
<Typography.Text></Typography.Text>
|
||||
{isOverviewEditing ? (
|
||||
<Input.TextArea
|
||||
autoFocus
|
||||
rows={5}
|
||||
placeholder={'Task overview'}
|
||||
value={uxtestingStore.instance.guidelines}
|
||||
onChange={(e) => uxtestingStore.instance!.setProperty('guidelines', e.target.value)}
|
||||
value={guidelines}
|
||||
onChange={(e) => setGuidelines(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text>
|
||||
<div className={'whitespace-pre-wrap'}>
|
||||
{uxtestingStore.instance?.guidelines?.length
|
||||
? uxtestingStore.instance.guidelines
|
||||
: 'Provide an overview of this user test to and input guidelines that can be of assistance to users at any point during the test.'}
|
||||
</Typography.Text>
|
||||
: 'Provide an overview of this usability test to and input guidelines that can be of assistance to users at any point during the test.'}
|
||||
</div>
|
||||
)}
|
||||
<div className={'flex gap-2'}>
|
||||
{isOverviewEditing ? (
|
||||
<>
|
||||
<Button type={'primary'} onClick={() => setIsOverviewEditing(false)}>
|
||||
<Button
|
||||
type={'primary'}
|
||||
onClick={() => {
|
||||
uxtestingStore.instance!.setProperty('guidelines', guidelines);
|
||||
setIsOverviewEditing(false);
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
uxtestingStore.instance!.setProperty('guidelines', '');
|
||||
setIsOverviewEditing(false);
|
||||
setGuidelines(uxtestingStore.instance?.guidelines || '');
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => setIsOverviewEditing(true)}>Add</Button>
|
||||
<Button type={'primary'} ghost onClick={() => setIsOverviewEditing(true)}>
|
||||
{uxtestingStore.instance?.guidelines?.length ? 'Edit' : 'Add'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -238,6 +251,8 @@ function TestEdit() {
|
|||
))}
|
||||
<div>
|
||||
<Button
|
||||
type={'primary'}
|
||||
ghost
|
||||
onClick={() =>
|
||||
showModal(
|
||||
<StepsModal
|
||||
|
|
@ -264,10 +279,8 @@ function TestEdit() {
|
|||
{isConclusionEditing ? (
|
||||
<Input.TextArea
|
||||
placeholder={'Thanks for participation!..'}
|
||||
value={uxtestingStore.instance!.conclusionMessage}
|
||||
onChange={(e) =>
|
||||
uxtestingStore.instance!.setProperty('conclusionMessage', e.target.value)
|
||||
}
|
||||
value={conclusion}
|
||||
onChange={(e) => setConclusion(e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text>{uxtestingStore.instance!.conclusionMessage}</Typography.Text>
|
||||
|
|
@ -276,27 +289,39 @@ function TestEdit() {
|
|||
<div className={'flex gap-2'}>
|
||||
{isConclusionEditing ? (
|
||||
<>
|
||||
<Button type={'primary'} onClick={() => setIsConclusionEditing(false)}>
|
||||
<Button
|
||||
type={'primary'}
|
||||
onClick={() => {
|
||||
uxtestingStore.instance!.setProperty('conclusionMessage', conclusion);
|
||||
setIsConclusionEditing(false);
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
uxtestingStore.instance!.setProperty('conclusionMessage', '');
|
||||
setIsConclusionEditing(false);
|
||||
setConclusion(uxtestingStore.instance?.conclusionMessage || '');
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button onClick={() => setIsConclusionEditing(true)}>Edit</Button>
|
||||
<Button type={'primary'} ghost onClick={() => setIsConclusionEditing(true)}>
|
||||
Edit
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SidePanel onSave={() => onSave(false)} onPreview={() => onSave(true)} />
|
||||
<SidePanel
|
||||
taskLen={uxtestingStore.instance.tasks.length}
|
||||
onSave={() => onSave(false)}
|
||||
onPreview={() => onSave(true)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { durationFormatted } from 'App/date';
|
||||
import usePageTitle from "App/hooks/usePageTitle";
|
||||
import { numberWithCommas } from 'App/utils';
|
||||
import { getPdf2 } from 'Components/AssistStats/pdfGenerator';
|
||||
import { useModal } from 'Components/Modal';
|
||||
|
|
@ -64,11 +65,13 @@ function TestOverview() {
|
|||
const { siteId, testId } = useParams();
|
||||
const { showModal } = useModal();
|
||||
const { uxtestingStore } = useStore();
|
||||
usePageTitle(`Usability Tests | ${uxtestingStore.instance?.title || ''}`);
|
||||
|
||||
React.useEffect(() => {
|
||||
uxtestingStore.getTest(testId);
|
||||
}, [testId]);
|
||||
|
||||
|
||||
if (!uxtestingStore.instance) {
|
||||
return <Loader loading={uxtestingStore.isLoading}>No data.</Loader>;
|
||||
}
|
||||
|
|
@ -78,7 +81,7 @@ function TestOverview() {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full mx-auto" style={{ maxWidth: '1360px'}}>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{
|
||||
|
|
@ -172,7 +175,7 @@ function TestOverview() {
|
|||
</Loader>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -378,7 +381,7 @@ const Title = observer(({ testId, siteId }: any) => {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<Button>
|
||||
<Button type={'primary'} ghost>
|
||||
<Space align={'center'}>
|
||||
Distribute
|
||||
<ShareAltOutlined rev={undefined} />
|
||||
|
|
@ -386,7 +389,7 @@ const Title = observer(({ testId, siteId }: any) => {
|
|||
</Button>
|
||||
</Popover>
|
||||
<Dropdown menu={{ items: menuItems, onClick: onMenuClick }}>
|
||||
<Button icon={<MoreOutlined rev={undefined} />}></Button>
|
||||
<Button ghost type={'primary'} icon={<MoreOutlined rev={undefined} />}></Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,16 +12,17 @@ import { UnorderedListOutlined, ArrowRightOutlined } from '@ant-design/icons';
|
|||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { withSiteId, usabilityTestingEdit, usabilityTestingView } from 'App/routes';
|
||||
import { debounce } from 'App/utils';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
const PER_PAGE = 10;
|
||||
|
||||
let debouncedSearch: any = () => null
|
||||
|
||||
const defaultDescription = `To evaluate the usability of [Feature Name], focusing on user interaction, efficiency, and satisfaction. The aim is to identify any usability issues that users may encounter, understand how they navigate [Feature Name], and gauge the intuitiveness of the workflow.`
|
||||
function TestsTable() {
|
||||
const [newTestTitle, setNewTestTitle] = React.useState('');
|
||||
const [newTestDescription, setNewTestDescription] = React.useState('');
|
||||
const [newTestDescription, setNewTestDescription] = React.useState(defaultDescription);
|
||||
const [isModalVisible, setIsModalVisible] = React.useState(false);
|
||||
const { uxtestingStore } = useStore();
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ function TestsTable() {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full mx-auto" style={{ maxWidth: '1360px'}}>
|
||||
<Modal
|
||||
title="Create Usability Test"
|
||||
open={isModalVisible}
|
||||
|
|
@ -78,8 +79,9 @@ function TestsTable() {
|
|||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography.Text strong>Name this user test</Typography.Text>
|
||||
<Typography.Text strong>Title</Typography.Text>
|
||||
<Input
|
||||
autoFocus
|
||||
placeholder="E.g. Checkout user journey evaluation"
|
||||
style={{ marginBottom: '2em' }}
|
||||
value={newTestTitle}
|
||||
|
|
@ -87,6 +89,7 @@ function TestsTable() {
|
|||
/>
|
||||
<Typography.Text strong>Test Objective (optional)</Typography.Text>
|
||||
<Input.TextArea
|
||||
rows={5}
|
||||
value={newTestDescription}
|
||||
onChange={(e) => setNewTestDescription(e.target.value)}
|
||||
placeholder="Share a brief statement about what you aim to discover through this study."
|
||||
|
|
@ -126,7 +129,7 @@ function TestsTable() {
|
|||
<AnimatedSVG name={ICONS.NO_FFLAGS} size={285} />
|
||||
<div className="text-center text-gray-600 mt-4">
|
||||
{uxtestingStore.searchQuery === ''
|
||||
? "You haven't created any user tests yet"
|
||||
? "You haven't created any usability tests yet"
|
||||
: 'No matching results'}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -161,7 +164,7 @@ function TestsTable() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -202,4 +205,4 @@ function Cell({ size, children }: { size: number; children?: React.ReactNode })
|
|||
return <div className={`col-span-${size}`}>{children}</div>;
|
||||
}
|
||||
|
||||
export default observer(TestsTable);
|
||||
export default withPageTitle('Usability Tests')(observer(TestsTable))
|
||||
|
|
|
|||
|
|
@ -27,6 +27,27 @@ interface Response {
|
|||
duration: number;
|
||||
}
|
||||
|
||||
const defaultGuidelines = `
|
||||
Introduction:localStorage
|
||||
Thank you for participating in this important stage of our [Website/App] development. Your insights will help us enhance the [Desktop/Mobile] experience for all users.localStoragelocalStorage
|
||||
|
||||
Before You Begin:localStorage
|
||||
• Device: Ensure you're using a [Desktop/Mobile] for this test.localStoragelocalStorage
|
||||
|
||||
• Environment: Choose a quiet location where you can focus without interruptions.localStoragelocalStorage
|
||||
|
||||
Test Guidelines:localStorage
|
||||
1. Task Flow: You will perform a series of tasks as you normally would when using a [Website/App].localStoragelocalStorage
|
||||
|
||||
2. Think Aloud: Please verbalize your thoughts. If something is confusing, interesting, or pleasing, let us know.localStoragelocalStorage
|
||||
|
||||
3. No Right or Wrong: There are no correct answers here, just your honest experience.localStoragelocalStorage
|
||||
|
||||
4. Pace Yourself: Take your time, there's no rush to complete the tasks quickly.localStoragelocalStorage
|
||||
|
||||
5. Technical Issues: If you encounter any issues, please describe what you were attempting to do when the issue occurred.localStorage
|
||||
`
|
||||
|
||||
export default class UxtestingStore {
|
||||
client = uxtestingService;
|
||||
tests: UxTListEntry[] = [];
|
||||
|
|
@ -125,7 +146,7 @@ export default class UxtestingStore {
|
|||
requireMic: false,
|
||||
requireCamera: false,
|
||||
description: description,
|
||||
guidelines: '',
|
||||
guidelines: defaultGuidelines,
|
||||
conclusionMessage: '',
|
||||
visibility: true,
|
||||
tasks: [],
|
||||
|
|
@ -185,6 +206,7 @@ export default class UxtestingStore {
|
|||
try {
|
||||
const test = await this.client.fetchTest(testId);
|
||||
this.setInstance(new UxTestInst(test));
|
||||
return this.instance
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "11.0.0-beta.1",
|
||||
"version": "11.0.0-beta.4",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export default class UserTestManager {
|
|||
)
|
||||
|
||||
this.removeGreeting = () => {
|
||||
this.container.innerHTML = ''
|
||||
// this.container.innerHTML = ''
|
||||
if (micRequired || cameraRequired) {
|
||||
void this.userRecorder.startRecording(30, Quality.Standard, micRequired, cameraRequired)
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ export default class UserTestManager {
|
|||
this.removeGreeting()
|
||||
this.durations.testStart = this.app.timestamp()
|
||||
void this.signalTest('begin')
|
||||
this.showWidget(this.test?.description || '', this.test?.tasks || [])
|
||||
this.showWidget(this.test?.guidelines || '', this.test?.tasks || [])
|
||||
}
|
||||
|
||||
this.container.append(titleElement, descriptionElement, noticeElement, buttonElement)
|
||||
|
|
@ -222,7 +222,7 @@ export default class UserTestManager {
|
|||
}
|
||||
|
||||
showWidget(
|
||||
description: string,
|
||||
guidelines: string,
|
||||
tasks: {
|
||||
title: string
|
||||
description: string
|
||||
|
|
@ -248,7 +248,7 @@ export default class UserTestManager {
|
|||
// Create title section
|
||||
const titleSection = this.createTitleSection()
|
||||
Object.assign(this.container.style, styles.containerWidgetStyle)
|
||||
const descriptionSection = this.createDescriptionSection(description)
|
||||
const descriptionSection = this.createDescriptionSection(guidelines)
|
||||
const tasksSection = this.createTasksSection(tasks)
|
||||
const stopButton = createElement('div', 'stop_bn_or', styles.stopWidgetStyle, 'Abort Session')
|
||||
|
||||
|
|
@ -326,7 +326,7 @@ export default class UserTestManager {
|
|||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
toggleDescriptionVisibility = () => {}
|
||||
|
||||
createDescriptionSection(description: string) {
|
||||
createDescriptionSection(guidelines: string) {
|
||||
const section = createElement('div', 'description_section_or', styles.descriptionWidgetStyle)
|
||||
const titleContainer = createElement('div', 'description_s_title_or', styles.sectionTitleStyle)
|
||||
const title = createElement('div', 'title', {}, 'Introduction & Guidelines')
|
||||
|
|
@ -334,9 +334,10 @@ export default class UserTestManager {
|
|||
const content = createElement('div', 'content', styles.contentStyle)
|
||||
const descriptionC = createElement('div', 'text_description', {
|
||||
maxHeight: '250px',
|
||||
overflow: 'scroll',
|
||||
overflowY: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
})
|
||||
descriptionC.innerHTML = description
|
||||
descriptionC.innerHTML = guidelines
|
||||
const button = createElement('div', 'button_begin_or', styles.buttonWidgetStyle, 'Begin Test')
|
||||
|
||||
titleContainer.append(title, icon)
|
||||
|
|
@ -543,7 +544,7 @@ export default class UserTestManager {
|
|||
'end_description_or',
|
||||
{},
|
||||
this.test?.conclusion ??
|
||||
'Thank you for participating in our user test. Your feedback has been captured and will be used to enhance our website. \n' +
|
||||
'Thank you for participating in our usability test. Your feedback has been captured and will be used to enhance our website. \n' +
|
||||
'\n' +
|
||||
'We appreciate your time and valuable input.',
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue