Session details screen.

This commit is contained in:
Mike Cao 2024-07-29 19:09:13 -07:00
parent c3c3b46ef6
commit f32bf0a2e0
21 changed files with 252 additions and 33 deletions

View file

@ -1,4 +1,4 @@
import { useSessions } from 'components/hooks';
import { useWebsiteSessions } from 'components/hooks';
import SessionsTable from './SessionsTable';
import DataTable from 'components/common/DataTable';
import { ReactNode } from 'react';
@ -11,7 +11,7 @@ export default function SessionsDataTable({
teamId?: string;
children?: ReactNode;
}) {
const queryResult = useSessions(websiteId);
const queryResult = useWebsiteSessions(websiteId);
if (queryResult?.result?.data?.length === 0) {
return children;

View file

@ -0,0 +1,20 @@
.timeline {
display: flex;
flex-direction: column;
gap: 20px;
}
.row {
display: flex;
align-items: center;
gap: 20px;
}
.time {
color: var(--font-color200);
width: 120px;
}
.header {
font-weight: bold;
}

View file

@ -0,0 +1,52 @@
import { formatDate } from 'lib/date';
import { isSameDay } from 'date-fns';
import { Loading, Icon, StatusLight } from 'react-basics';
import Icons from 'components/icons';
import { useLocale, useSessionActivity } from 'components/hooks';
import styles from './SessionActivity.module.css';
export function SessionActivity({
websiteId,
sessionId,
}: {
websiteId: string;
sessionId: string;
}) {
const { locale } = useLocale();
const { data, isLoading } = useSessionActivity(websiteId, sessionId);
if (isLoading) {
return <Loading position="page" />;
}
let lastDay = null;
return (
<div className={styles.timeline}>
<h1>Activity log</h1>
{data.map(({ eventId, createdAt, urlPath, eventName, visitId }) => {
const showHeader = !lastDay || !isSameDay(new Date(lastDay), new Date(createdAt));
lastDay = createdAt;
return (
<>
{showHeader && (
<div className={styles.header}>
{formatDate(new Date(createdAt), 'EEEE, PPP', locale)}
</div>
)}
<div key={eventId} className={styles.row}>
<div className={styles.time}>
<StatusLight color={`#${visitId?.substring(0, 6)}`}>
{formatDate(new Date(createdAt), 'h:mm:ss aaa', locale)}
</StatusLight>
</div>
<Icon>{eventName ? <Icons.Bolt /> : <Icons.Eye />}</Icon>
<div>{eventName || urlPath}</div>
</div>
</>
);
})}
</div>
);
}

View file

@ -1,18 +1,27 @@
.page {
display: grid;
grid-template-columns: 300px 1fr;
grid-template-columns: 300px 1fr max-content;
margin-bottom: 40px;
}
.sidebar {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: flex-start;
gap: 20px;
padding-right: 20px;
border-right: 1px solid var(--base300);
}
.content {
padding: 0 20px;
}
.stats {
display: flex;
flex-direction: column;
gap: 20px;
border-left: 1px solid var(--base300);
padding-left: 20px;
}

View file

@ -1,10 +1,12 @@
'use client';
import WebsiteHeader from '../../WebsiteHeader';
import SessionInfo from './SessionInfo';
import { useSession } from 'components/hooks';
import { useWebsiteSession } from 'components/hooks';
import { Loading } from 'react-basics';
import Profile from 'components/common/Profile';
import styles from './SessionDetailsPage.module.css';
import { SessionActivity } from './SessionActivity';
import SessionStats from './SessionStats';
export default function SessionDetailsPage({
websiteId,
@ -13,7 +15,7 @@ export default function SessionDetailsPage({
websiteId: string;
sessionId: string;
}) {
const { data, isLoading } = useSession(websiteId, sessionId);
const { data, isLoading } = useWebsiteSession(websiteId, sessionId);
if (isLoading) {
return <Loading position="page" />;
@ -27,7 +29,12 @@ export default function SessionDetailsPage({
<Profile seed={data?.id} />
<SessionInfo data={data} />
</div>
<div className={styles.content}>oh hi.</div>
<div className={styles.content}>
<SessionActivity websiteId={websiteId} sessionId={sessionId} />
</div>
<div className={styles.stats}>
<SessionStats data={data} />
</div>
</div>
</>
);

View file

@ -1,4 +1,4 @@
import { format } from 'date-fns';
import { formatDate } from 'lib/date';
import { useFormat, useLocale, useMessages, useRegionNames } from 'components/hooks';
import TypeIcon from 'components/common/TypeIcon';
import { Icon, CopyIcon } from 'react-basics';
@ -18,15 +18,19 @@ export default function SessionInfo({ data }) {
<dd>
{data?.id} <CopyIcon value={data?.id} />
</dd>
<dt>{formatMessage(labels.firstSeen)}</dt>
<dd>{format(new Date(data?.firstAt), 'PPPpp')}</dd>
<dt>{formatMessage(labels.lastSeen)}</dt>
<dd>{format(new Date(data?.lastAt), 'PPPpp')}</dd>
<dd>{formatDate(new Date(data?.lastAt), 'EEEE, PPPpp', locale)}</dd>
<dt>{formatMessage(labels.firstSeen)}</dt>
<dd>{formatDate(new Date(data?.firstAt), 'EEEE, PPPpp', locale)}</dd>
<dt>{formatMessage(labels.country)}</dt>
<dd>
<TypeIcon type="country" value={data?.country} />
{formatValue(data?.country, 'country')}
</dd>
<dt>{formatMessage(labels.region)}</dt>
<dd>
<Icon>
@ -34,6 +38,7 @@ export default function SessionInfo({ data }) {
</Icon>
{getRegionName(data?.subdivision1)}
</dd>
<dt>{formatMessage(labels.city)}</dt>
<dd>
<Icon>
@ -41,16 +46,19 @@ export default function SessionInfo({ data }) {
</Icon>
{data?.city}
</dd>
<dt>{formatMessage(labels.os)}</dt>
<dd>
<TypeIcon type="os" value={data?.os?.toLowerCase()?.replaceAll(/\W/g, '-')} />
{formatValue(data?.os, 'os')}
</dd>
<dt>{formatMessage(labels.device)}</dt>
<dd>
<TypeIcon type="device" value={data?.device} />
{formatValue(data?.device, 'device')}
</dd>
<dt>{formatMessage(labels.browser)}</dt>
<dd>
<TypeIcon type="browser" value={data?.browser} />

View file

@ -0,0 +1,14 @@
import MetricCard from 'components/metrics/MetricCard';
import { useMessages } from 'components/hooks';
export default function SessionStats({ data }) {
const { formatMessage, labels } = useMessages();
return (
<>
<MetricCard label={formatMessage(labels.visits)} value={data?.visits} />
<MetricCard label={formatMessage(labels.views)} value={data?.views} />
<MetricCard label={formatMessage(labels.events)} value={data?.events} />
</>
);
}