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

feat: [hackathon] friends, profiles & judgmental activities feed #7508

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
5 changes: 3 additions & 2 deletions src/components/AccountDrawer/AuthenticatedHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@ const StatusWrapper = styled.div`
display: inline-flex;
`

const AccountNamesWrapper = styled.div`
export const AccountNamesWrapper = styled.div`
overflow: hidden;
white-space: nowrap;
display: flex;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
justify-content: flex-end;
margin-left: 8px;
`

Expand Down
190 changes: 186 additions & 4 deletions src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { BrowserEvent, InterfaceElementName, SharedEventName } from '@uniswap/analytics-events'
import ParentSize from '@visx/responsive/lib/components/ParentSize'
import { TraceEvent } from 'analytics'
import { PriceChart } from 'components/Charts/PriceChart'
import Column from 'components/Column'
import AlertTriangleFilled from 'components/Icons/AlertTriangleFilled'
import { LoaderV2 } from 'components/Icons/LoadingSpinner'
import Row from 'components/Row'
import { TransactionStatus } from 'graphql/data/__generated__/types-and-hooks'
import { JudgementalActivity } from 'components/SocialFeed/hooks'
import { usePriceHistory } from 'components/Tokens/TokenDetails/ChartSection'
import {
Chain,
HistoryDuration,
TransactionStatus,
useTokenPriceQuery,
} from 'graphql/data/__generated__/types-and-hooks'
import { TimePeriod } from 'graphql/data/util'
import { getV2Prices } from 'graphql/thegraph/getV2Prices'
import useENSName from 'hooks/useENSName'
import { useCallback } from 'react'
import styled from 'styled-components'
import { ClickableText } from 'pages/Pool/styled'
import { useCallback, useEffect, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import styled, { useTheme } from 'styled-components'
import { EllipsisStyle, ThemedText } from 'theme/components'
import { shortenAddress } from 'utils'
import { useFormatter } from 'utils/formatNumbers'
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'

import { PortfolioLogo } from '../PortfolioLogo'
import { PortfolioAvatar, PortfolioLogo } from '../PortfolioLogo'
import PortfolioRow from '../PortfolioRow'
import { useOpenOffchainActivityModal } from './OffchainActivityModal'
import { useTimeSince } from './parseRemote'
Expand Down Expand Up @@ -90,3 +104,171 @@
</TraceEvent>
)
}

function isJudgementalActivity(activity: Activity | JudgementalActivity): activity is JudgementalActivity {
return (activity as JudgementalActivity).isJudgmental !== undefined
}

const ActivityCard = styled.div`
display: flex;
flex-direction: column;

gap: 20px;
padding: 20px;
width: 100%;
/* width: 420px; */

/* background-color: ${({ theme }) => theme.surface1}; */
/* border-radius: 12px; */
border-bottom: 1px solid ${({ theme }) => theme.surface3};
`
const CardHeader = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 20px;
justify-content: space-between;
white-space: nowrap;
`

const Who = styled.div`
display: flex;
flex-direction: row;
gap: 10px;
width: 100%;
`

const DescriptionContainer = styled(Row)`
white-space: nowrap;
flex-wrap: wrap;
`

function NormalFeedRow({ activity }: { activity: Activity }) {
const { ENSName } = useENSName(activity.owner)
const { ENSName: otherAccountENS } = useENSName(activity.otherAccount)
const timeSince = useTimeSince(activity.timestamp)

const navigate = useNavigate()

const shouldHide = useMemo(
() =>
activity.title.includes('Approv') ||
activity.title.includes('Contract') ||
activity.descriptor?.includes('Contract') ||
activity.title.includes('Sent') ||
activity.title?.includes('Swapped') ||
activity.title.includes('Received') ||
activity.title.includes('Unknown'),
[activity.title, activity.descriptor]
)
if (shouldHide) return null

return (
<ActivityCard>
<CardHeader>
<Who>
<PortfolioAvatar accountAddress={activity.owner} size={30} />
<ClickableText onClick={() => navigate('/account/' + ENSName ?? activity.owner)}>
<ThemedText.BodyPrimary>{ENSName ?? shortenAddress(activity.owner)}</ThemedText.BodyPrimary>
</ClickableText>
</Who>
<ThemedText.LabelSmall>{timeSince}</ThemedText.LabelSmall>
</CardHeader>
<ThemedText.BodySecondary color="neutral1">
<DescriptionContainer gap="8px">
{activity.title} {activity.descriptor}{' '}
<PortfolioLogo size="24px" chainId={1} currencies={activity.currencies} />{' '}
<ClickableText onClick={() => navigate(`/account/${otherAccountENS ?? activity.otherAccount}`)}>
<b>{otherAccountENS ?? activity.otherAccount}</b>
</ClickableText>
</DescriptionContainer>
</ThemedText.BodySecondary>
</ActivityCard>
)
}

function JudgementChart({ activity, hidePrice }: { activity: JudgementalActivity; hidePrice?: boolean }) {
const theme = useTheme()
const { data: tokenPriceQuery } = useTokenPriceQuery({
variables: {
address: activity.currency.wrapped.address,
chain: Chain.Ethereum,
duration: HistoryDuration.Year,
},
errorPolicy: 'all',
})

const prices = usePriceHistory(tokenPriceQuery)

useEffect(() => {
getV2Prices(activity.currency.wrapped.address).then(console.log)
}, [])

Check warning on line 205 in src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'activity.currency.wrapped.address'. Either include it or remove the dependency array

if (!prices) return null

return (
<ParentSize>
{({ width }) => (
<PriceChart
prices={prices}
width={width}
height={200}
timePeriod={TimePeriod.YEAR}
activity={activity.activities}
color={activity.negative ? theme.critical : theme.success}
hidePrice={hidePrice}
backupAddress={activity.currency.wrapped.address}
/>
)}
</ParentSize>
)
}

function JudgementalActivityRow({ activity, hidePrice }: { activity: JudgementalActivity; hidePrice?: boolean }) {
const { ENSName } = useENSName(activity.owner)
const timeSince = useTimeSince(activity.timestamp)
const { formatNumber } = useFormatter()

Check warning on line 230 in src/components/AccountDrawer/MiniPortfolio/Activity/ActivityRow.tsx

View workflow job for this annotation

GitHub Actions / lint

'formatNumber' is assigned a value but never used
const theme = useTheme()
const navigate = useNavigate()

return (
<ActivityCard>
<CardHeader>
<Who>
<PortfolioAvatar accountAddress={activity.owner} size={30} />
<ClickableText onClick={() => navigate('/account/' + activity.owner)}>
<ThemedText.BodyPrimary>{ENSName ?? shortenAddress(activity.owner)}</ThemedText.BodyPrimary>
</ClickableText>
</Who>
<ThemedText.LabelSmall>{timeSince}</ThemedText.LabelSmall>
</CardHeader>
<JudgementChart activity={activity} hidePrice={hidePrice} />
<ThemedText.BodySecondary color="neutral1">
<DescriptionContainer gap="8px">
{activity.description} <PortfolioLogo size="24px" chainId={1} currencies={[activity.currency]} />{' '}
<ClickableText
onClick={() =>
navigate(
'/tokens/ethereum/' + (activity.currency.isNative ? 'NATIVE' : activity.currency.wrapped.address)
)
}
>
<b>{activity.currency.symbol}</b>
</ClickableText>
<span style={{ color: activity.profit > 0 ? theme.success : theme.critical }}>
{activity.profit < 0 ? '-' : ''}(${Math.abs(activity.profit).toFixed(2)})
</span>{' '}
{activity.hodlingTimescale}
</DescriptionContainer>
</ThemedText.BodySecondary>
</ActivityCard>
)
}

export function FeedRow({ activity, hidePrice }: { activity: Activity | JudgementalActivity; hidePrice?: boolean }) {
if (!isJudgementalActivity(activity)) {
return <NormalFeedRow activity={activity} />
// return null
}
return <JudgementalActivityRow activity={activity} hidePrice={hidePrice} />
}
7 changes: 2 additions & 5 deletions src/components/AccountDrawer/MiniPortfolio/Activity/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,13 @@ function combineActivities(localMap: ActivityMap = {}, remoteMap: ActivityMap =
export function useAllActivities(account: string) {
const { formatNumberOrString } = useFormatter()
const { data, loading, refetch } = useActivityQuery({
variables: { account },
variables: { accounts: [account] },
errorPolicy: 'all',
fetchPolicy: 'cache-first',
})

const localMap = useLocalActivities(account)
const remoteMap = useMemo(
() => parseRemoteActivities(formatNumberOrString, data?.portfolios?.[0].assetActivities),
[data?.portfolios, formatNumberOrString]
)
const remoteMap = useMemo(() => parseRemoteActivities(formatNumberOrString, data), [data, formatNumberOrString])
const updateCancelledTx = useTransactionCanceller()

/* Updates locally stored pendings tx's when remote data contains a conflicting cancellation tx */
Expand Down
5 changes: 2 additions & 3 deletions src/components/AccountDrawer/MiniPortfolio/Activity/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { atom, useAtom } from 'jotai'
import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent'
import { useEffect, useMemo } from 'react'
import styled from 'styled-components'
import { ThemedText } from 'theme/components'

import { PortfolioSkeleton, PortfolioTabWrapper } from '../PortfolioRow'
import { ActivityRow } from './ActivityRow'
Expand Down Expand Up @@ -53,9 +52,9 @@ export function ActivityTab({ account }: { account: string }) {
<PortfolioTabWrapper>
{activityGroups.map((activityGroup) => (
<ActivityGroupWrapper key={activityGroup.title}>
<ThemedText.SubHeader color="neutral2" marginLeft="16px">
{/* <ThemedText.SubHeader color="neutral2" marginLeft="16px">
{activityGroup.title}
</ThemedText.SubHeader>
</ThemedText.SubHeader> */}
<Column data-testid="activity-content">
{activityGroup.transactions.map((activity) => (
<ActivityRow key={activity.hash} activity={activity} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export function transactionToActivity(
const status = getTransactionStatus(details)

const defaultFields = {
owner: details.from,
hash: details.hash,
chainId,
title: getActivityTitle(details.info.type, status),
Expand Down Expand Up @@ -249,6 +250,7 @@ export function signatureToActivity(
const { title, statusMessage, status } = OrderTextTable[signature.status]

return {
owner: signature.offerer,
hash: signature.orderHash,
chainId: signature.chainId,
title,
Expand Down
Loading
Loading