Skip to content

Commit 888360e

Browse files
committed
Merge branch 'fix/calendar' into develop
2 parents 47dbf77 + 5a3167a commit 888360e

File tree

13 files changed

+188
-129
lines changed

13 files changed

+188
-129
lines changed

packages/plugins/Calendar/src/SiteAdaptor/CalendarContent.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { EMPTY_LIST } from '@masknet/shared-base'
33
import { MaskTabList, makeStyles, useTabs } from '@masknet/theme'
44
import { TabContext, TabPanel } from '@mui/lab'
55
import { Tab } from '@mui/material'
6-
import { useState, type HTMLProps } from 'react'
6+
import { uniq } from 'lodash-es'
7+
import { useMemo, useState, type HTMLProps } from 'react'
8+
import { useNewsList } from '../hooks/useEventList.js'
9+
import { useLumaEvents } from '../hooks/useLumaEvents.js'
710
import { DatePickerTab } from './components/DatePickerTab.js'
811
import { EventList } from './components/EventList.js'
912
import { Footer } from './components/Footer.js'
@@ -46,10 +49,20 @@ export function CalendarContent(props: Props) {
4649
const { classes, cx } = useStyles()
4750
const [currentTab, onChange, tabs] = useTabs('news', 'events')
4851
const [date, setDate] = useState(() => new Date(Math.floor(Date.now() / 1000) * 1000)) // round to seconds
52+
const [pickerDate, setPickerDate] = useState(date)
4953
const [open, setOpen] = useState(false)
5054

5155
const [allowedDates, setAllowedDates] = useState<string[]>(EMPTY_LIST)
5256

57+
const { data: newsList = EMPTY_LIST } = useNewsList(pickerDate, currentTab === tabs.news)
58+
const { data: eventList = EMPTY_LIST } = useLumaEvents(pickerDate, currentTab === tabs.events)
59+
60+
const allAllowedDates = useMemo(() => {
61+
const list = currentTab === tabs.news ? newsList : eventList
62+
const dates = list.map((x) => new Date(x.event_date).toLocaleDateString())
63+
return uniq([...dates, ...allowedDates])
64+
}, [allowedDates, newsList, eventList, currentTab])
65+
5366
return (
5467
<div {...props} className={cx(classes.calendar, props.className)}>
5568
<TabContext value={currentTab}>
@@ -64,7 +77,8 @@ export function CalendarContent(props: Props) {
6477
onToggle={setOpen}
6578
date={date}
6679
onChange={setDate}
67-
allowedDates={allowedDates}
80+
allowedDates={allAllowedDates}
81+
onMonthChange={setPickerDate}
6882
/>
6983
<TabPanel value={tabs.news} className={classes.tabPanel}>
7084
<NewsList date={date} onDatesUpdate={setAllowedDates} />

packages/plugins/Calendar/src/SiteAdaptor/components/DatePicker.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,29 +76,24 @@ const useStyles = makeStyles()((theme) => {
7676
}
7777
})
7878

79-
interface DatePickerProps {
79+
export interface DatePickerProps {
8080
open: boolean
8181
onToggle: (x: boolean) => void
8282
date: Date
83-
onChange: (date: Date) => void
83+
/** locale date string list */
8484
allowedDates: string[]
85+
onChange: (date: Date) => void
86+
onMonthChange: (date: Date) => void
8587
}
8688

87-
export function DatePicker({ date, onChange, open, onToggle, allowedDates }: DatePickerProps) {
89+
export function DatePicker({ date, onChange, open, onToggle, allowedDates, onMonthChange }: DatePickerProps) {
8890
const { classes } = useStyles()
8991
const [currentDate, setCurrentDate] = useState(date)
9092
const monthStart = startOfMonth(currentDate)
9193
const startingDayOfWeek = monthStart.getDay()
9294
const daysInMonth = endOfMonth(currentDate).getDate()
9395
const daysInPrevMonth = endOfMonth(addMonths(currentDate, -1)).getDate()
9496

95-
const isPrevMonthDisabled = useMemo(() => {
96-
return !isAfter(currentDate, endOfMonth(new Date()))
97-
}, [currentDate])
98-
const isNextMonthDisabled = useMemo(() => {
99-
return isAfter(addMonths(currentDate, 1), addMonths(endOfMonth(new Date()), 2))
100-
}, [currentDate])
101-
10297
if (!open) return null
10398

10499
const handleDateClick = (date: Date) => {
@@ -107,7 +102,9 @@ export function DatePicker({ date, onChange, open, onToggle, allowedDates }: Dat
107102
}
108103

109104
const changeMonth = (amount: number) => {
110-
setCurrentDate(addMonths(currentDate, amount))
105+
const date = addMonths(currentDate, amount)
106+
setCurrentDate(date)
107+
onMonthChange(date)
111108
}
112109

113110
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
@@ -180,10 +177,10 @@ export function DatePicker({ date, onChange, open, onToggle, allowedDates }: Dat
180177
<div className={classes.header}>
181178
<Typography className={classes.headerText}>{format(currentDate, 'MMMM yyyy')}</Typography>
182179
<Box className={classes.headerIcon}>
183-
<IconButton size="small" onClick={() => changeMonth(-1)} disabled={isPrevMonthDisabled}>
180+
<IconButton size="small" onClick={() => changeMonth(-1)}>
184181
<Icons.LeftArrow size={24} />
185182
</IconButton>
186-
<IconButton size="small" onClick={() => changeMonth(1)} disabled={isNextMonthDisabled}>
183+
<IconButton size="small" onClick={() => changeMonth(1)}>
187184
<Icons.RightArrow size={24} />
188185
</IconButton>
189186
</Box>

packages/plugins/Calendar/src/SiteAdaptor/components/DatePickerTab.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Icons } from '@masknet/icons'
2-
import { EMPTY_LIST } from '@masknet/shared-base'
32
import { makeStyles } from '@masknet/theme'
43
import { ClickAwayListener, IconButton, Typography } from '@mui/material'
54
import { eachDayOfInterval, endOfWeek, startOfWeek } from 'date-fns'
65
import { useMemo } from 'react'
7-
import { DatePicker } from './DatePicker.js'
6+
import { DatePicker, type DatePickerProps } from './DatePicker.js'
87

98
const useStyles = makeStyles()((theme) => ({
109
container: {
@@ -40,20 +39,16 @@ const useStyles = makeStyles()((theme) => ({
4039
},
4140
}))
4241

43-
interface DatePickerTabProps {
44-
open: boolean
45-
onToggle: (x: boolean) => void
46-
/** locale date string list */
47-
allowedDates: string[]
48-
date: Date
49-
onChange: (date: Date) => void
50-
}
42+
interface DatePickerTabProps extends DatePickerProps {}
5143

52-
export function DatePickerTab({ date, onChange, allowedDates = EMPTY_LIST, open, onToggle }: DatePickerTabProps) {
44+
export function DatePickerTab(props: DatePickerTabProps) {
45+
const { open, date, allowedDates, onChange, onToggle } = props
5346
const { classes } = useStyles()
47+
5448
const days = useMemo(() => {
5549
return eachDayOfInterval({ start: startOfWeek(date), end: endOfWeek(date) })
5650
}, [date])
51+
5752
return (
5853
<div className={classes.container}>
5954
{days.map((v) => {
@@ -81,13 +76,7 @@ export function DatePickerTab({ date, onChange, allowedDates = EMPTY_LIST, open,
8176
}}>
8277
<Icons.LinearCalendar size={24} />
8378
</IconButton>
84-
<DatePicker
85-
open={open}
86-
onToggle={onToggle}
87-
date={date}
88-
onChange={onChange}
89-
allowedDates={allowedDates}
90-
/>
79+
<DatePicker {...props} />
9180
</div>
9281
</ClickAwayListener>
9382
</div>

packages/plugins/Calendar/src/SiteAdaptor/components/EventList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export function EventList({ date, onDatesUpdate }: EventListProps) {
139139
<div className={classes.paddingWrap}>
140140
<div className={cx(classes.empty, classes.eventTitle)}>
141141
<EmptyStatus>
142-
<Trans>No events</Trans>
142+
<Trans>No content for the last two weeks.</Trans>
143143
</EmptyStatus>
144144
</div>
145145
</div>

packages/plugins/Calendar/src/SiteAdaptor/components/NewsList.tsx

Lines changed: 115 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Trans } from '@lingui/macro'
2-
import { EmptyStatus, Image, LoadingStatus } from '@masknet/shared'
3-
import { EMPTY_OBJECT } from '@masknet/shared-base'
4-
import { makeStyles } from '@masknet/theme'
2+
import { ElementAnchor, EmptyStatus, Image, LoadingStatus, ReloadStatus } from '@masknet/shared'
3+
import { EMPTY_LIST } from '@masknet/shared-base'
4+
import { LoadingBase, makeStyles } from '@masknet/theme'
5+
import type { ParsedEvent } from '@masknet/web3-providers/types'
56
import { Link, Typography } from '@mui/material'
67
import { format } from 'date-fns'
7-
import { useCallback, useEffect, useMemo } from 'react'
8+
import { uniq } from 'lodash-es'
9+
import { useEffect, useMemo } from 'react'
810
import { useNewsList } from '../../hooks/useEventList.js'
911

1012
const useStyles = makeStyles()((theme) => ({
@@ -102,83 +104,132 @@ const useStyles = makeStyles()((theme) => ({
102104
color: theme.palette.maskColor.main,
103105
padding: '10px 0',
104106
},
107+
loading: {
108+
color: theme.palette.maskColor.main,
109+
},
105110
}))
106111

107112
interface NewsListProps {
108113
date: Date
109114
onDatesUpdate(/** locale date string list */ dates: string[]): void
110115
}
111116

117+
interface Group {
118+
date: number
119+
/** 2024/10/10 */
120+
label: string
121+
events: ParsedEvent[]
122+
}
112123
export function NewsList({ date, onDatesUpdate }: NewsListProps) {
113-
const { data: list = EMPTY_OBJECT, isLoading } = useNewsList(date)
114-
const dateString = date.toLocaleDateString()
115-
const empty = !Object.keys(list).length
116124
const { classes, cx } = useStyles()
117-
const futureNewsList = useMemo(() => {
118-
const newsList: string[] = []
119-
for (const key in list) {
120-
if (new Date(key) >= date) {
121-
newsList.push(key)
125+
const { isLoading, isFetching, data: newsList, error, hasNextPage, fetchNextPage } = useNewsList(date)
126+
127+
const groups = useMemo(() => {
128+
if (!newsList?.length) return EMPTY_LIST
129+
const groups: Group[] = []
130+
newsList.forEach((event) => {
131+
const eventDate = new Date(event.event_date)
132+
if (eventDate < date) return
133+
const label = eventDate.toLocaleDateString()
134+
const group: Group = groups.find((g) => g.label === label) || {
135+
date: event.event_date,
136+
label,
137+
events: [],
122138
}
123-
}
124-
return newsList
125-
}, [list, date])
139+
if (!group.events.length) groups.push(group)
140+
group.events.push(event)
141+
})
142+
return groups
143+
}, [newsList, date])
126144

127145
useEffect(() => {
128-
onDatesUpdate(Object.keys(list))
129-
}, [list, onDatesUpdate])
130-
131-
const listRef = useCallback((el: HTMLDivElement | null) => {
132-
el?.scrollTo({ top: 0 })
133-
}, [])
146+
if (!newsList) return onDatesUpdate(EMPTY_LIST)
147+
onDatesUpdate(uniq(newsList.map((x) => new Date(x.event_date).toLocaleDateString())))
148+
}, [onDatesUpdate, newsList])
134149

135-
return (
136-
<div className={classes.container} ref={listRef} key={dateString}>
137-
<div className={classes.paddingWrap}>
138-
{isLoading && empty ?
150+
if (isLoading) {
151+
return (
152+
<div className={classes.container}>
153+
<div className={classes.paddingWrap}>
139154
<div className={cx(classes.empty, classes.eventTitle)}>
140155
<LoadingStatus />
141156
</div>
142-
: !empty && futureNewsList.length ?
143-
futureNewsList.map((key) => {
144-
return (
145-
<div key={key}>
146-
<Typography className={classes.dateDiv}>
147-
{format(new Date(key), 'MMM dd,yyy')}
148-
</Typography>
149-
{list[key].map((event) => (
150-
<Link
151-
key={event.event_url}
152-
href={event.event_url}
153-
className={classes.eventCard}
154-
rel="noopener noreferrer"
155-
target="_blank">
156-
<div className={classes.eventHeader}>
157-
<div className={classes.projectWrap}>
158-
<Image
159-
src={event.project?.logo || event.poster_url}
160-
classes={{ container: classes.logo }}
161-
size={24}
162-
alt={event.project?.name || event.event_title}
163-
/>
164-
<Typography className={classes.projectName}>
165-
{event.project?.name || event.event_title}
166-
</Typography>
167-
</div>
168-
<Typography className={classes.eventType}>{event.event_type}</Typography>
157+
</div>
158+
</div>
159+
)
160+
}
161+
162+
if (error) {
163+
return (
164+
<div className={classes.container}>
165+
<div className={classes.paddingWrap}>
166+
<div className={cx(classes.empty, classes.eventTitle)}>
167+
<ReloadStatus message={error.message}></ReloadStatus>
168+
</div>
169+
</div>
170+
</div>
171+
)
172+
}
173+
if (!groups.length) {
174+
return (
175+
<div className={classes.container}>
176+
<div className={classes.paddingWrap}>
177+
<div className={cx(classes.empty, classes.eventTitle)}>
178+
<EmptyStatus>
179+
<Trans>No content for the last two weeks.</Trans>
180+
</EmptyStatus>
181+
</div>
182+
</div>
183+
</div>
184+
)
185+
}
186+
187+
return (
188+
<div className={classes.container}>
189+
<div className={classes.paddingWrap}>
190+
{groups.map((group) => {
191+
return (
192+
<div key={group.label}>
193+
<Typography className={classes.dateDiv}>
194+
{format(new Date(group.date), 'MMM dd,yyy')}
195+
</Typography>
196+
{group.events.map((event) => (
197+
<Link
198+
key={event.event_url}
199+
href={event.event_url}
200+
className={classes.eventCard}
201+
rel="noopener noreferrer"
202+
target="_blank">
203+
<div className={classes.eventHeader}>
204+
<div className={classes.projectWrap}>
205+
<Image
206+
src={event.project?.logo || event.poster_url}
207+
classes={{ container: classes.logo }}
208+
size={24}
209+
alt={event.project?.name || event.event_title}
210+
/>
211+
<Typography className={classes.projectName}>
212+
{event.project?.name || event.event_title}
213+
</Typography>
169214
</div>
170-
<Typography className={classes.eventTitle}>{event.event_title}</Typography>
171-
<Typography className={classes.eventContent}>
172-
{event.event_description}
173-
</Typography>
174-
</Link>
175-
))}
176-
</div>
177-
)
178-
})
179-
: <EmptyStatus className={classes.empty}>
180-
<Trans>No content for the last two weeks.</Trans>
181-
</EmptyStatus>
215+
<Typography className={classes.eventType}>{event.event_type}</Typography>
216+
</div>
217+
<Typography className={classes.eventTitle}>{event.event_title}</Typography>
218+
<Typography className={classes.eventContent}>{event.event_description}</Typography>
219+
</Link>
220+
))}
221+
</div>
222+
)
223+
})}
224+
{hasNextPage ?
225+
<ElementAnchor height={30} callback={() => fetchNextPage()}>
226+
{isFetching ?
227+
<LoadingBase className={classes.loading} />
228+
: null}
229+
</ElementAnchor>
230+
: <Typography color={(theme) => theme.palette.maskColor.second} textAlign="center" py={2}>
231+
<Trans>No more data available.</Trans>
232+
</Typography>
182233
}
183234
</div>
184235
</div>

0 commit comments

Comments
 (0)