diff --git a/frontend/app/components/shared/SessionItem/SessionItem.js b/frontend/app/components/shared/SessionItem/SessionItem.js
deleted file mode 100644
index 6222327f3..000000000
--- a/frontend/app/components/shared/SessionItem/SessionItem.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import { connect } from 'react-redux';
-import cn from 'classnames';
-import {
- Link,
- Icon,
- CountryFlag,
- Avatar,
- TextEllipsis,
- Label,
-} from 'UI';
-import { toggleFavorite, setSessionPath } from 'Duck/sessions';
-import { session as sessionRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
-import { durationFormatted, formatTimeOrDate } from 'App/date';
-import stl from './sessionItem.css';
-import Counter from './Counter'
-import { withRouter } from 'react-router-dom';
-import SessionMetaList from './SessionMetaList';
-import PlayLink from './PlayLink';
-import ErrorBars from './ErrorBars';
-import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from "App/routes";
-import { capitalize } from 'App/utils';
-
-const ASSIST_ROUTE = assistRoute();
-const ASSIST_LIVE_SESSION = liveSession()
-const SESSIONS_ROUTE = sessionsRoute();
-
-@connect(state => ({
- timezone: state.getIn(['sessions', 'timezone']),
- siteId: state.getIn([ 'site', 'siteId' ]),
-}), { toggleFavorite, setSessionPath })
-@withRouter
-export default class SessionItem extends React.PureComponent {
- // eslint-disable-next-line complexity
- render() {
- const {
- session: {
- sessionId,
- userBrowser,
- userOs,
- userId,
- userAnonymousId,
- userDisplayName,
- userCountry,
- startedAt,
- duration,
- eventsCount,
- errorsCount,
- pagesCount,
- viewed,
- favorite,
- userDeviceType,
- userUuid,
- userNumericHash,
- live,
- metadata,
- userSessionsCount,
- issueTypes,
- active,
- },
- timezone,
- onUserClick = () => null,
- hasUserFilter = false,
- disableUser = false,
- metaList = [],
- showActive = false,
- lastPlayedSessionId,
- } = this.props;
- const formattedDuration = durationFormatted(duration);
- const hasUserId = userId || userAnonymousId;
- const isSessions = isRoute(SESSIONS_ROUTE, this.props.location.pathname);
- const isAssist = isRoute(ASSIST_ROUTE, this.props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, this.props.location.pathname);
- const isLastPlayed = lastPlayedSessionId === sessionId;
-
- const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => {
- const value = metadata[key];
- return { label: key, value };
- });
-
- return (
-
-
-
-
-
-
-
(!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)}
- >
-
-
-
-
-
-
{formatTimeOrDate(startedAt, timezone) }
-
- {!isAssist && (
- <>
-
- { eventsCount }
- { eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' }
-
-
·
- >
- )}
-
{ live ? : formattedDuration }
-
-
-
-
-
-
-
-
-
·
-
-
-
-
·
-
-
-
-
-
- { isSessions && (
-
-
-
- )}
-
-
-
- { isAssist && showActive && (
-
- )}
-
-
-
- { _metaList.length > 0 && (
-
- )}
-
- );
- }
-}
\ No newline at end of file
diff --git a/frontend/app/components/shared/SessionItem/SessionItem.tsx b/frontend/app/components/shared/SessionItem/SessionItem.tsx
index dc51bfc17..f00f58c82 100644
--- a/frontend/app/components/shared/SessionItem/SessionItem.tsx
+++ b/frontend/app/components/shared/SessionItem/SessionItem.tsx
@@ -1,90 +1,112 @@
-import React from 'react';
-import { connect } from 'react-redux';
+import React from 'react'
import cn from 'classnames';
-import {
- Link,
- Icon,
+import {
CountryFlag,
Avatar,
TextEllipsis,
Label,
} from 'UI';
-import { toggleFavorite, setSessionPath } from 'Duck/sessions';
-import { session as sessionRoute, liveSession as liveSessionRoute, withSiteId } from 'App/routes';
+import { useStore } from 'App/mstore';
+import { observer } from 'mobx-react-lite';
import { durationFormatted, formatTimeOrDate } from 'App/date';
import stl from './sessionItem.css';
import Counter from './Counter'
-import { withRouter } from 'react-router-dom';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
import SessionMetaList from './SessionMetaList';
+import PlayLink from './PlayLink';
import ErrorBars from './ErrorBars';
import { assist as assistRoute, liveSession, sessions as sessionsRoute, isRoute } from "App/routes";
import { capitalize } from 'App/utils';
-import { SKIP_TO_ISSUE, TIMEZONE, DURATION_FILTER } from 'App/constants/storageKeys'
const ASSIST_ROUTE = assistRoute();
const ASSIST_LIVE_SESSION = liveSession()
const SESSIONS_ROUTE = sessionsRoute();
-// @connect(state => ({
-// timezone: state.getIn(['sessions', 'timezone']),
-// siteId: state.getIn([ 'site', 'siteId' ]),
-// }), { toggleFavorite, setSessionPath })
-// @withRouter
-function SessionItem(props) {
- // render() {
- const {
- session: {
- sessionId,
- userBrowser,
- userOs,
- userId,
- userAnonymousId,
- userDisplayName,
- userCountry,
- startedAt,
- duration,
- eventsCount,
- errorsCount,
- pagesCount,
- viewed,
- favorite,
- userDeviceType,
- userUuid,
- userNumericHash,
- live,
- metadata,
- userSessionsCount,
- issueTypes,
- active,
- },
- timezone,
- onUserClick = () => null,
- hasUserFilter = false,
- disableUser = false,
- metaList = [],
- showActive = false,
- lastPlayedSessionId,
- } = props;
- const formattedDuration = durationFormatted(duration);
- const hasUserId = userId || userAnonymousId;
- const isSessions = isRoute(SESSIONS_ROUTE, props.location.pathname);
- const isAssist = isRoute(ASSIST_ROUTE, props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, props.location.pathname);
- const isLastPlayed = lastPlayedSessionId === sessionId;
+interface Props {
+ session: {
+ sessionId: string;
+ userBrowser: string;
+ userOs: string;
+ userId: string;
+ userAnonymousId: string;
+ userDisplayName: string;
+ userCountry: string;
+ startedAt: number;
+ duration: string;
+ eventsCount: number;
+ errorsCount: number;
+ pagesCount: number;
+ viewed: boolean;
+ favorite: boolean;
+ userDeviceType: string;
+ userUuid: string;
+ userNumericHash: number;
+ live: boolean
+ metadata: Record;
+ userSessionsCount: number
+ issueTypes: [];
+ active: boolean;
+ onUserClick: (userId: string, userAnonymousId: string) => null;
+ hasUserFilter: boolean;
+ disableUser: boolean;
+ metaList: Array;
+ showActive: boolean;
+ lastPlayedSessionId: string;
+ },
+}
- const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => {
- const value = metadata[key];
- return { label: key, value };
- });
+function SessionItem(props: RouteComponentProps) {
+ const { settingsStore } = useStore();
+ const { timezone } = settingsStore.sessionSettings;
- return (
-
+ const {
+ session: {
+ sessionId,
+ userBrowser,
+ userOs,
+ userId,
+ userAnonymousId,
+ userDisplayName,
+ userCountry,
+ startedAt,
+ duration,
+ eventsCount,
+ viewed,
+ userDeviceType,
+ userNumericHash,
+ live,
+ metadata,
+ issueTypes,
+ active,
+ },
+ onUserClick = () => null,
+ hasUserFilter = false,
+ disableUser = false,
+ metaList = [],
+ showActive = false,
+ lastPlayedSessionId,
+ } = props;
+
+ const formattedDuration = durationFormatted(duration);
+ const hasUserId = userId || userAnonymousId;
+ const isSessions = isRoute(SESSIONS_ROUTE, props.location.pathname);
+ const isAssist = isRoute(ASSIST_ROUTE, props.location.pathname) || isRoute(ASSIST_LIVE_SESSION, props.location.pathname);
+ const isLastPlayed = lastPlayedSessionId === sessionId;
+
+ const _metaList = Object.keys(metadata).filter(i => metaList.includes(i)).map(key => {
+ const value = metadata[key];
+ return { label: key, value };
+ });
+
+ return (
+
(!disableUser && !hasUserFilter) && onUserClick(userId, userAnonymousId)}
>
@@ -111,7 +133,7 @@ function SessionItem(props) {
-
+
·
@@ -145,9 +167,11 @@ function SessionItem(props) {
)}
)}
-
-
-
+
@@ -155,10 +179,7 @@ function SessionItem(props) {
)}
- );
- }
+ )
+}
-export default connect(state => ({
- timezone: localStorage.getItem(TIMEZONE) || '',
- siteId: state.getIn([ 'site', 'siteId' ]),
-}), { toggleFavorite, setSessionPath })(withRouter(SessionItem));
\ No newline at end of file
+export default withRouter(observer(SessionItem))
diff --git a/frontend/app/components/shared/SessionItem/index.js b/frontend/app/components/shared/SessionItem/index.ts
similarity index 100%
rename from frontend/app/components/shared/SessionItem/index.js
rename to frontend/app/components/shared/SessionItem/index.ts
diff --git a/frontend/app/components/shared/SessionItem/sessionItem.css b/frontend/app/components/shared/SessionItem/sessionItem.css
index cb7b87c3a..897ee327f 100644
--- a/frontend/app/components/shared/SessionItem/sessionItem.css
+++ b/frontend/app/components/shared/SessionItem/sessionItem.css
@@ -1,11 +1,9 @@
-@import 'mixins.css';
-
.sessionItem {
background-color: #fff;
- @mixin defaultHover;
user-select: none;
border-radius: 3px;
border: solid thin #EEEEEE;
+ transition: all 0.4s;
& .favorite {
opacity: 0;
@@ -15,6 +13,10 @@
}
&:hover {
+ background-color: $active-blue;
+ border: solid thin $active-blue-border;
+ transition: all 0.2s;
+
& .playLink {
transition: all 0.4s;
opacity: 1;
@@ -103,7 +105,11 @@
letter-spacing: 1px;
}
-.userName:hover {
- text-decoration: underline;
- text-decoration-color: $teal;
-}
\ No newline at end of file
+.userName {
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ text-decoration-color: $teal;
+ }
+}
diff --git a/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx
index 8e6edee88..1f1299025 100644
--- a/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx
+++ b/frontend/app/components/shared/SessionSettings/components/DefaultTimezone.tsx
@@ -5,24 +5,49 @@ import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
const str = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)
-const d = str && str[1] || 'UTC';
-const localMachineFormat = new Date().toString().match(/([A-Z]+[\+-][0-9]+)/)
-const middlePoint = localMachineFormat && localMachineFormat[1].length - 2
-const readableLocalTimezone = localMachineFormat && middlePoint ?
- `${localMachineFormat[1].substring(0, 3)} ${localMachineFormat[1].substring(3, middlePoint)}:${localMachineFormat[1].substring(middlePoint)}`
- : null
+interface TimezonesDropdownValue {
+ label: string;
+ value: string;
+}
+type TimezonesDropdown = TimezonesDropdownValue[]
-const timezoneOptions = [
- { label: readableLocalTimezone, value: 'local' },
- { label: 'UTC', value: 'UTC' },
-]
+const generateGMTZones = (): TimezonesDropdown => {
+ const timezones: TimezonesDropdown = []
+
+ const positiveNumbers = [...Array(12).keys()];
+ const negativeNumbers = [...Array(12).keys()].reverse();
+ negativeNumbers.pop(); // remove trailing zero since we have on in positive numbers array
+
+ const combinedArray = [...negativeNumbers, ...positiveNumbers];
+
+ for (let i = 0; i < 23; i++) {
+ let symbol = i < 11 ? '-' : '+';
+ let isUTC = i === 11
+ let prefix = isUTC ? 'UTC / GMT' : 'GMT';
+ let value = String(combinedArray[i]).padStart(2, '0');
+
+ let tz = `${prefix} ${symbol}${String(combinedArray[i]).padStart(2, '0')}:00`
+
+ let dropdownValue = `UTC${symbol}${value}`
+ timezones.push({ label: tz, value: isUTC ? 'UTC' : dropdownValue })
+ }
+
+ timezones.splice(17, 0, { label: 'GMT +05:30', value: 'GMT +05:30' })
+ return timezones
+}
+
+const timezoneOptions: TimezonesDropdown = [...generateGMTZones()]
function DefaultTimezone(props) {
const [changed, setChanged] = React.useState(false);
const { settingsStore } = useStore();
const [timezone, setTimezone] = React.useState(settingsStore.sessionSettings.timezone);
- const sessionSettings = useObserver(() => settingsStore.sessionSettings)
+ const sessionSettings = useObserver(() => settingsStore.sessionSettings);
+
+ useEffect(() => {
+ if (!timezone) setTimezone('local');
+ }, []);
return (
<>
diff --git a/frontend/app/date.ts b/frontend/app/date.ts
index 8a9501a86..e32704db1 100644
--- a/frontend/app/date.ts
+++ b/frontend/app/date.ts
@@ -20,7 +20,7 @@ export const durationFormatted = (duration: Duration):string => {
export function durationFromMsFormatted(ms: number): string {
return durationFormatted(Duration.fromMillis(ms || 0));
-}
+}
export const durationFormattedFull = (duration: Duration): string => {
if (duration.as('minutes') < 1) { // show in seconds
@@ -35,7 +35,7 @@ export const durationFormattedFull = (duration: Duration): string => {
} else if (duration.as('months') < 1) { // show in days and hours
let d = duration.toFormat('d');
duration = d + (d > 1 ? ' days' : ' day');
- } else {
+ } else {
let d = Math.trunc(duration.as('months'));
duration = d + (d > 1 ? ' months' : ' month');;
}
@@ -49,7 +49,7 @@ export const msToSec = (ms:number): number => Math.round(ms / 1000);
export const diffFromNowString = (ts:number): string =>
durationFormattedFull(DateTime.fromMillis(Date.now()).diff(DateTime.fromMillis(ts)));
-export const diffFromNowShortString = (ts: number): string =>
+export const diffFromNowShortString = (ts: number): string =>
durationFormatted(DateTime.fromMillis(Date.now()).diff(DateTime.fromMillis(ts)));
export const getDateFromMill = date =>
@@ -69,11 +69,19 @@ export function formatDateTimeDefault(timestamp: number): string {
return isToday(date) ? 'Today' : date.toFormat('LLL dd, yyyy') + ', ' + date.toFormat('hh:mm a')
}
+/**
+ * Formats timestamps into readable date
+ * @param {Number} timestamp
+ * @param {String} timezone fixed offset like UTC+6
+ * @returns {String} formatted date (or time if its today)
+ */
export function formatTimeOrDate(timestamp: number, timezone: string): string {
var date = DateTime.fromMillis(timestamp)
- if (timezone === 'UTC')
- date = date.toUTC();
-
+ if (timezone) {
+ if (timezone === 'UTC') date = date.toUTC();
+ date = date.setZone(timezone)
+ }
+
return isToday(date) ? date.toFormat('hh:mm a') : date.toFormat('LLL dd, yyyy, hh:mm a');
}