Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2.2.0 #190

Merged
merged 9 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "safe-dao-governance-app",
"homepage": "https://github.com/safe-global/safe-dao-governance-app",
"license": "GPL-3.0",
"version": "2.1.0",
"version": "2.2.0",
"scripts": {
"build": "next build && next export",
"lint": "tsc && next lint",
Expand Down
246 changes: 162 additions & 84 deletions src/components/Points/ActivityPointsFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,83 @@ import { GLOBAL_CAMPAIGN_IDS } from '@/config/constants'
import { Campaign } from '@/hooks/useCampaigns'
import { useChainId } from '@/hooks/useChainId'
import { useOwnCampaignRank } from '@/hooks/useLeaderboard'
import { formatDatetime } from '@/utils/date'
import { formatDate } from '@/utils/date'
import { Divider, Skeleton, Stack, Typography } from '@mui/material'
import Box from '@mui/material/Box'
import { useEffect, useMemo, useState } from 'react'
import { ReactNode, useEffect, useMemo, useState } from 'react'
import PointsCounter from '../PointsCounter'
import Barcode from '@/public/images/horizontal_barcode.svg'
import css from './styles.module.css'
import { useLatestCampaignUpdate } from '@/hooks/useLatestCampaignUpdate'

const BorderedBox = ({ children }: { children: ReactNode }) => {
return (
<Box
sx={{
border: ({ palette }) => `1px solid ${palette.border.light}`,
borderRadius: '6px',
p: '24px 32px',
display: 'flex',
flexDirection: 'column',
gap: 2,
position: 'relative',
}}
>
{children}
</Box>
)
}

const HiddenValue = () => (
<Typography minWidth="80px">
<Skeleton />
</Typography>
)

const DataWrapper = ({ children }: { children: ReactNode }) => {
return (
<Stack
direction={{ xs: 'column', lg: 'row' }}
justifyContent="space-between"
alignItems={{ xs: 'start', lg: 'center' }}
spacing={2}
>
{children}
</Stack>
)
}

export const ActivityPointsFeed = ({ campaign }: { campaign?: Campaign }) => {
const { data: ownEntry, isLoading } = useOwnCampaignRank(campaign?.resourceId)
const { data: latestUpdate, isLoading: isLatestUpdateLoading } = useLatestCampaignUpdate(campaign?.resourceId)

const data = useMemo(() => {
return {
activityPoints: ownEntry?.totalPoints ?? 0,
boostedPoints: ownEntry ? ownEntry.totalBoostedPoints - ownEntry.totalPoints : 0,
totalPoints: ownEntry?.totalBoostedPoints ?? 0,
activityPoints: latestUpdate?.totalPoints ?? 0,
boostedPoints: latestUpdate ? latestUpdate.totalBoostedPoints - latestUpdate.totalPoints : 0,
totalPoints: latestUpdate?.totalBoostedPoints ?? 0,
overallPoints: ownEntry?.totalBoostedPoints ?? 0,
}
}, [ownEntry])
}, [latestUpdate, ownEntry])

const [showBoostPoints, setShowBoostPoints] = useState(false)
const [showTotalPoints, setShowTotalPoints] = useState(false)
const [showOverallPoints, setShowOverallPoints] = useState(false)

useEffect(() => {
if (ownEntry !== undefined || !isLoading) {
const showBoostPointsTimeout = setTimeout(() => setShowBoostPoints(true), 1000)
const showTotalPointsTimeout = setTimeout(() => setShowTotalPoints(true), 2000)
if (isLoading || isLatestUpdateLoading) {
return
}
const showBoostPointsTimeout = setTimeout(() => setShowBoostPoints(true), 1000)
const showTotalPointsTimeout = setTimeout(() => setShowTotalPoints(true), 2000)
const showOverallPointsTimeout = setTimeout(() => setShowOverallPoints(true), 3000)

return () => {
clearTimeout(showBoostPointsTimeout)
clearTimeout(showTotalPointsTimeout)
}
return () => {
clearTimeout(showBoostPointsTimeout)
clearTimeout(showTotalPointsTimeout)
clearTimeout(showOverallPointsTimeout)
}
}, [ownEntry, isLoading])
}, [ownEntry, isLoading, isLatestUpdateLoading])

const chainId = useChainId()

Expand All @@ -53,81 +92,120 @@ export const ActivityPointsFeed = ({ campaign }: { campaign?: Campaign }) => {

if (isLoading) {
return (
<Box
key={campaign?.resourceId}
sx={{
border: ({ palette }) => `1px solid ${palette.border.light}`,
borderRadius: '6px',
p: '24px 32px',
display: 'flex',
flexDirection: 'column',
gap: 2,
position: 'relative',
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">Activity points</Typography>
<HiddenValue />
</Stack>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">Boost points</Typography>
<HiddenValue />
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">{!isGlobal && 'Campaign'} Total</Typography>
<HiddenValue />
</Stack>
<HiddenValue />
<Barcode className={css.barcode} />
</Box>
<>
<BorderedBox key={campaign?.resourceId}>
<DataWrapper>
<Typography color="text.secondary">Last drop</Typography>
<HiddenValue />
</DataWrapper>
<Divider
sx={{
marginRight: '-32px',
marginLeft: '-32px',
}}
/>
<DataWrapper>
<Typography color="text.secondary">Activity points</Typography>
<HiddenValue />
</DataWrapper>
<DataWrapper>
<Typography color="text.secondary">Boost points</Typography>
<HiddenValue />
</DataWrapper>
<Divider />
<DataWrapper>
<Typography color="text.secondary">{!isGlobal && 'Campaign'} Week total</Typography>
<HiddenValue />
</DataWrapper>
<Divider />
<DataWrapper>
<Typography color="text.secondary">{!isGlobal && 'Campaign'} Overall</Typography>
<HiddenValue />
</DataWrapper>
<Typography mt={6} alignSelf="center" color="text.secondary">
Your points are updated weekly.
</Typography>
<Barcode className={css.barcode} />
</BorderedBox>
{!isGlobal && (
<BorderedBox>
<DataWrapper>
<Typography color="text.secondary">Campaign total</Typography>
<HiddenValue />
</DataWrapper>
</BorderedBox>
)}
</>
)
}

return (
<Box
key={campaign?.resourceId}
sx={{
border: ({ palette }) => `1px solid ${palette.border.light}`,
borderRadius: '6px',
p: '24px 32px',
display: 'flex',
flexDirection: 'column',
gap: 2,
position: 'relative',
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">Activity points</Typography>
<PointsCounter value={Number(data.activityPoints)} fontWeight={700}>
points
</PointsCounter>
</Stack>
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">Boost points</Typography>
{showBoostPoints ? (
<PointsCounter value={Number(data.boostedPoints)} fontWeight={700}>
points
</PointsCounter>
) : (
<HiddenValue />
)}
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
<Typography color="text.secondary">{!isGlobal && 'Campaign'} Total</Typography>
{showTotalPoints ? (
<PointsCounter value={Number(data.totalPoints)} fontWeight={700} fontSize="27px" color="primary">
<>
<BorderedBox key={campaign?.resourceId}>
<DataWrapper>
<Typography color="text.secondary">Your last drop</Typography>
{latestUpdate && (
<Typography color="text.secondary">
{formatDate(new Date(latestUpdate.startDate))} - {formatDate(new Date(latestUpdate.endDate))}
</Typography>
)}
</DataWrapper>
<Divider
sx={{
marginRight: '-32px',
marginLeft: '-32px',
}}
/>
<DataWrapper>
<Typography color="text.secondary">Activity points</Typography>
<PointsCounter value={Number(data.activityPoints)} fontWeight={700}>
points
</PointsCounter>
) : (
<HiddenValue />
)}
</Stack>
<Typography mt={6} alignSelf="center" color="text.secondary">
Last updated {formatDatetime(new Date(campaign.lastUpdated))}
</Typography>
<Barcode className={css.barcode} />
</Box>
</DataWrapper>
<DataWrapper>
<Typography color="text.secondary">Boost points</Typography>
{showBoostPoints ? (
<PointsCounter value={Number(data.boostedPoints)} fontWeight={700}>
points
</PointsCounter>
) : (
<HiddenValue />
)}
</DataWrapper>
<Divider />
<DataWrapper>
<Typography>{!isGlobal && 'Campaign'} Week total</Typography>
{showTotalPoints ? (
<PointsCounter value={Number(data.totalPoints)} fontWeight={700}>
points
</PointsCounter>
) : (
<HiddenValue />
)}
</DataWrapper>
<Typography mt={6} alignSelf="center" color="text.secondary">
Points are updated weekly.
</Typography>
<Typography mt={-2} alignSelf="center" color="text.secondary">
Last update: {formatDate(new Date(campaign.lastUpdated))}
</Typography>
<Barcode className={css.barcode} />
</BorderedBox>

{!isGlobal && (
<BorderedBox>
<DataWrapper>
<Typography color="text.secondary">Campaign total</Typography>
{showOverallPoints ? (
<PointsCounter value={Number(data.overallPoints)} fontWeight={700}>
points
</PointsCounter>
) : (
<HiddenValue />
)}
</DataWrapper>
</BorderedBox>
)}
</>
)
}
47 changes: 38 additions & 9 deletions src/components/Points/CampaignTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,44 @@
import * as React from 'react'
import Tabs from '@mui/material/Tabs'
import Tab from '@mui/material/Tab'
import { Box, Chip, Typography, useMediaQuery } from '@mui/material'
import { Box, Chip, Tooltip, Typography } from '@mui/material'
import { useTheme } from '@mui/material/styles'
import css from './styles.module.css'

const CAMPAIGN_TABS = [
{
label: 'Global',
label: (
<Typography display="flex" flexDirection="row" gap={1} alignItems="center" fontWeight={700}>
<Tooltip title="This campaign is active now" arrow>
<Box
sx={{
borderRadius: '100%',
backgroundColor: ({ palette }) => palette.primary.main,
minWidth: '6px',
minHeight: '6px',
flexShrink: 0,
marginRight: 1,
}}
/>
</Tooltip>
Global
</Typography>
),
disabled: false,
},
{
label: (
<Typography display="flex" flexDirection="row" gap={1} alignItems="center">
Campaigns <Chip variant="outlined" sx={{ borderRadius: '4px' }} label="soon" />
<Typography display="flex" flexDirection="row" gap={1} alignItems="center" fontWeight={700}>
<Box
sx={{
borderRadius: '100%',
backgroundColor: ({ palette }) => palette.text.disabled,
minWidth: '6px',
minHeight: '6px',
flexShrink: 0,
marginRight: 1,
}}
/>
Campaigns <Chip variant="outlined" className={css.comingSoon} label="soon" />
</Typography>
),
disabled: true,
Expand All @@ -21,19 +47,22 @@ const CAMPAIGN_TABS = [

const CampaignTabs = ({ onChange, selectedTabIdx }: { onChange: (tab: number) => void; selectedTabIdx: number }) => {
const theme = useTheme()
const isSmallScreen = useMediaQuery(theme.breakpoints.down('lg'))
return (
<Box padding="8px 0px">
<Tabs
orientation={isSmallScreen ? 'horizontal' : 'vertical'}
orientation="vertical"
variant="scrollable"
value={selectedTabIdx}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: 'divider' }}
sx={{ border: 1, borderColor: 'divider', borderRadius: '6px' }}
onChange={(_, value) => onChange(value)}
>
{CAMPAIGN_TABS.map((tab, tabIdx) => (
<Tab key={tabIdx} {...tab} />
<Tab
sx={{ textTransform: 'none', fontWeight: 700, textAlign: 'left', alignItems: 'start' }}
key={tabIdx}
{...tab}
/>
))}
</Tabs>
</Box>
Expand Down
Loading
Loading