Skip to content

Commit

Permalink
OCT-2060: Add the AW timer back to the app in the new calendar (#528)
Browse files Browse the repository at this point in the history
## Description

## Definition of Done

1. [ ] If required, the desciption of your change is added to the [QA
changelog](https://www.notion.so/octantapp/Changelog-for-the-QA-d96fa3b411cf488bb1d8d9a598d88281)
2. [ ] Acceptance criteria are met.
3. [ ] PR is manually tested before the merge by developer(s).
    - [ ] Happy path is manually checked.
4. [ ] PR is manually tested by QA when their assistance is required
(1).
- [ ] Octant Areas & Test Cases are checked for impact and updated if
required (2).
5. [ ] Unit tests are added unless there is a reason to omit them.
6. [ ] Automated tests are added when required.
7. [ ] The code is merged.
8. [ ] Tech documentation is added / updated, reviewed and approved
(including mandatory approval by a code owner, should such exist for
changed files).
    - [ ] BE: Swagger documentation is updated.
9. [ ] When required by QA:
    - [ ] Deployed to the relevant environment.
    - [ ] Passed system tests.

---

(1) Developer(s) in coordination with QA decide whether it's required.
For small tickets introducing small changes QA assistance is most
probably not required.

(2) [Octant Areas & Test
Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc).
  • Loading branch information
jmikolajczyk authored Oct 23, 2024
2 parents f7df4a5 + 9203483 commit 2121af6
Show file tree
Hide file tree
Showing 12 changed files with 226 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { isAfter, isSameDay, isWithinInterval } from 'date-fns';
import { motion, useMotionValue } from 'framer-motion';
import React, { ReactElement, useCallback, useEffect, useRef } from 'react';
import React, { FC, useCallback, useEffect, useRef } from 'react';

import CalendarItem from 'components/shared/Layout/LayoutTopBarCalendar/CalendarItem';
import getMilestones, { Milestone } from 'constants/milestones';
import useMediaQuery from 'hooks/helpers/useMediaQuery';

import styles from './Calendar.module.scss';
import CalendarProps from './types';

let isInitialResizeDone = false;

const Calendar = (): ReactElement => {
const Calendar: FC<CalendarProps> = ({ showAWAlert, durationToChangeAWInMinutes }) => {
const constraintsRef = useRef<HTMLDivElement>(null);
const milestonesWrapperRef = useRef<HTMLDivElement>(null);
const { isMobile } = useMediaQuery();
Expand All @@ -31,11 +32,15 @@ const Calendar = (): ReactElement => {
isAfter(curr.from, currentDate) &&
!acc.some(element => element.isActive));

acc.push({ ...curr, isActive });
acc.push({
...curr,
isActive,
isAlert: isActive && showAWAlert && curr.isAllocationWindowMilestone,
});

return acc;
},
[] as (Milestone & { isActive: boolean })[],
[] as (Milestone & { isActive: boolean; isAlert?: boolean })[],
);

const setMotionValue = useCallback(() => {
Expand Down Expand Up @@ -102,7 +107,12 @@ const Calendar = (): ReactElement => {
style={isMobile ? { y } : { x }}
>
{milestonesWithIsActive.map(({ id, ...milestone }) => (
<CalendarItem key={id} id={id} {...milestone} />
<CalendarItem
key={id}
id={id}
{...milestone}
durationToChangeAWInMinutes={durationToChangeAWInMinutes}
/>
))}
</motion.div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface CalendarProps {
durationToChangeAWInMinutes: number;
showAWAlert?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
&.isActive {
background-color: $color-octant-green5;

&:hover {
background-color: $color-octant-green11;
}

.day,
.monthShort,
.label,
Expand All @@ -71,6 +75,29 @@
}
}

&.isActive.isAlert {
background-color: $color-octant-orange9;

&:hover {
background-color: $color-octant-orange10;
}

.day,
.monthShort,
.label,
.date {
color: $color-octant-orange;
}

.arrow {
svg,
path {
stroke: $color-octant-orange;
fill: $color-octant-orange;
}
}
}

&.isInView {
opacity: 1;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable jsx-a11y/mouse-events-have-key-events */
import cx from 'classnames';
import { format } from 'date-fns';
import { useInView } from 'framer-motion';
import React, { FC, useRef, useState } from 'react';
import React, { FC, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

import Svg from 'components/ui/Svg';
Expand All @@ -18,11 +19,46 @@ const CalendarItem: FC<CalendarItemProps> = ({
isActive,
href,
shouldUseThirdPersonSingularVerb,
isAlert,
durationToChangeAWInMinutes,
}) => {
const { i18n } = useTranslation();
const { t, i18n } = useTranslation('translation', { keyPrefix: 'layout.topBar' });
const ref = useRef(null);
const isInView = useInView(ref, { amount: 'all' });
const [initialClientX, setInitialClientX] = useState<number | null>(null);
const [isHovered, setIsHovered] = useState(false);

const date = useMemo(() => {
if (to) {
let dateFormat = `${format(to, 'dd MMMM haaa')} CET`;

if (isAlert && isHovered) {
const durationToChangeAWInHours = Math.floor(durationToChangeAWInMinutes / 60);
const durationHours = `${durationToChangeAWInHours} ${t('hours', { count: durationToChangeAWInHours })}`;

const durationMinutes = `${durationToChangeAWInMinutes} ${t('minutes', { count: durationToChangeAWInMinutes })}`;

const duration = durationToChangeAWInMinutes <= 60 ? durationMinutes : durationHours;

dateFormat = `${t('in')} ${duration}`;
}

if (shouldUseThirdPersonSingularVerb) {
return `${i18n.t('common.closes')} ${dateFormat}`;
}
return `${i18n.t('common.close')} ${dateFormat}`;
}

return `${format(from, 'haaa')} CET`;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
// eslint-disable-next-line react-hooks/exhaustive-deps
to?.getTime(),
isAlert,
isHovered,
durationToChangeAWInMinutes,
shouldUseThirdPersonSingularVerb,
]);

return (
<div
Expand All @@ -32,6 +68,7 @@ const CalendarItem: FC<CalendarItemProps> = ({
isActive && styles.isActive,
isInView && styles.isInView,
href && styles.hasHref,
isAlert && styles.isAlert,
)}
data-test="CalendarItem"
id={id}
Expand All @@ -41,6 +78,8 @@ const CalendarItem: FC<CalendarItemProps> = ({
}
setInitialClientX(e.clientX);
}}
onMouseLeave={() => setIsHovered(false)}
onMouseOver={() => setIsHovered(true)}
onMouseUp={e => {
if (!href) {
return;
Expand Down Expand Up @@ -69,11 +108,7 @@ const CalendarItem: FC<CalendarItemProps> = ({
/>
)}
</div>
<div className={styles.date}>
{to
? `${i18n.t(shouldUseThirdPersonSingularVerb ? 'common.closes' : 'common.close')} ${format(to, 'dd MMMM haaa')} CET`
: `${format(from, 'haaa')} CET`}
</div>
<div className={styles.date}>{date}</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
export default interface CalendarItemProps {
durationToChangeAWInMinutes: number;
from: Date;
href?: string;
id: string;
isActive: boolean;
isAlert?: boolean;
label: string;
shouldUseThirdPersonSingularVerb?: boolean;
to?: Date;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,30 @@
color: $color-octant-green;
border-radius: $border-radius-16;
cursor: pointer;
border: 0.2rem solid transparent;

&:hover {
border-color: $color-octant-green10;
}

.calendarIcon {
margin-right: 1.2rem;
}

&.showAWAlert {
background: $color-octant-orange8;
color: $color-octant-orange;

.calendarIcon {
path {
stroke: $color-octant-orange;
}
}

&:hover {
border-color: $color-octant-orange7;
}
}
}

.desktopCalendarWrapper {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import cx from 'classnames';
import { differenceInMinutes } from 'date-fns';
import { motion, AnimatePresence } from 'framer-motion';
import React, { ReactElement, useMemo, useState } from 'react';
import React, { ReactElement, useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';

Expand All @@ -10,6 +11,7 @@ import Svg from 'components/ui/Svg';
import useMediaQuery from 'hooks/helpers/useMediaQuery';
import useCurrentEpoch from 'hooks/queries/useCurrentEpoch';
import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen';
import useEpochsStartEndTime from 'hooks/subgraph/useEpochsStartEndTime';
import { calendar } from 'svg/misc';

import styles from './LayoutTopBarCalendar.module.scss';
Expand All @@ -19,27 +21,98 @@ const LayoutTopBarCalendar = (): ReactElement => {
const { isMobile } = useMediaQuery();
const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen();
const { data: currentEpoch } = useCurrentEpoch();

const { data: epochsStartEndTime } = useEpochsStartEndTime();
const [durationToChangeAWInMinutes, setDurationToChangeAWInMinutes] = useState(0);
const [showAWAlert, setShowAWAlert] = useState(false);
const [isCalendarOpen, setIsCalendarOpen] = useState(false);

const allocationInfoText = useMemo(() => {
const epoch = currentEpoch! - 1;

const durationMinutes = `${durationToChangeAWInMinutes}${isMobile ? `${t('minutesShort')}` : ` ${t('minutes', { count: durationToChangeAWInMinutes })}`}`;

const durationToChangeAWInHours = Math.floor(durationToChangeAWInMinutes / 60);
const durationHours = `${durationToChangeAWInHours}${isMobile ? `${t('hoursShort')}` : ` ${t('hours', { count: durationToChangeAWInHours })}`}`;

const durationToChangeAWInDays = Math.ceil(durationToChangeAWInHours / 24);
const durationDays = `${durationToChangeAWInDays} ${t('days', { count: durationToChangeAWInDays })}`;

let duration = durationDays;

if (durationToChangeAWInHours <= 24) {
duration = durationHours;
}
if (durationToChangeAWInMinutes <= 60) {
duration = durationMinutes;
}

if (isDecisionWindowOpen) {
if (showAWAlert) {
return isMobile
? t('epochAllocationClosesInShort', { duration })
: t('epochAllocationClosesIn', {
duration,
epoch,
});
}

return isMobile
? t('epochAllocationWindowOpenShort', { epoch })
: t('epochAllocationWindowOpen', { epoch });
}

return isMobile
? t('epochAllocationWindowClosedShort', { epoch })
: t('epochAllocationWindowClosed', { epoch });
if (isMobile) {
return t('epochAllocationOpensInShort', { duration });
}
return t('epochAllocationOpensIn', { duration, epoch });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDecisionWindowOpen, currentEpoch, isMobile, durationToChangeAWInMinutes, showAWAlert]);

useEffect(() => {
if (!epochsStartEndTime || isDecisionWindowOpen === undefined) {
return;
}

const epochData = epochsStartEndTime[currentEpoch! - 1];
const allocationWindowEndTimestamp =
(parseInt(epochData?.fromTs, 10) + parseInt(epochData.decisionWindow, 10)) * 1000;
const nextAllocationWindowStartTimestamp =
(parseInt(epochData?.toTs, 10) + parseInt(epochData.decisionWindow, 10)) * 1000;

const setNextDuration = () => {
const minutes =
Math.abs(
differenceInMinutes(
isDecisionWindowOpen
? allocationWindowEndTimestamp
: nextAllocationWindowStartTimestamp,
new Date(),
),
) || 1;

setDurationToChangeAWInMinutes(minutes);

setShowAWAlert(isDecisionWindowOpen && minutes <= 24 * 60);
};

setNextDuration();

const intervalId = setInterval(setNextDuration, 60 * 1000);

return () => {
clearInterval(intervalId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDecisionWindowOpen, currentEpoch, isMobile]);
}, [!!epochsStartEndTime, isDecisionWindowOpen]);

const calendarProps = { durationToChangeAWInMinutes, showAWAlert };

return (
<>
<div className={styles.allocationInfo} onClick={() => setIsCalendarOpen(true)}>
<div
className={cx(styles.allocationInfo, showAWAlert && styles.showAWAlert)}
onClick={() => setIsCalendarOpen(true)}
>
{!isMobile && <Svg classNameSvg={styles.calendarIcon} img={calendar} size={1.6} />}
{allocationInfoText}
</div>
Expand All @@ -64,7 +137,7 @@ const LayoutTopBarCalendar = (): ReactElement => {
exit={{ opacity: 0 }}
initial={{ left: '50%', opacity: 0, top: 64, x: '-50%' }}
>
<Calendar />
<Calendar {...calendarProps} />
</motion.div>
</>
)}
Expand All @@ -76,7 +149,7 @@ const LayoutTopBarCalendar = (): ReactElement => {
isOpen={isMobile && isCalendarOpen}
onClosePanel={() => setIsCalendarOpen(false)}
>
<Calendar />
<Calendar {...calendarProps} />
</Modal>
</>
);
Expand Down
Loading

0 comments on commit 2121af6

Please sign in to comment.