From 17ecf8638b394f3ad496878afd4d7139cbd704ce Mon Sep 17 00:00:00 2001 From: Melisa Anabella Rossi Date: Mon, 25 Mar 2024 15:12:51 -0300 Subject: [PATCH] feat: add notifications for events (#517) --- .../Icons/Notifications/EventCancelled.tsx | 41 ++++++ .../Icons/Notifications/EventStarted.tsx | 41 ++++++ .../Icons/Notifications/EventStartsSoon.tsx | 37 +++++ .../Notifications/NotificationItem.css | 13 ++ .../Events/EventStartedNotification.tsx | 59 ++++++++ .../Events/EventStartsSoonNotification.tsx | 130 ++++++++++++++++++ .../Notifications/NotificationTypes/index.ts | 6 +- .../Notifications/Notifications.stories.tsx | 73 ++++++++++ src/components/Notifications/types.ts | 29 +++- src/components/Notifications/utils.ts | 6 +- 10 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 src/components/Icons/Notifications/EventCancelled.tsx create mode 100644 src/components/Icons/Notifications/EventStarted.tsx create mode 100644 src/components/Icons/Notifications/EventStartsSoon.tsx create mode 100644 src/components/Notifications/NotificationTypes/Events/EventStartedNotification.tsx create mode 100644 src/components/Notifications/NotificationTypes/Events/EventStartsSoonNotification.tsx diff --git a/src/components/Icons/Notifications/EventCancelled.tsx b/src/components/Icons/Notifications/EventCancelled.tsx new file mode 100644 index 00000000..d7ffab45 --- /dev/null +++ b/src/components/Icons/Notifications/EventCancelled.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +const EventCancelled = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + ) +} + +export default EventCancelled diff --git a/src/components/Icons/Notifications/EventStarted.tsx b/src/components/Icons/Notifications/EventStarted.tsx new file mode 100644 index 00000000..45b932e1 --- /dev/null +++ b/src/components/Icons/Notifications/EventStarted.tsx @@ -0,0 +1,41 @@ +import React from 'react' + +const EventStarted = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + + ) +} + +export default EventStarted diff --git a/src/components/Icons/Notifications/EventStartsSoon.tsx b/src/components/Icons/Notifications/EventStartsSoon.tsx new file mode 100644 index 00000000..94a63b67 --- /dev/null +++ b/src/components/Icons/Notifications/EventStartsSoon.tsx @@ -0,0 +1,37 @@ +import React from 'react' + +const EventStartsSoon = (props: React.SVGAttributes) => { + return ( + + + + + + + + + + + ) +} + +export default EventStartsSoon diff --git a/src/components/Notifications/NotificationItem.css b/src/components/Notifications/NotificationItem.css index 3aa48d6f..04a65a7f 100644 --- a/src/components/Notifications/NotificationItem.css +++ b/src/components/Notifications/NotificationItem.css @@ -28,6 +28,10 @@ margin: 4px 0; } +.dcl.notification-item__content-description a { + text-decoration: underline; +} + .dcl.notification-item__content-timestamp { font-size: 12px; font-weight: 600; @@ -41,3 +45,12 @@ color: var(--primary); text-decoration: underline; } + +.notification-item__countdown { + padding: 2px 10px; + background-color: #37333d; + width: fit-content; + color: white; + border-radius: 20px; + display: flex; +} diff --git a/src/components/Notifications/NotificationTypes/Events/EventStartedNotification.tsx b/src/components/Notifications/NotificationTypes/Events/EventStartedNotification.tsx new file mode 100644 index 00000000..278b8476 --- /dev/null +++ b/src/components/Notifications/NotificationTypes/Events/EventStartedNotification.tsx @@ -0,0 +1,59 @@ +import React from 'react' + +import { CommonNotificationProps, EventsStartedNotification } from '../../types' +import NotificationItem from '../../NotificationItem' +import EventStarted from '../../../Icons/Notifications/EventStarted' + +const i18N = { + en: { + description: ( + metadata: EventsStartedNotification['metadata'] + ): React.ReactNode => ( + <> + The event {metadata.name} has begun! + + ), + title: 'Event started' + }, + es: { + description: ( + metadata: EventsStartedNotification['metadata'] + ): React.ReactNode => ( + <> + El evento {metadata.name} ha empezado! + + ), + title: 'Evento ha comenzado' + }, + zh: { + description: ( + metadata: EventsStartedNotification['metadata'] + ): React.ReactNode => ( + <> + 事件 {metadata.name} 已开始 + + ), + title: '事件开始' + } +} + +const EventsStartedNotification = ({ + notification, + locale +}: CommonNotificationProps) => ( + }} + timestamp={notification.timestamp} + isNew={!notification.read} + locale={locale} + > +

{i18N[locale].title}

+ +

+ {i18N[locale].description(notification.metadata)} +

+
+
+) + +export default EventsStartedNotification diff --git a/src/components/Notifications/NotificationTypes/Events/EventStartsSoonNotification.tsx b/src/components/Notifications/NotificationTypes/Events/EventStartsSoonNotification.tsx new file mode 100644 index 00000000..daeb63b8 --- /dev/null +++ b/src/components/Notifications/NotificationTypes/Events/EventStartsSoonNotification.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from 'react' +import Icon from 'semantic-ui-react/dist/commonjs/elements/Icon/Icon' + +import { + CommonNotificationProps, + EventsStartsSoonNotification +} from '../../types' +import NotificationItem from '../../NotificationItem' +import EventStartsSoon from '../../../Icons/Notifications/EventStartsSoon' + +let interval: NodeJS.Timeout + +function Countdown({ startDate }: { startDate: string }) { + const [minutes, setMinutes] = useState(undefined) + const [seconds, setSeconds] = useState(undefined) + + useEffect(() => { + interval = setInterval(() => { + const eventStartDate = new Date(startDate).getTime() + const distance = eventStartDate - Date.now() + const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)) + const seconds = Math.floor((distance % (1000 * 60)) / 1000) + + setMinutes(minutes) + setSeconds(seconds) + if (distance < 0) { + setMinutes(0) + setSeconds(0) + clearInterval(interval) + } + }, 1000) + return () => clearInterval(interval) + }, [setMinutes, setSeconds]) + + const minutesString = + minutes !== undefined + ? minutes.toLocaleString('en-US', { + minimumIntegerDigits: 2, + useGrouping: false + }) + : '--' + const secondsString = + seconds !== undefined + ? seconds.toLocaleString('en-US', { + minimumIntegerDigits: 2, + useGrouping: false + }) + : '--' + + return ( +
+ + {`${minutesString}:${secondsString}`} +
+ ) +} + +const i18N = { + en: { + description: ( + metadata: EventsStartsSoonNotification['metadata'] + ): React.ReactNode => + new Date(metadata.startsAt).getTime() > Date.now() ? ( + <> + The event {metadata.name} is about to + start in + + ) : ( + <> + The event {metadata.name} starts in an + hour + + ), + title: 'Event starts soon' + }, + es: { + description: ( + metadata: EventsStartsSoonNotification['metadata'] + ): React.ReactNode => + new Date(metadata.startsAt).getTime() > Date.now() ? ( + <> + El evento {metadata.name} esta por empezar + en + + ) : ( + <> + El evento {metadata.name} empieza en una + hora + + ), + title: 'Evento empieza pronto' + }, + zh: { + description: ( + metadata: EventsStartsSoonNotification['metadata'] + ): React.ReactNode => + new Date(metadata.startsAt).getTime() > Date.now() ? ( + <> + 事件 {metadata.name} 即将开始 在{' '} + + + ) : ( + <> + 事件 {metadata.name} 从一个开始 小时 + + ), + title: '事件即将开始' + } +} + +const EventsStartsSoonNotification = ({ + notification, + locale +}: CommonNotificationProps) => ( + }} + timestamp={notification.timestamp} + isNew={!notification.read} + locale={locale} + > +

{i18N[locale].title}

+ +

+ {i18N[locale].description(notification.metadata)} +

+
+
+) + +export default EventsStartsSoonNotification diff --git a/src/components/Notifications/NotificationTypes/index.ts b/src/components/Notifications/NotificationTypes/index.ts index c0dcef5a..eca0a5f4 100644 --- a/src/components/Notifications/NotificationTypes/index.ts +++ b/src/components/Notifications/NotificationTypes/index.ts @@ -11,6 +11,8 @@ import GovernanceCoauthorRequestedNotification from './Governance/GovernanceCoau import GovernanceNewCommentOnProposalNotification from './Governance/GovernanceNewCommentOnProposalNotification' import GovernanceProposalEnactedNotification from './Governance/GovernanceProposalEnactedNotification' import GovernanceVotingEndedVoterNotification from './Governance/GovernanceVotingEndedVoterNotification' +import EventsStartsSoonNotification from './Events/EventStartsSoonNotification' +import EventsStartedNotification from './Events/EventStartedNotification' export { WorldsAccessRestoredNotification, @@ -25,5 +27,7 @@ export { GovernanceCoauthorRequestedNotification, GovernanceNewCommentOnProposalNotification, GovernanceProposalEnactedNotification, - GovernanceVotingEndedVoterNotification + GovernanceVotingEndedVoterNotification, + EventsStartsSoonNotification, + EventsStartedNotification } diff --git a/src/components/Notifications/Notifications.stories.tsx b/src/components/Notifications/Notifications.stories.tsx index 6ba96342..174f0094 100644 --- a/src/components/Notifications/Notifications.stories.tsx +++ b/src/components/Notifications/Notifications.stories.tsx @@ -12,6 +12,10 @@ import GovernanceCoauthorRequestedNotification from './NotificationTypes/Governa import GovernanceNewCommentOnProposalNotification from './NotificationTypes/Governance/GovernanceNewCommentOnProposalNotification' import GovernanceVotingEndedVoterNotification from './NotificationTypes/Governance/GovernanceVotingEndedVoterNotification' import GovernanceProposalEnactedNotification from './NotificationTypes/Governance/GovernanceProposalEnactedNotification' +import { + EventsStartedNotification, + EventsStartsSoonNotification +} from './NotificationTypes' storiesOf('Notifications Toggle', module) .add('Without new notifications', () => { @@ -451,3 +455,72 @@ storiesOf('Notifications Toggle', module) ) }) + .add('Events Notifications', () => { + const futureStartDate = new Date() + futureStartDate.setSeconds(futureStartDate.getSeconds() + 50) + + const pastStartDate = new Date() + pastStartDate.setSeconds(pastStartDate.getSeconds() - 50) + return ( +
+ + + +
+ ) + }) diff --git a/src/components/Notifications/types.ts b/src/components/Notifications/types.ts index ebda6584..14deacf8 100644 --- a/src/components/Notifications/types.ts +++ b/src/components/Notifications/types.ts @@ -34,7 +34,9 @@ export enum DecentralandNotificationType { GOVERNANCE_NEW_COMMENT_ON_PROPOSAL = 'governance_new_comment_on_proposal', WORLDS_MISSING_RESOURCES = 'worlds_missing_resources', WORLDS_ACCESS_RESTRICTED = 'worlds_access_restricted', - WORLDS_ACCESS_RESTORED = 'worlds_access_restored' + WORLDS_ACCESS_RESTORED = 'worlds_access_restored', + EVENTS_STARTS_SOON = 'events_starts_soon', + EVENTS_STARTED = 'events_started' } // Marketplace Notifications @@ -170,10 +172,35 @@ type WorldsNotifications = | WorldsAccessRestrictedNotification | WorldsAccessRestoredNotification +type CommonEventsMetadata = { + image: string + link: string + name: string +} + +// events notifications +export type EventsStartsSoonNotification = RawDecentralandNotification< + DecentralandNotificationType.EVENTS_STARTS_SOON, + CommonEventsMetadata & { + startsAt: string + endsAt: string + } +> + +export type EventsStartedNotification = RawDecentralandNotification< + DecentralandNotificationType.EVENTS_STARTED, + CommonEventsMetadata +> + +type EventsNotificatiosn = + | EventsStartsSoonNotification + | EventsStartedNotification + export type DCLNotification = | MarketplaceNotifications | GovernanceNotifications | WorldsNotifications + | EventsNotificatiosn export type CommonNotificationProps = { notification: N diff --git a/src/components/Notifications/utils.ts b/src/components/Notifications/utils.ts index 916f14f4..e1f2a054 100644 --- a/src/components/Notifications/utils.ts +++ b/src/components/Notifications/utils.ts @@ -21,6 +21,7 @@ import { WorldsMissingResourcesNotification } from './NotificationTypes' import { FunctionComponent } from 'react' +import EventsStartsSoonNotification from './NotificationTypes/Events/EventStartsSoonNotification' export const MAXIMUM_FRACTION_DIGITS = 2 @@ -66,7 +67,10 @@ export const NotificationComponentByType: DecentralandNotificationComponentByTyp [DecentralandNotificationType.WORLDS_ACCESS_RESTORED]: WorldsAccessRestoredNotification, [DecentralandNotificationType.WORLDS_ACCESS_RESTRICTED]: - WorldsAccessRestrictedNotification + WorldsAccessRestrictedNotification, + [DecentralandNotificationType.EVENTS_STARTS_SOON]: + EventsStartsSoonNotification, + [DecentralandNotificationType.EVENTS_STARTED]: EventsStartsSoonNotification } export const CURRENT_AVAILABLE_NOTIFICATIONS = Object.values(