-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat jalezi/sozial marie banner 466 (#474)
* feat: ✨ adds useTimer custom hook for timer countdown * feat: ✨ adds getTimeDifference function Calculate time difference in days, hours, minutes, and seconds. * feat: ✨ adds useLocalStorage hook * feat: ✨ adds SozialMarie alert * fix: 🚑️ timer does not restart on date change * fix: 🚑️ timer resets to soon * refactor: ♻️ creates VotingButton component Add translations for "votingHasEnded" in en.json and sl.json * refactor: 🌐 Capitalize start and end strings for SozialMarie * refactor: ♻️ Add AlertCountDown component * refactor: ♻️ adds SozialMarieLink component * refactor: ♻️ adds AlertFooterContent component * refactor: ♻️ replace days, hours, min and sec prop with time prop * refactor: 🚚 rename file due to typo * fix: 🐛 not perfect handling of negative time left value * perf: ⚡️ use useCallback hook in SozialMarie component * refactor: ♻️ no need to hold initial time in ref * feat: ✨ show voting date range * refactor: ♻️ adds AlertHeaderContent component * perf: ⚡️ memoize AlertFooterContent * refactor: ♻️ extract date range to separate file * feat: ✨ adds variant prop to AlertCountDown component * refactor: ♻️ handle voting expiration * refactor: ♻️ SozialMarie component to use configurable delay values * fix: 🐛 initial load does not respect localStorage * refactor: 🚸 better remind-me/no-show label * refactor: 🎨 extract "localStorage" related vars and func to separate file * fix: 🚑️ safari & iOS does not support date format and breaks the page new Date("YYYY-MM-DD HH:MM GMT+0200") is "Invalid Date" * refactor: ⚰️ remove unused CountDown component * fix: ♿️ wrong datetime attr value use valid time duration format * refactor: ♻️ adds time constants and import them in relevant files * feat: ✨ adds FullCountDown component * test: 🔧 adds mobile browser tests * test: ✅ adds e2e test for Sozial Marie voting button * refactor: ♻️ duplicate vars * chore: 🧑💻 allow unused vars starting with "_" * feat: ✨ trigger button for SozialMarie will render after specific date The date is set to "Tue Apr 02 2024 00:00:00 GMT+0200" For developing all the dates are configured based on now + delays. env var (.env.development) REACT_APP_SM_SHOW_TRIGGER_BUTTON_IMMEDIATELY=false respects delays. For testing set to true. Don't have better solutions ATM. * feat: ⚗️ show SozialMarie trigger immediately, not on/after specific date * fix: 🚑️ translations prersist on locale change if SM popup is open and user change locale, some translations are not applied due to memoization. * fix: 🚑️ translations prersist on locale change if SM popup is open and user change locale, some translations are not applied due to memoization. * fix: 🐛 new voting date range and voting link * fix: ⚰️ remove about SM text * fix: 🐛 link always points to sl lang --------- Co-authored-by: Štefan Baebler <[email protected]>
- Loading branch information
Showing
24 changed files
with
844 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import PropTypes from 'prop-types'; | ||
import i18n from 'i18next'; | ||
import { getTimeDurationAttrValue } from 'utils'; | ||
|
||
const INTL_LANGS = { | ||
en: 'en-GB', | ||
de: 'de-DE', | ||
sl: 'sl-SI', | ||
hr: 'hr-HR', | ||
it: 'it-IT', | ||
hu: 'hu-HU', | ||
}; | ||
|
||
export const SimpleCountDown = function SimpleCountDown({ days, hours, minutes, seconds }) { | ||
const d = days.toString(); | ||
const h = hours.toString(); | ||
const m = minutes.toString(); | ||
const s = seconds.toString(); | ||
|
||
const timeDuration = getTimeDurationAttrValue({ days, hours, minutes, seconds }); | ||
|
||
return ( | ||
<time dateTime={timeDuration} aria-live="polite"> | ||
{d.padStart(2, '0')}:{h.padStart(2, '0')}:{m.padStart(2, '0')}:{s.padStart(2, '0')} | ||
</time> | ||
); | ||
}; | ||
|
||
SimpleCountDown.propTypes = { | ||
days: PropTypes.number.isRequired, | ||
hours: PropTypes.number.isRequired, | ||
minutes: PropTypes.number.isRequired, | ||
seconds: PropTypes.number.isRequired, | ||
}; | ||
|
||
export const FullCountDown = function FullCountDown({ days, hours, minutes, seconds }) { | ||
const rtf = new Intl.RelativeTimeFormat(INTL_LANGS[i18n.language], { | ||
numeric: 'always', | ||
style: 'narrow', | ||
}); | ||
|
||
const daysParts = rtf.formatToParts(days, 'day'); | ||
const hoursParts = rtf.formatToParts(hours, 'hour'); | ||
const minutesParts = rtf.formatToParts(minutes, 'minute'); | ||
const secondsParts = rtf.formatToParts(seconds, 'second'); | ||
|
||
const value = [daysParts, hoursParts, minutesParts, secondsParts] | ||
.map(part => `${part[1].value} ${part[2].value}`) | ||
.join(', '); | ||
|
||
const timeDuration = getTimeDurationAttrValue({ days, hours, minutes, seconds }); | ||
|
||
return ( | ||
<time dateTime={timeDuration} aria-live="polite"> | ||
{value} | ||
</time> | ||
); | ||
}; | ||
|
||
FullCountDown.propTypes = { | ||
days: PropTypes.number.isRequired, | ||
hours: PropTypes.number.isRequired, | ||
minutes: PropTypes.number.isRequired, | ||
seconds: PropTypes.number.isRequired, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { FullCountDown, SimpleCountDown } from 'components/Shared/CountDown'; | ||
import PropTypes from 'prop-types'; | ||
import { getTimeDifference } from 'utils'; | ||
|
||
const AlertCountDown = function AlertCountDown({ time, variant = 'simple' }) { | ||
const { days, hours, minutes, seconds } = getTimeDifference(time); | ||
if (variant === 'simple') { | ||
return <SimpleCountDown days={days} hours={hours} minutes={minutes} seconds={seconds} />; | ||
} | ||
|
||
return <FullCountDown days={days} hours={hours} minutes={minutes} seconds={seconds} />; | ||
}; | ||
|
||
AlertCountDown.defaultProps = { | ||
variant: 'simple', | ||
}; | ||
|
||
AlertCountDown.propTypes = { | ||
time: PropTypes.number.isRequired, | ||
variant: PropTypes.oneOf(['simple', 'full']), | ||
}; | ||
|
||
export default AlertCountDown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { FormControlLabel, Checkbox } from '@mui/material'; | ||
import { t } from 'i18next'; | ||
import PropTypes from 'prop-types'; | ||
import { memo } from 'react'; | ||
|
||
const AlertFooterContent = function AlertFooter({ checked, handleChecked, isBefore, lang }) { | ||
const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); | ||
const label = isBefore | ||
? sozialMarieTranslations.noShowBefore | ||
: sozialMarieTranslations.noShowDuring; | ||
|
||
return ( | ||
<> | ||
<FormControlLabel | ||
key={lang} | ||
labelPlacement="start" | ||
control={ | ||
<Checkbox name="no-show" checked={checked} onChange={handleChecked} size="small" /> | ||
} | ||
label={label} | ||
sx={{ | ||
marginInline: 0, | ||
'& .MuiFormControlLabel-label': { fontSize: '0.875rem' }, | ||
}} | ||
/> | ||
<p>{sozialMarieTranslations.seeAlert}</p> | ||
</> | ||
); | ||
}; | ||
|
||
AlertFooterContent.propTypes = { | ||
checked: PropTypes.bool.isRequired, | ||
handleChecked: PropTypes.func.isRequired, | ||
isBefore: PropTypes.bool.isRequired, | ||
lang: PropTypes.string.isRequired, | ||
}; | ||
|
||
const areEqual = (prevProps, nextProps) => | ||
prevProps.checked === nextProps.checked && | ||
prevProps.isBefore === nextProps.isBefore && | ||
prevProps.lang === nextProps.lang; | ||
|
||
export default memo(AlertFooterContent, areEqual); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Typography } from '@mui/material'; | ||
import { t } from 'i18next'; | ||
import PropTypes from 'prop-types'; | ||
import { memo } from 'react'; | ||
|
||
import { ONE_DAY_IN_MILLISECONDS } from 'const/time'; | ||
|
||
const INTL_LANGS = { | ||
en: 'en-GB', | ||
de: 'de-DE', | ||
sl: 'sl-SI', | ||
hr: 'hr-HR', | ||
it: 'it-IT', | ||
hu: 'hu-HU', | ||
}; | ||
|
||
function getIntlFormatOptions(dateRangeInMilliseconds) { | ||
if (dateRangeInMilliseconds > ONE_DAY_IN_MILLISECONDS) { | ||
return { | ||
year: 'numeric', | ||
month: 'numeric', | ||
day: 'numeric', | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
}; | ||
} | ||
|
||
// for dev purposes | ||
return { | ||
year: 'numeric', | ||
month: 'numeric', | ||
day: 'numeric', | ||
hour: 'numeric', | ||
minute: 'numeric', | ||
}; | ||
} | ||
|
||
const AlertContentHeader = function AlertContentHeader({ endDate, startDate, lang }) { | ||
const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); | ||
const intlDate = Intl.DateTimeFormat(INTL_LANGS[lang], getIntlFormatOptions(endDate - startDate)); | ||
|
||
const dateRange = intlDate.formatRange(startDate, endDate); | ||
|
||
return ( | ||
<> | ||
<Typography component="h2" fontWeight={600}> | ||
{sozialMarieTranslations.title} | ||
</Typography> | ||
|
||
<Typography | ||
component="time" | ||
dateTime={`${intlDate.format(startDate)}-${intlDate.format(endDate)}`} | ||
fontSize="0.875rem" | ||
> | ||
{dateRange} | ||
</Typography> | ||
</> | ||
); | ||
}; | ||
|
||
AlertContentHeader.propTypes = { | ||
endDate: PropTypes.instanceOf(Date).isRequired, | ||
startDate: PropTypes.instanceOf(Date).isRequired, | ||
lang: PropTypes.string.isRequired, | ||
}; | ||
|
||
const areEqual = (prev, next) => | ||
prev.endDate === next.endDate && prev.startDate === next.startDate && prev.lang === next.lang; | ||
export default memo(AlertContentHeader, areEqual); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { t } from 'i18next'; | ||
import PropTypes from 'prop-types'; | ||
|
||
const SozialMarieLink = function SozialMarieLink({ href }) { | ||
const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); | ||
|
||
return ( | ||
<p> | ||
{sozialMarieTranslations.clicking}{' '} | ||
<a href={href} target="_blank" rel="noopener noreferrer"> | ||
{sozialMarieTranslations.thisLink} | ||
</a>{' '} | ||
{sozialMarieTranslations.inNewTab} | ||
</p> | ||
); | ||
}; | ||
|
||
SozialMarieLink.defaultProps = { | ||
href: '#', | ||
}; | ||
|
||
SozialMarieLink.propTypes = { | ||
href: PropTypes.string, | ||
}; | ||
|
||
export default SozialMarieLink; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Box, Button, Tooltip } from '@mui/material'; | ||
import { SimpleCountDown } from 'components/Shared/CountDown'; | ||
import { t } from 'i18next'; | ||
import PropTypes from 'prop-types'; | ||
import { getTimeDifference } from 'utils'; | ||
|
||
const VotingButton = function VotingButton({ | ||
date, | ||
handleClick, | ||
isBeforeVoting, | ||
isVoting, | ||
isAfterVoting, | ||
time, | ||
}) { | ||
const { days, hours, minutes, seconds } = getTimeDifference(time); | ||
|
||
const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); | ||
|
||
return ( | ||
<Tooltip | ||
title={ | ||
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center"> | ||
<span> | ||
{isBeforeVoting ? `${sozialMarieTranslations.untilVotingStarts}:` : null} | ||
{isVoting ? `${sozialMarieTranslations.untilVotingEnds}:` : null} | ||
{isAfterVoting ? `${sozialMarieTranslations.votingHasEnded}!` : null} | ||
</span> | ||
|
||
{isAfterVoting ? null : ( | ||
<SimpleCountDown | ||
date={date} | ||
days={days} | ||
hours={hours} | ||
minutes={minutes} | ||
seconds={seconds} | ||
/> | ||
)} | ||
</Box> | ||
} | ||
> | ||
<Button type="button" aria-label="vote" onClick={handleClick} color="inherit"> | ||
{sozialMarieTranslations.vote}! | ||
</Button> | ||
</Tooltip> | ||
); | ||
}; | ||
|
||
VotingButton.propTypes = { | ||
date: PropTypes.instanceOf(Date).isRequired, | ||
handleClick: PropTypes.func.isRequired, | ||
isBeforeVoting: PropTypes.bool.isRequired, | ||
isVoting: PropTypes.bool.isRequired, | ||
isAfterVoting: PropTypes.bool.isRequired, | ||
time: PropTypes.number.isRequired, | ||
}; | ||
|
||
export default VotingButton; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { ONE_SECOND_MILLISECONDS } from '../../const/time'; | ||
import { getDevVotingDateRange } from './getDevVotingDateRange'; | ||
|
||
// Safari and iOS don't support the date format 'YYYY-MM-DD HH:MM GMT+0200' https://www.coditty.com/code/javascript-new-date-not-working-on-ie-and-safari | ||
const SM_VOTING_STARTS = 'Tue Apr 08 2024 08:00:00 GMT+0200'; | ||
const SM_VOTING_ENDS = 'Wed Apr 15 2024 23:55:00 GMT+0200'; | ||
const SM_DO_NOT_SHOW_BEFORE = 'Tue Apr 02 2024 00:00:00 GMT+0200'; | ||
|
||
const delayToVotingStart = ONE_SECOND_MILLISECONDS * 5; | ||
const votingTime = ONE_SECOND_MILLISECONDS * 30; | ||
// Test for SozialMarie will fail if this delay is too big. | ||
// For testing purposes set env variable REACT_APP_SM_SHOW_TRIGGER_BUTTON_IMMEDIATELY to true | ||
// It will show the button immediately and you can test the button functionality. | ||
const delayToNotShowBefore = ONE_SECOND_MILLISECONDS * 10; | ||
|
||
const now = new Date(new Date().setMilliseconds(0)); | ||
const dateRange = | ||
process.env.NODE_ENV === 'development' | ||
? getDevVotingDateRange(now, delayToVotingStart, votingTime, delayToNotShowBefore) | ||
: [new Date(SM_VOTING_STARTS), new Date(SM_VOTING_ENDS), new Date(SM_DO_NOT_SHOW_BEFORE)]; | ||
|
||
export const startDate = dateRange[0]; | ||
export const endDate = dateRange[1]; | ||
export const doNotShowBefore = dateRange[2]; |
Oops, something went wrong.