Skip to content

Commit

Permalink
Playground Layout: Put visitor ID in Identification column (#148)
Browse files Browse the repository at this point in the history
* feat: external link animation

* feat: custom spinner

* feat: running intelligence animation

* fix: move refresh button to the top

* feat: add remote control smart signal

* feat: add velocity smart signal

* left align hero section on mobile

* fix accordion color

* fix build

* feat: use the same mapbox tiles as the main website

* ci: run check even when PR is not into main

* feat: put visitor ID box in the identification column

* fix mobile layout

* self-review: remove comments

* test: fix locator

* fix map z index

* fix: disable scroll restoration on playground

* feat: add table animations

* fix: remove !important
  • Loading branch information
JuroUhlar authored Jul 15, 2024
1 parent 5049a57 commit f96d384
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 136 deletions.
2 changes: 1 addition & 1 deletion e2e/playground.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test.beforeEach(async ({ page }) => {
test.describe('Playground page', () => {
test('Page renders basic skeleton elements', async ({ page }) => {
await page.getByText('Fingerprint Pro Playground', { exact: true }).waitFor();
await page.getByText('Welcome, this is your visitor ID').waitFor();
await page.getByText('Your Visitor ID is').waitFor();
await page.getByTestId(TEST_ID.refreshButton).first().waitFor();

await page.getByText('Identification', { exact: true }).waitFor();
Expand Down
223 changes: 124 additions & 99 deletions src/app/playground/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { FunctionComponent } from 'react';
import { FunctionComponent, useEffect, ReactNode } from 'react';
import { CollapsibleJsonViewer } from '../../client/components/common/CodeSnippet/CodeSnippet';
import dynamic from 'next/dynamic';
import SignalTable, { TableCellData } from './components/SignalTable';
Expand Down Expand Up @@ -32,6 +32,7 @@ import {
} from '../../client/components/common/Collapsible/Collapsible';
import { ChevronSvg } from '../../client/img/chevronSvg';
import { pluralize } from '../../shared/utils';
import { motion } from 'framer-motion';

const PLAYGROUND_COPY = {
androidOnly: 'Applicable only to Android devices',
Expand Down Expand Up @@ -63,6 +64,17 @@ const DocsLink: FunctionComponent<{ children: string; href: string; style?: Reac
// Map cannot be server-side rendered
const Map = dynamic(() => import('./components/Map'), { ssr: false });

const TableTitle = ({ children }: { children: ReactNode }) => (
<motion.h3
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
className={styles.tableTitle}
>
{children}
</motion.h3>
);

function Playground() {
const {
agentResponse,
Expand All @@ -75,6 +87,13 @@ function Playground() {
serverError,
} = usePlaygroundSignals();

/**
* Prevent restoring scroll position on page refresh since there is nothing to scroll to while the data is being loaded
*/
useEffect(() => {
window.history.scrollRestoration = 'manual';
}, []);

if (agentError) {
return <Alert severity={'error'}>JavaScript Agent Error: {agentError.message}.</Alert>;
}
Expand Down Expand Up @@ -147,14 +166,14 @@ function Playground() {
content: (
<>
{latitude && longitude && (
<div>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.25 }}>
<Map
key={[latitude, longitude].toString()}
position={[latitude, longitude]}
zoom={zoom}
height='95px'
/>
</div>
</motion.div>
)}
</>
),
Expand Down Expand Up @@ -448,120 +467,126 @@ function Playground() {
</h1>
<p>Analyze your browser with Fingerprint Pro and see all the available signals.</p>
</Container>
{agentResponse && (
<Container size='large'>
<div className={styles.visitorIdBox}>
<p>Welcome, this is your visitor ID</p>
<h2 className={styles.visitorId}>{agentResponse?.visitorId}</h2>
</div>
</Container>
)}
{!cachedEvent ? (
<Container size='large'>
<div className={styles.runningIntelligence}>
<Spinner size={64} />
<Container size='large'>
<div className={styles.runningIntelligence}>
{!cachedEvent ? (
<h2>
Running Device Intelligence<span className={styles.blink}>_</span>
</h2>
</div>
</Container>
) : (
<>
<Container size='large'>
) : (
<RefreshButton
loading={isLoadingAgentResponse || isLoadingServerResponse}
getAgentData={getAgentData}
className={styles.reloadButton}
/>

<div className={styles.tablesContainer}>
)}
</div>
</Container>
<>
<Container size='large'>
<div className={styles.tablesContainer}>
{agentResponse ? (
<MyCollapsible defaultOpen>
<h3 className={styles.tableTitle}>
<TableTitle>
Identification{' '}
<MyCollapsibleTrigger>
<ChevronSvg />
</MyCollapsibleTrigger>
</h3>
</TableTitle>
<MyCollapsibleContent>
<SignalTable data={identificationSignals} />
</MyCollapsibleContent>
</MyCollapsible>
<div className={styles.visitorIdBox}>
<p>Your Visitor ID is </p>
<h2 className={styles.visitorId}>{agentResponse?.visitorId}</h2>
</div>

<MyCollapsible defaultOpen>
<h3 className={styles.tableTitle}>
Smart signals{' '}
<MyCollapsibleTrigger>
<ChevronSvg />
</MyCollapsibleTrigger>
</h3>
<MyCollapsibleContent>
<SignalTable data={smartSignals} />
</MyCollapsibleContent>
</MyCollapsible>
<MyCollapsible defaultOpen>
<h3 className={styles.tableTitle}>
Mobile Smart signals{' '}
<MyCollapsibleTrigger>
<ChevronSvg />
</MyCollapsibleTrigger>
</h3>
<MyCollapsibleContent>
<SignalTable data={mobileSmartSignals} />
<SignalTable data={identificationSignals} />
</MyCollapsibleContent>
</MyCollapsible>
</div>
</Container>

<Container size='large' className={styles.isSection}>
<h2 className={styles.sectionTitle}>How to use this demo</h2>
<HowToUseThisPlayground />
</Container>
<Container size='large' className={classnames(styles.isSection, styles.jsonSection)}>
<div className={styles.jsonContainer}>
<div>
<h4 className={styles.jsonTitle}>
JavaScript Agent Response {isLoadingAgentResponse && <Spinner size={16} />}
</h4>
<CollapsibleJsonViewer
dataTestId={TEST_IDS.playground.agentResponseJSON}
json={displayedAgentResponse ?? {}}
/>
</div>
<div>
<h4 className={styles.jsonTitle}>
Server API Response {isLoadingServerResponse && <Spinner size={16} />}
</h4>
<CollapsibleJsonViewer
dataTestId={TEST_IDS.playground.serverResponseJSON}
json={usedIdentificationEvent ?? {}}
/>
) : (
// Spacer element to push footer down when no data is ready
<div style={{ height: '800px' }} />
)}
{cachedEvent ? (
<>
<MyCollapsible defaultOpen>
<TableTitle>
Smart signals{' '}
<MyCollapsibleTrigger>
<ChevronSvg />
</MyCollapsibleTrigger>
</TableTitle>
<MyCollapsibleContent>
<SignalTable data={smartSignals} />
</MyCollapsibleContent>
</MyCollapsible>
<MyCollapsible defaultOpen>
<TableTitle>
Mobile Smart signals{' '}
<MyCollapsibleTrigger>
<ChevronSvg />
</MyCollapsibleTrigger>
</TableTitle>
<MyCollapsibleContent>
<SignalTable data={mobileSmartSignals} />
</MyCollapsibleContent>
</MyCollapsible>
</>
) : null}
</div>
</Container>
{cachedEvent ? (
<>
<Container size='large' className={styles.isSection}>
<h2 className={styles.sectionTitle}>How to use this demo</h2>
<HowToUseThisPlayground />
</Container>
<Container size='large' className={classnames(styles.isSection, styles.jsonSection)}>
<div className={styles.jsonContainer}>
<div>
<h4 className={styles.jsonTitle}>
JavaScript Agent Response {isLoadingAgentResponse && <Spinner size={16} />}
</h4>
<CollapsibleJsonViewer
dataTestId={TEST_IDS.playground.agentResponseJSON}
json={displayedAgentResponse ?? {}}
/>
</div>
<div>
<h4 className={styles.jsonTitle}>
Server API Response {isLoadingServerResponse && <Spinner size={16} />}
</h4>
<CollapsibleJsonViewer
dataTestId={TEST_IDS.playground.serverResponseJSON}
json={usedIdentificationEvent ?? {}}
/>
</div>
</div>
</div>
</Container>
<Container size='large' className={styles.learnMoreSection}>
<h2 className={styles.sectionTitle}>Learn more</h2>
</Container>
<ResourceLinks
resources={[
{
title: 'Quick Start Guide',
url: 'https://dev.fingerprint.com/docs/quick-start-guide',
type: 'Article',
},
{
title: 'What is Fingerprint',
url: 'https://dev.fingerprint.com/docs/what-is-fingerprint',
type: 'Article',
},
{
title: 'Intro to Device Intelligence Webinar',
url: 'https://www.youtube.com/watch?v=YTRmWUeQWyY',
type: 'Webinar',
},
]}
/>
</>
)}
</Container>
<Container size='large' className={styles.learnMoreSection}>
<h2 className={styles.sectionTitle}>Learn more</h2>
</Container>
<ResourceLinks
resources={[
{
title: 'Quick Start Guide',
url: 'https://dev.fingerprint.com/docs/quick-start-guide',
type: 'Article',
},
{
title: 'What is Fingerprint',
url: 'https://dev.fingerprint.com/docs/what-is-fingerprint',
type: 'Article',
},
{
title: 'Intro to Device Intelligence Webinar',
url: 'https://www.youtube.com/watch?v=YTRmWUeQWyY',
type: 'Webinar',
},
]}
/>
</>
) : null}
</>
</>
);
}
Expand Down
8 changes: 2 additions & 6 deletions src/app/playground/components/RefreshButton.module.scss
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
.refreshButton.refreshButton {
display: flex;
padding: rem(12px) rem(24px);
padding: 16px;
font-weight: 500;
font-size: 16px;
font-weight: 500;
font-weight: 400;
line-height: 150%; /* 24px */
white-space: unset;

@include media('<=phoneLandscape') {
font-size: 14px;
}

img {
transition: scale 0.2s ease;
margin-left: rem(4px);
Expand Down
12 changes: 9 additions & 3 deletions src/app/playground/components/SignalTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode, FunctionComponent } from 'react';
import styles from './SignalTable.module.scss';
import { motion } from 'framer-motion';

export type TableCellData = {
content: ReactNode | ReactNode[];
Expand All @@ -10,14 +11,19 @@ const SignalTable: FunctionComponent<{ data: TableCellData[][] }> = ({ data }) =
return (
<table className={styles.signalTable}>
<tbody>
{data.map((row, i) => (
<tr key={i}>
{data.map((row, rowIndex) => (
<motion.tr
key={rowIndex}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: rowIndex * 0.1 }}
>
{row.map((cell, j) => (
<td key={j} className={cell.className}>
{cell.content}
</td>
))}
</tr>
</motion.tr>
))}
</tbody>
</table>
Expand Down
Loading

0 comments on commit f96d384

Please sign in to comment.