diff --git a/.env.development b/.env.development index 2e1a73bf..487b30f8 100644 --- a/.env.development +++ b/.env.development @@ -18,5 +18,3 @@ REACT_APP_GOOGLE_FORM_INPUT_PHONE=1724437941 REACT_APP_GOOGLE_FORM_INPUT_EMAIL= REACT_APP_GOOGLE_FORM_INPUT_ORDERFORM= REACT_APP_GOOGLE_FORM_INPUT_NOTE=1714314704 -# SozialMarie set to true when running tests -REACT_APP_SM_SHOW_TRIGGER_BUTTON_IMMEDIATELY=true diff --git a/.env.production b/.env.production index 0533a795..fcebe8ea 100644 --- a/.env.production +++ b/.env.production @@ -17,5 +17,3 @@ REACT_APP_GOOGLE_FORM_INPUT_PHONE=1724437941 REACT_APP_GOOGLE_FORM_INPUT_EMAIL=1408261095 REACT_APP_GOOGLE_FORM_INPUT_ORDERFORM=1910910180 REACT_APP_GOOGLE_FORM_INPUT_NOTE=1714314704 -# SozialMarie, when going live (before 2024-4-2), change to false, otherwise it doesn't matter -REACT_APP_SM_SHOW_TRIGGER_BUTTON_IMMEDIATELY=true diff --git a/.eslintrc.js b/.eslintrc.js index 1db9ead2..caa9c85e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,14 +26,6 @@ module.exports = { // @TODO: These should be turned "ON" one by one 'react/jsx-props-no-spreading': 'warn', - 'no-unused-vars': [ - 'error', // or "error" - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], }, settings: { 'import/resolver': { diff --git a/playwright.config.js b/playwright.config.js index 23ec9338..bfb679e8 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -50,14 +50,14 @@ module.exports = defineConfig({ }, /* Test against mobile viewports. */ - { - name: 'Mobile Chrome', - use: { ...devices['Pixel 5'] }, - }, - { - name: 'Mobile Safari', - use: { ...devices['iPhone 12'] }, - }, + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, /* Test against branded browsers. */ // { diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 85fd5d72..7ed76391 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -4,7 +4,6 @@ import { IconButton, TextField, Toolbar } from '@mui/material'; import * as Icons from 'components/Shared/Icons'; import i18next, { languages } from 'i18n'; import { useFilter } from 'context/filterContext'; -import SozialMarie from 'components/SozialMarie'; import TemporaryDrawer from './Drawer'; import NavLinks from './NavLinks'; import SocialLinks from './SocialLinks'; @@ -69,7 +68,6 @@ const Header = function Header() { > - diff --git a/src/components/Shared/CountDown/index.jsx b/src/components/Shared/CountDown/index.jsx deleted file mode 100644 index afe92ce2..00000000 --- a/src/components/Shared/CountDown/index.jsx +++ /dev/null @@ -1,65 +0,0 @@ -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 ( - - ); -}; - -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 ( - - ); -}; - -FullCountDown.propTypes = { - days: PropTypes.number.isRequired, - hours: PropTypes.number.isRequired, - minutes: PropTypes.number.isRequired, - seconds: PropTypes.number.isRequired, -}; diff --git a/src/components/Shared/ExpandMore.js b/src/components/Shared/ExpandMore.js index d3dd4b3a..3d5ea9a6 100644 --- a/src/components/Shared/ExpandMore.js +++ b/src/components/Shared/ExpandMore.js @@ -3,7 +3,7 @@ import IconButton from '@mui/material/IconButton'; import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material'; import PropTypes from 'prop-types'; -const ExpandMoreButton = styled(({ _expand, ...other }) => )( +const ExpandMoreButton = styled(({ expand, ...other }) => )( ({ theme, expand }) => ({ transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)', marginLeft: 'auto', diff --git a/src/components/SozialMarie/AlertCountDown.jsx b/src/components/SozialMarie/AlertCountDown.jsx deleted file mode 100644 index 417abda5..00000000 --- a/src/components/SozialMarie/AlertCountDown.jsx +++ /dev/null @@ -1,23 +0,0 @@ -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 ; - } - - return ; -}; - -AlertCountDown.defaultProps = { - variant: 'simple', -}; - -AlertCountDown.propTypes = { - time: PropTypes.number.isRequired, - variant: PropTypes.oneOf(['simple', 'full']), -}; - -export default AlertCountDown; diff --git a/src/components/SozialMarie/AlertFooterContent.jsx b/src/components/SozialMarie/AlertFooterContent.jsx deleted file mode 100644 index 78926177..00000000 --- a/src/components/SozialMarie/AlertFooterContent.jsx +++ /dev/null @@ -1,43 +0,0 @@ -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 ( - <> - - } - label={label} - sx={{ - marginInline: 0, - '& .MuiFormControlLabel-label': { fontSize: '0.875rem' }, - }} - /> -

{sozialMarieTranslations.seeAlert}

- - ); -}; - -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); diff --git a/src/components/SozialMarie/AlertHeaderContent.jsx b/src/components/SozialMarie/AlertHeaderContent.jsx deleted file mode 100644 index bf00ae87..00000000 --- a/src/components/SozialMarie/AlertHeaderContent.jsx +++ /dev/null @@ -1,69 +0,0 @@ -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 ( - <> - - {sozialMarieTranslations.title} - - - - {dateRange} - - - ); -}; - -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); diff --git a/src/components/SozialMarie/SozialMarieLink.jsx b/src/components/SozialMarie/SozialMarieLink.jsx deleted file mode 100644 index 938b4120..00000000 --- a/src/components/SozialMarie/SozialMarieLink.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import { t } from 'i18next'; -import PropTypes from 'prop-types'; - -const SozialMarieLink = function SozialMarieLink({ href }) { - const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); - - return ( -

- {sozialMarieTranslations.clicking}{' '} - - {sozialMarieTranslations.thisLink} - {' '} - {sozialMarieTranslations.inNewTab} -

- ); -}; - -SozialMarieLink.defaultProps = { - href: '#', -}; - -SozialMarieLink.propTypes = { - href: PropTypes.string, -}; - -export default SozialMarieLink; diff --git a/src/components/SozialMarie/VotingButton.jsx b/src/components/SozialMarie/VotingButton.jsx deleted file mode 100644 index 26be1a59..00000000 --- a/src/components/SozialMarie/VotingButton.jsx +++ /dev/null @@ -1,57 +0,0 @@ -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 ( - - - {isBeforeVoting ? `${sozialMarieTranslations.untilVotingStarts}:` : null} - {isVoting ? `${sozialMarieTranslations.untilVotingEnds}:` : null} - {isAfterVoting ? `${sozialMarieTranslations.votingHasEnded}!` : null} - - - {isAfterVoting ? null : ( - - )} - - } - > - - - ); -}; - -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; diff --git a/src/components/SozialMarie/date-range.js b/src/components/SozialMarie/date-range.js deleted file mode 100644 index afdf6c40..00000000 --- a/src/components/SozialMarie/date-range.js +++ /dev/null @@ -1,24 +0,0 @@ -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]; diff --git a/src/components/SozialMarie/getDevVotingDateRange.js b/src/components/SozialMarie/getDevVotingDateRange.js deleted file mode 100644 index 46d930d1..00000000 --- a/src/components/SozialMarie/getDevVotingDateRange.js +++ /dev/null @@ -1,25 +0,0 @@ -import { addMilliseconds } from 'utils'; - -export function getDevVotingDateRange( - now = new Date(), - startDelay = 5000, - addToEndDelay = 5000, - doNotShowBeforeDelay = 5000, -) { - if (!(now instanceof Date)) { - throw new TypeError('The now parameter must be a Date object.'); - } - - if (typeof startDelay !== 'number' || startDelay < 0) { - throw new TypeError('The startDelay parameter must be a non-negative number.'); - } - - if (typeof addToEndDelay !== 'number' || addToEndDelay < 0) { - throw new TypeError('The endDelay parameter must be a non-negative number.'); - } - const noShow = addMilliseconds(now, doNotShowBeforeDelay); - const starts = addMilliseconds(noShow, startDelay); - const ends = addMilliseconds(starts, addToEndDelay); - - return [starts, ends, noShow]; -} diff --git a/src/components/SozialMarie/index.jsx b/src/components/SozialMarie/index.jsx deleted file mode 100644 index 8fd939da..00000000 --- a/src/components/SozialMarie/index.jsx +++ /dev/null @@ -1,207 +0,0 @@ -import useTimer from 'hooks/useTimer'; -import { Alert, Box, Divider, Snackbar, Stack } from '@mui/material'; -import { useLocalStorage } from 'hooks'; -import { useEffect, useState, useCallback, useRef } from 'react'; -import i18n, { t } from 'i18next'; -import VotingButton from './VotingButton'; -import AlertCountDown from './AlertCountDown'; -import SozialMarieLink from './SozialMarieLink'; -import AlertFooterContent from './AlertFooterContent'; -import AlertContentHeader from './AlertHeaderContent'; -import { startDate, endDate, doNotShowBefore } from './date-range'; -import { LOCAL_STORAGE_KEY, LOCAL_STORAGE_VALUES, getInitialIsShow } from './localStorage'; - -const DELAY_TO_HIDE_ALERT = 5000; -const DELAY_TO_HIDE_TRIGGER = 6000; - -if (DELAY_TO_HIDE_ALERT > DELAY_TO_HIDE_TRIGGER) { - throw new Error('DELAY_TO_HIDE_ALERT should be less than DELAY_TO_HIDE_TRIGGER'); -} - -const SozialMarieBase = function SozialMarieBase() { - const SOZIAL_MARIE_LINK = `https://www.sozialmarie.org/${i18n.language === 'it' ? 'en' : i18n.language}/projects/9280/`; - const currentDate = new Date(); - const countDownDate = currentDate < startDate ? startDate : endDate; - const isVoting = currentDate >= startDate && currentDate < endDate; - const isBefore = currentDate < startDate; - const isAfter = currentDate >= endDate; - const roundedInitialTime = Math.floor((countDownDate - currentDate) / 1000) * 1000; - const [timeLeft, setTimeLeft] = useTimer(roundedInitialTime); - - const [localStorageVal, updateLocalstorageVal] = useLocalStorage(LOCAL_STORAGE_KEY, 'first'); - const isShow = getInitialIsShow(localStorageVal, { isBefore, isVoting }); - const [open, setOpen] = useState(isShow); - const [noShowChecked, setNoShowChecked] = useState(!isShow); - - const [votingExpired, setVotingExpired] = useState(false); - - const sozialMarieTranslations = t('sozialMarie', { returnObjects: true }); - - if (isVoting && localStorageVal === LOCAL_STORAGE_VALUES.remindMe) { - updateLocalstorageVal(LOCAL_STORAGE_VALUES.noShow); - } - - useEffect(() => { - if (isVoting) { - setTimeLeft(Math.floor((endDate - new Date()) / 1000) * 1000); - } - }, [isVoting, timeLeft, setTimeLeft]); - - useEffect(() => { - let timeoutId; - if (isAfter) { - timeoutId = setTimeout(() => { - const hasItem = !!localStorage.getItem(LOCAL_STORAGE_KEY); - if (hasItem) { - localStorage.removeItem(LOCAL_STORAGE_KEY); - } - setOpen(false); - }, DELAY_TO_HIDE_ALERT); - } - - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, [isAfter]); - - useEffect(() => { - let timeoutId; - if (isAfter) { - timeoutId = setTimeout(() => { - setVotingExpired(true); - }, DELAY_TO_HIDE_TRIGGER); - } - - return () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - }; - }, [isAfter]); - - const handleClick = useCallback(() => { - setOpen(true); - }, []); - - const handleClose = (_event, reason) => { - if (reason === 'clickaway') { - return; - } - - setOpen(false); - }; - - const handleChecked = useCallback( - e => { - const localstorageCheckedValue = isBefore - ? LOCAL_STORAGE_VALUES.remindMe - : LOCAL_STORAGE_VALUES.noShow; - updateLocalstorageVal( - e.target.checked ? localstorageCheckedValue : LOCAL_STORAGE_VALUES.show, - ); - setNoShowChecked(e.target.checked); - }, - [updateLocalstorageVal, isBefore], - ); - - if (votingExpired) { - return null; - } - - return ( - - - - - - - - - - - {sozialMarieTranslations.votingFor} - {isBefore ? ` ${sozialMarieTranslations.start}` : null} - {isVoting ? ` ${sozialMarieTranslations.end}` : null}: - - - {isAfter ? ( - `${sozialMarieTranslations.votingHasEnded}!` - ) : ( - - )} - - {isAfter ? null : ( - <> - - - - - - - )} - - - - ); -}; - -const envRespectDates = process.env?.REACT_APP_SM_SHOW_TRIGGER_BUTTON_IMMEDIATELY; -const donNotRespectDates = envRespectDates ? JSON.parse(envRespectDates) : false; - -const SozialMarie = function SozialMarie() { - const now = new Date(); - - const [showSozialMarie, setShowSozialMarie] = useState( - (doNotShowBefore < now && now < endDate) || Boolean(donNotRespectDates), - ); - - const timeoutIdRef = useRef(); - - useEffect(() => { - const timeoutId = timeoutIdRef?.current; - if (timeoutId) { - clearTimeout(timeoutId); - } - if (!showSozialMarie) { - timeoutIdRef.current = setTimeout(() => { - setShowSozialMarie(true); - }, doNotShowBefore - new Date()); - } - return () => { - clearTimeout(timeoutId); - }; - }, [showSozialMarie]); - return showSozialMarie ? : null; -}; - -export default SozialMarie; diff --git a/src/components/SozialMarie/localStorage.js b/src/components/SozialMarie/localStorage.js deleted file mode 100644 index 120a0f58..00000000 --- a/src/components/SozialMarie/localStorage.js +++ /dev/null @@ -1,58 +0,0 @@ -export const LOCAL_STORAGE_KEY = 'showSozialMarie'; - -/** - * @typedef {Object} LocalStorageValues - * @property {"first"} first - The value for the 'first' key in local storage. - * @property {"show"} show - The value for the 'show' key in local storage. - * @property {"remind-me"} remindMe - The value for the 'remind-me' key in local storage. - * @property {"no-show"} noShow - The value for the 'no-show' key in local storage. - */ - -/** @type {LocalStorageValues} */ -export const LOCAL_STORAGE_VALUES = { - first: 'first', - show: 'show', - remindMe: 'remind-me', - noShow: 'no-show', -}; - -/** - * @typedef {LocalStorageValues[keyof LocalStorageValues]} LocalStorageValue - */ - -/** - * Determines the initial value of the "isShow" flag based on the provided value and options. - * - * Always returns true if the value is "first" or "show". - * - * Before voting starts, returns false if the value is "remind-me" or "no-show". - * - * During voting, returns false if the value is "no-show" and true if the value is "remind-me". - * - * Othervise returns false. - * - * @param {LocalStorageValue} value - The value to check against. - * @param {Object} options - The options object. - * @param {boolean} options.isBefore - Indicates if it is before a certain event. - * @param {boolean} options.isVoting - Indicates if it is during a voting period. - * @returns {boolean} - The initial value of the "isShow" flag. - */ -export function getInitialIsShow(value, { isBefore, isVoting }) { - if ([LOCAL_STORAGE_VALUES.first, LOCAL_STORAGE_VALUES.show].includes(value)) { - return true; - } - - if (isBefore && [LOCAL_STORAGE_VALUES.remindMe, LOCAL_STORAGE_VALUES.noShow].includes(value)) { - return false; - } - - if (isVoting && value === LOCAL_STORAGE_VALUES.noShow) { - return false; - } - - if (isVoting && value === LOCAL_STORAGE_VALUES.remindMe) { - return true; - } - - return false; -} diff --git a/src/const/time.js b/src/const/time.js deleted file mode 100644 index dfbb5907..00000000 --- a/src/const/time.js +++ /dev/null @@ -1,4 +0,0 @@ -export const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000; -export const ONE_HOUR_IN_MILLISECONDS = 60 * 60 * 1000; -export const ONE_MINUTE_IN_MILLISECONDS = 60 * 1000; -export const ONE_SECOND_MILLISECONDS = 1000; diff --git a/src/hooks/index.js b/src/hooks/index.js index a6472a16..6359d5ce 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -3,5 +3,3 @@ export { default as useTimeout } from './useTimeout'; export { default as useDebounce } from './useDebounce'; export { default as useGeoLocation } from './useGeoLocation'; export { default as useEventListener } from './useEventListener'; -export { default as useTimer } from './useTimer'; -export { default as useLocalStorage } from './useLocalStorage'; diff --git a/src/hooks/useLocalStorage.js b/src/hooks/useLocalStorage.js deleted file mode 100644 index 2ca33d7a..00000000 --- a/src/hooks/useLocalStorage.js +++ /dev/null @@ -1,26 +0,0 @@ -import { useState } from 'react'; - -/** - * Custom hook to store and retrieve values in local storage. - * - * @param {string} key - The key to use for storing the value in local storage. - * @param {*} initialValue - The initial value to use if no value is found in local storage. - * @returns {Array} - An array containing the current value and a function to update the value. - */ -function useLocalStorage(key, initialValue) { - // Get stored value from local storage or use initial value - const storedValue = JSON.parse(localStorage.getItem(key)) || initialValue; - - // State to hold the current value - const [value, setValue] = useState(storedValue); - - // Update local storage and state when the value changes - const updateValue = newValue => { - setValue(newValue); - localStorage.setItem(key, JSON.stringify(newValue)); - }; - - return [value, updateValue]; -} - -export default useLocalStorage; diff --git a/src/hooks/useTimer.js b/src/hooks/useTimer.js deleted file mode 100644 index b8121f0a..00000000 --- a/src/hooks/useTimer.js +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; - -/** - * Custom hook for a timer. - * - * @param {number} initialTime - The initial time for the timer in milliseconds. - * @returns {number} - The time left in milliseconds. - */ -export default function useTimer(initialTime) { - const [timeLeft, setTimeLeft] = useState(initialTime); - - const intervalIdRef = useRef(null); - - const isTimeLeftValid = timeLeft >= 0; - - useEffect(() => { - let intervalId = intervalIdRef.current; - const handleTimer = () => { - setTimeLeft(prevTimeLeft => prevTimeLeft - 1000); - }; - - intervalId = isTimeLeftValid ? setInterval(handleTimer, 1000) : null; - - return () => { - if (intervalId) { - clearInterval(intervalId); - intervalIdRef.current = null; - } - }; - }, [isTimeLeftValid]); - - if (timeLeft < 0) { - clearInterval(intervalIdRef.current); - } - - return [timeLeft, setTimeLeft]; -} diff --git a/src/locales/en.json b/src/locales/en.json index be7291ce..64d5719a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -99,37 +99,7 @@ "datetime": "{{val, datetime}}", "at": "at" }, - "sozialMarie": { - "title": "SozialMarie Award", - "description": "The Doctors Tracker portal was created to bring the healthcare system closer to the patient. In Slovenia, we are facing a shortage of doctors at primary level. This leaves many patients without the basic right of access to a personal GP. In the Scientific association Tracker, we imported the data that was publicly available every 15 days in a spreadsheet that was not user friendly into our portal and allowed users to suggest corrections for faulty data in real time. This gave patients seeking primary care user-friendly and up-to-date information.", - "start": "STARTS in", - "end": "ENDS in", - "votingFor": "Voting for the SozialMarie award", - "and": "and", - "noShowBefore": "Don't show this again until the voting starts", - "noShowDuring": "Don't show this message again", - "clicking": "By clicking on", - "thisLink": "this link", - "inNewTab": "you will be redirected to voting page. Page will open in a new tab.", - "seeAlert": "Clicking on button \"Vote!\" you can see this message.", - "vote": "Vote", - "untilVotingStarts": "until voting starts", - "untilVotingEnds": "until voting ends", - "aboutSozialMarie": "some info about SozialMarie", - "votingHasEnded": "Voting has ended" - }, - "time": { - "day_one": "day", - "day_two": "days", - "day_other": "days", - "hour_one": "hour", - "hour_two": "hours", - "hour_other": "hours", - "minute_one": "minute", - "minute_two": "minutes", - "minute_other": "minutes", - "second_one": "second", - "second_two": "seconds", - "second_other": "seconds" + "sozialmarie": { + "description": "The Doctors Tracker portal was created to bring the healthcare system closer to the patient. In Slovenia, we are facing a shortage of doctors at primary level. This leaves many patients without the basic right of access to a personal GP. In the Scientific association Tracker, we imported the data that was publicly available every 15 days in a spreadsheet that was not user friendly into our portal and allowed users to suggest corrections for faulty data in real time. This gave patients seeking primary care user-friendly and up-to-date information." } -} \ No newline at end of file +} diff --git a/src/locales/sl.json b/src/locales/sl.json index 54a5127b..c472a4b0 100644 --- a/src/locales/sl.json +++ b/src/locales/sl.json @@ -101,41 +101,7 @@ "datetime": "{{val, datetime}}", "at": "ob" }, - "sozialMarie": { - "title": "SozialMarie nagrada", - "description": "Portal Zdravniki Sledilnik je bil ustvarjen z namenom približevanja oskrbe k pacientu. V Sloveniji se soočamo s pomanjkanjem zdravnikov na primarnem nivoju. Veliko pacientov je zaradi tega brez osnovne pravice dostopa do opredelitve osebnega zdravnika. V Znanstvenem društvu Sledilnik smo podatke, ki so se javno objavljali na 15 dni v nepregledni razpredelnici uvozili v naš portal ter omogočali uporabnikom, da podatke posodabljajo v realnem času. Tako smo pacientom, ki so iskali oskrbo na primarnem nivoju omogočili prijazno uporabniško informacijo ter ažurne informacije.", - "start": "PRIČNE čez", - "end": "KONČA čez", - "votingFor": "Glasovanje za SozialMarie nagrado se", - "and": "in", - "noShowBefore": "Ne pokaži tega obvestila do začetka glasovanje", - "noShowDuring": "Ne pokaži več tega obvestila", - "clicking": "S klikom na", - "thisLink": "to povezavo", - "inNewTab": "boste preusmerjeni na stran za glasovanje. Stran se bo odprla v novem zavihku.", - "seeAlert": "S klikom na na gumb \"Glasuj!\" lahko vedno vidite to obvestilo.", - "vote": "Glasuj", - "untilVotingStarts": "do začetka glasovanja", - "untilVotingEnds": "do konca glasovanja", - "aboutSozialMarie": "info o nagradi SozialMarie", - "votingHasEnded": "Glasovanje je končano!" - }, - "time": { - "day_one": "dan", - "day_two": "dneva", - "day_few": "dni", - "day_other": "dni", - "hour_one": "uro", - "hour_two": "uri", - "hour_few": "ure", - "hour_other": "ur", - "minute_one": "minuta", - "minute_two": "minuti", - "minute_few": "minute", - "minute_other": "minut", - "second_one": "sekunda", - "second_two": "sekundi", - "second_few": "sekunde", - "second_other": "sekund" + "sozialmarie": { + "description": "Portal Zdravniki Sledilnik je bil ustvarjen z namenom približevanja osrkbe k pacientu. V Sloveniji se soočamo s pomankanjem zdravnikov na primarnem nivoju. Veliko pacientov je zaradi tega brez osnovne pravice dostopa do opredelitve osebnega zdravnika. V Znanstvenem društvu Sledilnik smo podatke, ki so se javno objavljali na 15 dni v nepregledni razpredelnici uvozili v naš portal ter omogočali uporabnikom, da podatke posodabljajo v realnem času. Tako smo pacientom, ki so iskali oskrbo na primarnem nivoju omogočili prijazno uporabniško informacijo ter ažurne informacije." } -} \ No newline at end of file +} diff --git a/src/tests/e2e/sozial-marie.spec.js b/src/tests/e2e/sozial-marie.spec.js deleted file mode 100644 index 6e3a9489..00000000 --- a/src/tests/e2e/sozial-marie.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import { expect, test } from '@playwright/test'; - -// This date and delay are hardcoded in the app and should be updated manually -// from src/components/SozialMarie/date-range.js -const SM_VOTING_ENDS = 'Wed Apr 17 2024 00:00:00 GMT+0200'; -const delayToNotShowBefore = 0; - -test.describe('Sozial Marie', () => { - const votingEnds = new Date(SM_VOTING_ENDS); - const doNotRunAnyTests = new Date() > votingEnds; - test.skip(doNotRunAnyTests, 'Voting is expired'); - test.beforeEach(async ({ page }) => { - await page.goto('/'); - }); - - test.describe('Voting', () => { - // https://playwright.dev/docs/test-annotations#conditionally-skip-a-test - test.skip(new Date() > votingEnds, 'Voting is expired'); - test('has voting button', async ({ page }) => { - await page.reload(); - await page.waitForTimeout(delayToNotShowBefore); - await expect(page.getByLabel('vote')).toBeVisible(); - }); - }); -}); diff --git a/src/utils/index.js b/src/utils/index.js index 279ca8b7..05021f83 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,13 +1,6 @@ import L from 'leaflet'; import { v4 as uuidv4 } from 'uuid'; -import { - ONE_DAY_IN_MILLISECONDS, - ONE_HOUR_IN_MILLISECONDS, - ONE_MINUTE_IN_MILLISECONDS, - ONE_SECOND_MILLISECONDS, -} from 'const/time'; - function normalize(value) { // Replace all non ASCII chars and replace them with closest equivalent (č => c) return value @@ -81,56 +74,3 @@ export function filterBySearchValueInMapBounds({ searchValue = '', filtered = [] ); }); } - -/** - * @typedef {Object} TimeDifference - * @property {number} days - The time difference in days. - * @property {number} hours - The time difference in hours. - * @property {number} minutes - The time difference in minutes. - * @property {number} seconds - The time difference in seconds. - */ - -/** - * Calculates the time difference in days, hours, minutes, and seconds. - * - * @param {number} diff - The time difference in milliseconds. - * @returns {TimeDifference} - An object containing the time difference in days, hours, minutes, and seconds. - */ -export function getTimeDifference(diff) { - const diffAbs = Math.abs(diff); - - const days = Math.floor(diffAbs / ONE_DAY_IN_MILLISECONDS); - const hours = Math.floor((diffAbs % ONE_DAY_IN_MILLISECONDS) / ONE_HOUR_IN_MILLISECONDS); - const minutes = Math.floor((diffAbs % ONE_HOUR_IN_MILLISECONDS) / ONE_MINUTE_IN_MILLISECONDS); - const seconds = Math.floor((diffAbs % ONE_MINUTE_IN_MILLISECONDS) / ONE_SECOND_MILLISECONDS); - return { days, hours, minutes, seconds }; -} - -/** - * Returns a string representing the time duration for datetime attr in time html tag. - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time - * @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-duration-string - * - * @param {Object} duration - The duration object. - * @param {number} duration.days - The number of days. - * @param {number} duration.hours - The number of hours. - * @param {number} duration.minutes - The number of minutes. - * @param {number} duration.seconds - The number of seconds. - * @returns {string} The time duration in ISO 8601 format. - */ -export function getTimeDurationAttrValue({ days, hours, minutes, seconds }) { - if (days > 0) { - return `P${days}DT${hours}H${minutes}M${seconds}S`; - } - if (hours > 0) { - return `PT${hours}H${minutes}M${seconds}S`; - } - if (minutes > 0) { - return `PT${minutes}M${seconds}S`; - } - return `PT${seconds}S`; -} - -export function addMilliseconds(date, ms) { - return new Date(date.getTime() + ms); -}