-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
549 additions
and
4 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import React, { FC, useState } from 'react' | ||
import { differenceInHours } from 'date-fns' | ||
|
||
import flag from 'cozy-flags' | ||
|
||
import { AnnouncementsDialog } from './AnnouncementsDialog' | ||
import { useAnnouncements } from 'hooks/useAnnouncements' | ||
import { AnnouncementsConfigFlag } from './types' | ||
import { useAnnouncementsSettings } from 'hooks/useAnnouncementsSettings' | ||
|
||
const Announcements: FC = () => { | ||
const config = flag<AnnouncementsConfigFlag>('home.announcements') | ||
const [hasBeenDismissed, setBeenDismissed] = useState(false) | ||
const { values, save } = useAnnouncementsSettings() | ||
|
||
const handleDismiss = (): void => { | ||
save({ | ||
dismissedAt: new Date().toISOString() | ||
}) | ||
setBeenDismissed(true) | ||
} | ||
|
||
const moreThan = config?.delayAfterDismiss ?? 24 | ||
const hasBeenDismissedForMoreThan = values.dismissedAt | ||
? differenceInHours(Date.parse(values.dismissedAt), new Date()) >= moreThan | ||
: true | ||
const canBeDisplayed = !hasBeenDismissed && hasBeenDismissedForMoreThan | ||
const announcements = useAnnouncements({ | ||
canBeDisplayed | ||
}) | ||
|
||
if (canBeDisplayed && announcements.length > 0) { | ||
return ( | ||
<AnnouncementsDialog | ||
announcements={announcements} | ||
onDismiss={handleDismiss} | ||
/> | ||
) | ||
} | ||
|
||
return null | ||
} | ||
|
||
export { Announcements } |
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,101 @@ | ||
import React, { useState, FC } from 'react' | ||
import SwipeableViews from 'react-swipeable-views' | ||
|
||
import { FixedActionsDialog } from 'cozy-ui/transpiled/react/CozyDialogs' | ||
import CozyTheme from 'cozy-ui/transpiled/react/providers/CozyTheme' | ||
import MobileStepper from 'cozy-ui/transpiled/react/MobileStepper' | ||
import IconButton from 'cozy-ui/transpiled/react/IconButton' | ||
import Icon from 'cozy-ui/transpiled/react/Icon' | ||
import LeftIcon from 'cozy-ui/transpiled/react/Icons/Left' | ||
import RightIcon from 'cozy-ui/transpiled/react/Icons/Right' | ||
import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints' | ||
|
||
import { AnnouncementsDialogContent } from './AnnouncementsDialogContent' | ||
import { Announcement } from './types' | ||
import { useAnnouncementsSettings } from 'hooks/useAnnouncementsSettings' | ||
|
||
interface AnnouncementsDialogProps { | ||
announcements: Array<Announcement> | ||
onDismiss: () => void | ||
} | ||
|
||
const AnnouncementsDialog: FC<AnnouncementsDialogProps> = ({ | ||
announcements, | ||
onDismiss | ||
}) => { | ||
const { values, save } = useAnnouncementsSettings() | ||
const { isMobile } = useBreakpoints() | ||
|
||
const [activeStep, setActiveStep] = useState(0) | ||
|
||
const handleBack = (): void => { | ||
setActiveStep(activeStep - 1) | ||
} | ||
|
||
const handleNext = (): void => { | ||
const uuid = announcements[activeStep].attributes.uuid | ||
if (!values?.seen.includes(uuid)) { | ||
save({ | ||
seen: [...(values?.seen ?? []), uuid] | ||
}) | ||
} | ||
setActiveStep(activeStep + 1) | ||
} | ||
|
||
const handleChangedIndex = (index: number): void => { | ||
setActiveStep(index) | ||
} | ||
|
||
const maxSteps = announcements.length | ||
|
||
return ( | ||
<CozyTheme variant="normal"> | ||
<FixedActionsDialog | ||
open | ||
onClose={onDismiss} | ||
content={ | ||
<SwipeableViews | ||
index={activeStep} | ||
onChangeIndex={handleChangedIndex} | ||
animateTransitions={isMobile} | ||
> | ||
{announcements.map((announcement, index) => ( | ||
<AnnouncementsDialogContent | ||
key={index} | ||
isLast={index === maxSteps - 1} | ||
announcement={announcement} | ||
onDismiss={onDismiss} | ||
onNext={handleNext} | ||
/> | ||
))} | ||
</SwipeableViews> | ||
} | ||
actions={ | ||
maxSteps > 1 ? ( | ||
<MobileStepper | ||
className="u-mh-auto" | ||
steps={maxSteps} | ||
position="static" | ||
activeStep={activeStep} | ||
nextButton={ | ||
<IconButton | ||
onClick={handleNext} | ||
disabled={activeStep === maxSteps - 1} | ||
> | ||
<Icon icon={RightIcon} /> | ||
</IconButton> | ||
} | ||
backButton={ | ||
<IconButton onClick={handleBack} disabled={activeStep === 0}> | ||
<Icon icon={LeftIcon} /> | ||
</IconButton> | ||
} | ||
/> | ||
) : null | ||
} | ||
/> | ||
</CozyTheme> | ||
) | ||
} | ||
|
||
export { AnnouncementsDialog } |
105 changes: 105 additions & 0 deletions
105
src/components/Announcements/AnnouncementsDialogContent.tsx
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,105 @@ | ||
import React, { FC } from 'react' | ||
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' | ||
import Typography from 'cozy-ui/transpiled/react/Typography' | ||
import Buttons from 'cozy-ui/transpiled/react/Buttons' | ||
|
||
import { Announcement } from './types' | ||
import { useAnnouncementsImage } from 'hooks/useAnnouncementsImage' | ||
|
||
interface AnnouncementsDialogContentProps { | ||
isLast: boolean | ||
announcement: Announcement | ||
onDismiss: () => void | ||
onNext: () => void | ||
} | ||
|
||
const AnnouncementsDialogContent: FC<AnnouncementsDialogContentProps> = ({ | ||
isLast, | ||
announcement, | ||
onDismiss, | ||
onNext | ||
}) => { | ||
const { t, f } = useI18n() | ||
const primaryImage = useAnnouncementsImage( | ||
announcement.attributes.primary_image.data.attributes.formats.small.url | ||
) | ||
const secondaryImage = useAnnouncementsImage( | ||
announcement.attributes.secondary_image.data?.attributes.formats.thumbnail | ||
.url | ||
) | ||
|
||
const handleMainAction = (): void => { | ||
if (announcement.attributes.main_action?.link) { | ||
window.open(announcement.attributes.main_action.link, '_blank') | ||
} | ||
} | ||
|
||
return ( | ||
<div className="u-flex u-flex-column u-flex-items-center"> | ||
{primaryImage ? ( | ||
<img | ||
src={primaryImage} | ||
alt={ | ||
announcement.attributes.primary_image.data.attributes | ||
.alternativeText | ||
} | ||
className="u-mt-1 u-mb-2 u-bdrs-3 u-maw-100" | ||
style={{ | ||
objectFit: 'cover', | ||
objectPosition: '100% 0' | ||
}} | ||
/> | ||
) : null} | ||
<Typography align="center" className="u-mb-half" variant="h3"> | ||
{announcement.attributes.title} | ||
</Typography> | ||
<Typography | ||
align="center" | ||
color="textSecondary" | ||
className="u-mb-1" | ||
variant="body2" | ||
> | ||
{f( | ||
announcement.attributes.start_at, | ||
t('AnnouncementsDialogContent.dateFormat') | ||
)} | ||
</Typography> | ||
<Typography align="center" className="u-mb-1"> | ||
{announcement.attributes.content} | ||
</Typography> | ||
{announcement.attributes.main_action ? ( | ||
<Buttons | ||
className="u-mb-half" | ||
variant="secondary" | ||
label={announcement.attributes.main_action.label} | ||
onClick={handleMainAction} | ||
/> | ||
) : null} | ||
<Buttons | ||
label={t( | ||
isLast | ||
? 'AnnouncementsDialogContent.understand' | ||
: 'AnnouncementsDialogContent.next' | ||
)} | ||
variant="secondary" | ||
onClick={isLast ? onDismiss : onNext} | ||
/> | ||
{secondaryImage ? ( | ||
<img | ||
src={secondaryImage} | ||
alt={ | ||
announcement.attributes.secondary_image.data?.attributes | ||
.alternativeText | ||
} | ||
className="u-mt-1 u-w-2 u-h-2" | ||
style={{ | ||
objectFit: 'cover', | ||
objectPosition: '100% 0' | ||
}} | ||
/> | ||
) : null} | ||
</div> | ||
) | ||
} | ||
|
||
export { AnnouncementsDialogContent } |
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,19 @@ | ||
import { Announcement } from './types' | ||
|
||
export const getUnseenAnnouncements = ( | ||
data: Announcement[], | ||
announcements_seen: string[] | ||
): Announcement[] => { | ||
return data.filter(announcement => { | ||
if (announcements_seen) { | ||
return !announcements_seen.includes(announcement.attributes.uuid) | ||
} | ||
return true | ||
}) | ||
} | ||
|
||
export const isAnnouncement = ( | ||
announcement: unknown | ||
): announcement is Announcement => { | ||
return (announcement as Announcement).attributes?.title !== undefined | ||
} |
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,46 @@ | ||
export interface Announcement { | ||
id: string | ||
type: string | ||
attributes: { | ||
title: string | ||
content: string | ||
start_at: string | ||
uuid: string | ||
main_action?: { | ||
label: string | ||
link: string | ||
} | ||
primary_image: { | ||
data: { | ||
attributes: { | ||
formats: { | ||
small: { | ||
url: string | ||
} | ||
} | ||
alternativeText?: string | ||
} | ||
} | ||
} | ||
secondary_image: { | ||
data: { | ||
attributes: { | ||
formats: { | ||
thumbnail: { | ||
url: string | ||
} | ||
} | ||
alternativeText?: string | ||
} | ||
} | null | ||
} | ||
} | ||
} | ||
|
||
export interface AnnouncementsConfig { | ||
remoteDoctype: string | ||
channels: string | ||
delayAfterDismiss: number | ||
} | ||
|
||
export type AnnouncementsConfigFlag = AnnouncementsConfig | null |
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 |
---|---|---|
|
@@ -11,3 +11,5 @@ declare module 'assets/*' { | |
const assets: string | ||
export default assets | ||
} | ||
|
||
declare module 'react-swipeable-views' |
Oops, something went wrong.