Skip to content

Commit

Permalink
Merge branch 'main' into issue-#774
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagofilipenunes committed Apr 15, 2024
2 parents 0485dcd + c972a5f commit 69ed837
Show file tree
Hide file tree
Showing 19 changed files with 452 additions and 157 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/components/activity/ActivityNotification.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.icon::before {
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
transform-origin: center;
background-color: currentColor;
opacity: 0;
transform: scale(0);
animation: ripple 2s 0.9s cubic-bezier(0.16, 1, 0.3, 1);
pointer-events: none;
}
.icon {
animation: pulse 1.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.icon g {
animation: enter 1.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.icon line {
animation: scale 1.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes pulse {
0% {
transform: scale(0.3);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}

@keyframes enter {
0%, 30% {
transform: translateX(-100%);
}
100% {
transform: translateX(0);
}
}

@keyframes scale {
0%, 60% {
transform: scale(0.3);
}
100% {
transform: scale(1);
}
}

@keyframes ripple {
from {
transform: scale(0);
opacity: 1;
}
to {
transform: scale(20);
opacity: 0;
}
}
106 changes: 106 additions & 0 deletions src/components/activity/ActivityNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { FC, useId } from 'react';
import { NotificationActivity } from 'libs/notifications/types';
import { activityActionName, activityDescription } from './utils';
import { ReactComponent as IconClose } from 'assets/icons/X.svg';
import { Link } from '@tanstack/react-router';
import { cn } from 'utils/helpers';
import { ActivityAction } from 'libs/queries/extApi/activity';
import { unix } from 'dayjs';
import style from './ActivityNotification.module.css';

interface Props {
notification: NotificationActivity;
close: () => void;
}

export const ActivityNotification: FC<Props> = ({ notification, close }) => {
const titleId = useId();
const { activity } = notification;

return (
<article aria-labelledby={titleId} className="flex gap-16">
<AnimatedActionIcon action={activity.action} />
<div className="text-14 flex flex-1 flex-col gap-8 overflow-hidden">
<hgroup>
<h3 className="text-16" id={titleId} data-testid="notif-title">
{activityActionName[activity.action]}
</h3>
<p className="truncate text-white/60" data-testid="notif-description">
{activityDescription(activity)}
</p>
</hgroup>
<Link
to="/strategy/$id"
params={{ id: activity.strategy.id }}
className="font-weight-500 flex items-center"
>
View Activity
</Link>
</div>
<div className="flex flex-col items-end justify-between">
<button
onClick={close}
data-testid="notif-close"
aria-label="Remove notification"
>
<IconClose className="h-14 w-14 text-white/80" />
</button>

<p className="text-12 font-weight-500 whitespace-nowrap text-white/60">
{unix(notification.timestamp).fromNow(true)}
</p>
</div>
</article>
);
};

export const AnimatedActionIcon = (props: { action: ActivityAction }) => {
const transform = props.action === 'buy' ? 'rotate(30deg)' : 'rotate(-30deg)';
return (
<div
className={cn(
'h-38 w-38 relative grid place-items-center rounded-full',
style.icon,
{
'bg-buy/20 text-buy': props.action === 'buy',
'bg-sell/20 text-sell': props.action === 'sell',
}
)}
>
<svg width="24" height="24" viewBox="0 0 100 100" style={{ transform }}>
<g>
<line
x1="20"
x2="75"
y1="50"
y2="50"
stroke="currentColor"
stroke-width="8"
stroke-linecap="round"
transform-origin="75 50"
/>
<line
x1="50"
x2="75"
y1="25"
y2="50"
stroke="currentColor"
stroke-width="8"
stroke-linecap="round"
transform-origin="75 50"
/>
<line
x1="50"
x2="75"
y1="75"
y2="50"
stroke="currentColor"
stroke-width="8"
stroke-linecap="round"
transform-origin="75 50"
/>
</g>
</svg>
</div>
);
};
42 changes: 42 additions & 0 deletions src/components/activity/useActivityNotifiction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useWeb3 } from 'libs/web3';
import { useActivityQuery } from './useActivityQuery';
import { useEffect, useState } from 'react';
import { useNotifications } from 'hooks/useNotifications';

export const useActivityNotifications = () => {
const { user } = useWeb3();
const [previousUser, setPreviousUser] = useState<string | null>(null);
const [previous, setPrevious] = useState<number | null>(null);
const query = useActivityQuery({ ownerId: user });
const allActivities = query.data || [];
const buyOrSell = allActivities.filter(
(a) => a.action === 'sell' || a.action === 'buy'
);
const { dispatchNotification } = useNotifications();

useEffect(() => {
if (query.isLoading) return;
// We need to keep this in the same useEffect to force re-evaluate previous in next render
if (user && user !== previousUser) {
setPreviousUser(user);
setPrevious(null);
return;
}
const length = buyOrSell.length;
if (typeof previous === 'number' && length > previous) {
// Sorted by date desc
for (let i = 0; i < length - previous; i++) {
const activity = buyOrSell[i];
dispatchNotification('activity', { activity });
}
}
setPrevious(length);
}, [
buyOrSell,
dispatchNotification,
previous,
query.isLoading,
previousUser,
user,
]);
};
12 changes: 10 additions & 2 deletions src/components/activity/useActivityQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from '@tanstack/react-query';
import { isAddress } from 'ethers/lib/utils';
import { useTokens } from 'hooks/useTokens';
import { QueryKey } from 'libs/queries';
import {
Expand All @@ -8,6 +9,7 @@ import {
} from 'libs/queries/extApi/activity';
import { Token } from 'libs/tokens';
import { carbonApi } from 'utils/carbonApi';
import { THIRTY_SEC_IN_MS } from 'utils/time';

const toActivities = (
data: ServerActivity[],
Expand Down Expand Up @@ -39,8 +41,14 @@ const toActivities = (
});
};

const isValidParams = (params: QueryActivityParams) => {
if ('ownerId' in params && !isAddress(params.ownerId ?? '')) return false;
return true;
};

export const useActivityQuery = (params: QueryActivityParams = {}) => {
const { tokensMap, isLoading } = useTokens();
const validParams = isValidParams(params);
return useQuery(
QueryKey.activities(params),
async () => {
Expand All @@ -50,8 +58,8 @@ export const useActivityQuery = (params: QueryActivityParams = {}) => {
});
},
{
enabled: !isLoading,
refetchInterval: 30 * 1000,
enabled: !isLoading && validParams,
refetchInterval: THIRTY_SEC_IN_MS,
}
);
};
2 changes: 2 additions & 0 deletions src/components/core/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useRouterState,
useScrollRestoration,
} from 'libs/routing';
import { useActivityNotifications } from 'components/activity/useActivityNotifiction';

const paths: Record<string, Pathnames> = {
debug: '/debug',
Expand All @@ -28,6 +29,7 @@ export const MainContent: FC = () => {
const tokens = useTokens();
const sdk = useCarbonInit();
useScrollRestoration();
useActivityNotifications();

useEffect(() => {
if (prevPathnameRef.current !== location.pathname) {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const useNotifications = () => {
};

const checkStatus = async (n: Notification) => {
if (!n.txHash || !provider) return;
if (n.type !== 'tx' || !n.txHash || !provider) return;
try {
const tx = await provider.getTransactionReceipt(n.txHash);
if (tx && tx.status !== null) {
Expand Down
26 changes: 15 additions & 11 deletions src/libs/modals/modals/ModalNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,36 @@ import { NotificationLine } from 'libs/notifications/NotificationLine';
import { useNotifications } from 'hooks/useNotifications';

export const ModalNotifications: ModalFC<undefined> = ({ id }) => {
const { notifications, clearNotifications } = useNotifications();
const { notifications, clearNotifications, removeNotification } =
useNotifications();
const reversedNotifications = notifications.slice().reverse();

return (
<ModalSlideOver
id={id}
title={
<div className="flex w-full items-center justify-between">
<header className="flex w-full items-center justify-between">
Notifications
<button onClick={() => clearNotifications()} className="mr-20">
Clear All
</button>
</div>
</header>
}
size="md"
>
<div className="mt-25 space-y-10">
{reversedNotifications.map((notification) => (
<div
key={notification.id}
className="rounded-10 bg-black px-20 py-10"
<ul className="mt-25 flex flex-col gap-10">
{reversedNotifications.map((n) => (
<li
key={n.id}
className="rounded-10 overflow-hidden bg-black px-16 py-12"
>
<NotificationLine notification={notification} />
</div>
<NotificationLine
notification={n}
close={() => removeNotification(n.id)}
/>
</li>
))}
</div>
</ul>
</ModalSlideOver>
);
};
Loading

0 comments on commit 69ed837

Please sign in to comment.