Skip to content

Commit

Permalink
feat: 🎸 add events and joint airdrop activities (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
leosley-m authored May 31, 2024
1 parent 00b2258 commit b7b4d6e
Show file tree
Hide file tree
Showing 52 changed files with 962 additions and 195 deletions.
4 changes: 2 additions & 2 deletions src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ class Request {
return {};
case '50000':
message.error(errorMessage);
return null;
return Promise.reject(errorMessage);
default:
message.error(errorMessage);
return res;
return Promise.reject(res);
}
},
(error) => {
Expand Down
33 changes: 33 additions & 0 deletions src/api/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,36 @@ export const bindAddress = async (data: {
}): Promise<void> => {
return request.post('/app/bind-address', data);
};

export const getActivityDetailJoint = async (): Promise<{ data: IActivityDetailData }> => {
return cmsRequest.get('/items/activity_detail_joint');
};

export const activityInfo = async (): Promise<{
hasNewActivity: boolean;
}> => {
return request.get('/app/activity/info');
};

export const activityList = async (params: { skipCount?: number; maxResultCount?: number }): Promise<IActivityList> => {
return request.get('/app/activity/list', { params });
};

export const bindAddressActivity = async (data: {
aelfAddress: string;
sourceChainAddress: string;
signature: string;
publicKey: string;
activityId: string;
}): Promise<void> => {
return request.post('/app/activity/bind-address', data);
};

export const addressRelation = async (data: {
aelfAddress: string;
activityId: string;
}): Promise<{
sourceChainAddress?: string;
}> => {
return request.post('/app/activity/address-relation', data);
};
20 changes: 20 additions & 0 deletions src/api/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ interface ICatsListParams {
interface ICatDetailParams {
chainId: string;
symbol: string;
address?: string; // wallet address
}

interface ITransactionMessageListParams {
Expand Down Expand Up @@ -191,3 +192,22 @@ interface ITransactionMessageListData {
totalCount: number;
data: ITransactionMessageListItem[];
}

type TLinkType = 'externalLink' | 'link';

interface IActivityListItem {
bannerUrl?: string;
activityName?: string;
activityId: string;
beginTime?: string;
endTime?: string;
isNew?: boolean;
linkUrl?: string;
linkType?: TLinkType;
}

interface IActivityList {
hasNewActivity: boolean;
totalCount: number;
items: IActivityListItem[];
}
9 changes: 9 additions & 0 deletions src/app/activity-detail-joint/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client';

import dynamic from 'next/dynamic';
import { DynamicLoading } from 'components/DynamicLoading';

export default dynamic(() => import('pageComponents/activity-detail-joint'), {
ssr: false,
loading: () => <DynamicLoading />,
});
6 changes: 6 additions & 0 deletions src/app/event-list/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client';

import dynamic from 'next/dynamic';
import { DynamicLoading } from 'components/DynamicLoading';

export default dynamic(() => import('pageComponents/event-list'), { ssr: false, loading: () => <DynamicLoading /> });
1 change: 1 addition & 0 deletions src/assets/img/event/tag-hot.json

Large diffs are not rendered by default.

Binary file added src/assets/img/event/tag-new-square.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/event/tag-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions src/assets/img/icons/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/logo/schrodinger-b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/logo/schrodinger-w.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion src/components/EmptyList/components/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import clsx from 'clsx';
import SkeletonImage from 'components/SkeletonImage';
import { etransferDomain } from 'constants/url';
import { useJumpToPage } from 'hooks/useJumpToPage';
import { useResponsive } from 'hooks/useResponsive';
import React from 'react';
import { TEmptyChannelBanner } from 'types/misc';
import { AdTracker } from 'utils/ad';

function Banner({ banner }: { banner: TEmptyChannelBanner }) {
const { isLG } = useResponsive();
const { jumpToPage } = useJumpToPage();

const onBannerClick = () => {
jumpToPage({ link: banner?.link, linkType: banner?.linkType });
if (banner?.link?.includes(etransferDomain)) {
AdTracker.trackEvent('user_click_etransfer_noneSGR');
}
};

return (
<div
className={clsx('flex w-full h-auto first:mt-0 mt-[8px] overflow-hidden', banner?.link ? 'cursor-pointer' : '')}
onClick={() => jumpToPage({ link: banner?.link, linkType: banner?.linkType })}>
onClick={onBannerClick}>
<SkeletonImage
img={isLG ? banner.imgUrl.mobile || '' : banner.imgUrl.pc || ''}
className="w-full h-auto"
Expand Down
69 changes: 69 additions & 0 deletions src/components/EventScrollList/components/EventList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import SkeletonImage from 'components/SkeletonImage';
import React, { useMemo } from 'react';
import moment from 'moment';
import { useJumpToPage } from 'hooks/useJumpToPage';
import { ZERO } from 'constants/misc';

function EventList({
bannerUrl,
activityName,
beginTime,
endTime,
isNew = false,
linkUrl,
linkType = 'link',
}: IActivityListItem) {
const { jumpToPage } = useJumpToPage();

const isExpired = useMemo(() => {
if (!endTime) return false;
const now = new Date().getTime();
return ZERO.plus(now).gte(ZERO.plus(endTime));
}, [endTime]);

const formatTime = useMemo(() => {
if (!endTime || !beginTime) return false;

return `${moment(Number(beginTime)).utc().format('YYYY/MM/DD')} (UTC) ~ ${moment(Number(endTime))
.utc()
.format('YYYY/MM/DD')} (UTC)`;
}, [beginTime, endTime]);

return (
<div className="lg:px-[22px]">
<div className="p-[2px] relative">
<div
className="relative w-full h-auto cursor-pointer shadow-cardShadow2 overflow-hidden rounded-lg mb-[16px] lg:mb-[24px]"
onClick={() =>
jumpToPage({
link: linkUrl,
linkType,
})
}>
<div className="w-full h-[128px]">
<SkeletonImage
img={bannerUrl}
imageSizeType="cover"
className="w-full h-full overflow-visible"
imageClassName="rounded-none"
/>
</div>

<div className="p-[16px]">
<p className="text-base text-neutralTitle font-medium mb-[8px]">{activityName}</p>
{formatTime ? <p className="text-xs text-neutralSecondary">{formatTime}</p> : null}
</div>
{isExpired ? <div className="absolute top-0 left-0 w-full h-full bg-neutralWhiteBg opacity-60 z-10" /> : null}
</div>
{isNew ? (
<div className="absolute top-0 right-0">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={require('assets/img/event/tag-new.png').default.src} alt="new" className="w-[44px] h-auto" />
</div>
) : null}
</div>
</div>
);
}

export default React.memo(EventList);
143 changes: 143 additions & 0 deletions src/components/EventScrollList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import InfiniteScrollList from 'components/InfiniteScrollList';
import { PAGE_CONTAINER_ID } from 'constants/index';
import { useDebounceFn } from 'ahooks';
import Loading from 'components/Loading';
import EventList from './components/EventList';
import TableEmpty from 'components/TableEmpty';
import clsx from 'clsx';
import { activityList } from 'api/request';

const endMessage = (
<div className="text-textSecondary text-base font-medium pt-[28px] pb-[28px] text-center">No more yet~</div>
);

const emptyCom = <TableEmpty title="No events" description="There is no event yet" />;

function EventScrollList(props?: { useInfiniteScroll?: boolean }) {
const { useInfiniteScroll = true } = props || {};
const [hasMore, setHasMore] = useState<boolean>(true);
const [dataSource, setDataSource] = useState<IActivityListItem[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [loadingMore, setLoadingMore] = useState<boolean>(false);
const [pageSize, setPageSize] = useState<number>(0);

const loader = useMemo(
() => (
<div className={clsx('w-full flex justify-center items-center py-[12px]', pageSize <= 1 ? 'pt-[60px]' : '')}>
<Loading size={pageSize <= 1 ? 'default' : 'small'} />
</div>
),
[pageSize],
);

const getEventList = async (pageSize: number) => {
try {
if (loadingMore || !hasMore) return;
const res = await activityList({
skipCount: (pageSize - 1) * 20,
maxResultCount: 20,
});
if (pageSize === 1) {
setDataSource(res.items || []);
} else {
setDataSource((data) => {
return [...data, ...(res?.items || [])];
});
}

if (dataSource.length + res?.items.length === res.totalCount) {
setHasMore(false);
}
} finally {
setLoading(false);
setLoadingMore(false);
}
};

const loadMoreData = async () => {
try {
if (loadingMore || !hasMore) return;
setPageSize((prev) => ++prev);
} catch (error) {
/* empty */
}
};

const { run } = useDebounceFn(loadMoreData, {
wait: 100,
});

useEffect(() => {
if (pageSize) {
if (pageSize > 1) setLoadingMore(true);
getEventList(pageSize);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageSize]);

const handleScroll = useCallback(
async (event: Event) => {
const target = event.target as HTMLElement;
if (target.scrollHeight - target.scrollTop - target.clientHeight <= 75) {
run();
}
},
[run],
);

const init = async () => {
try {
setLoading(true);
setPageSize(1);
} catch (error) {
/* empty */
}
};

useEffect(() => {
document.querySelector(`#${PAGE_CONTAINER_ID}`)?.addEventListener('scroll', handleScroll);
return () => {
document.querySelector(`#${PAGE_CONTAINER_ID}`)?.removeEventListener('scroll', handleScroll);
};
}, [handleScroll]);

useEffect(() => {
init();
}, []);

if (useInfiniteScroll) {
return (
<InfiniteScrollList
infiniteScrollProps={{
next: loadMoreData,
dataLength: dataSource.length,
hasMore,
height: '100%',
loader: (loadingMore || loading) && hasMore ? loader : <></>,
endMessage: endMessage,
}}
listProps={{
dataSource,
locale: {
emptyText: !(loadingMore || loading) ? emptyCom : <></>,
},
renderItem: (item) => <EventList {...item} />,
}}
/>
);
} else {
return (
<div>
{dataSource.map((item) => {
return <EventList key={item.activityId} {...item} />;
})}
{(loadingMore || loading) && hasMore ? loader : null}
{!hasMore && !!dataSource.length && endMessage}
{!loading && !loadingMore && !dataSource.length && emptyCom}
</div>
);
}
}

export default memo(EventScrollList);
Loading

0 comments on commit b7b4d6e

Please sign in to comment.