Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
97859aa
feat: redesign dashboard and campaigns pages, add new components and …
KirillKirill Mar 11, 2026
9fad8a6
chore: refactored some code, mostly a cosmetic changes
KirillKirill Mar 12, 2026
cf2972d
chore: remove network and address columns from campaigns table
KirillKirill Mar 12, 2026
822ce91
chore: implement indicator for campaign timeline
KirillKirill Mar 12, 2026
1e0a6f3
fix: address copilot comments
KirillKirill Mar 12, 2026
e7ee995
feat: rework layout and reserve a place for bottom nav on mobile
KirillKirill Mar 20, 2026
4699b4c
feat: add history filters
KirillKirill Mar 23, 2026
b3354c5
chore: change color of the exchange lable in the table
KirillKirill Mar 23, 2026
a13fdcf
chore: remove countdown logic for now
KirillKirill Mar 23, 2026
dbd1e70
chore: using radio instead of checkbox for history filters
KirillKirill Mar 23, 2026
c6245ca
chore: memoize current filter values
KirillKirill Mar 23, 2026
9baab20
chore: implement BaseDrawer component; separate CampaignFilters compo…
KirillKirill Mar 24, 2026
5740a04
chore: minor ui improvements
KirillKirill Mar 25, 2026
c922fb8
chore: hide launch campaign button, if wallet is not connected
KirillKirill Mar 25, 2026
797953f
chore: disable history filters, when there's some loading; show Load …
KirillKirill Apr 3, 2026
fcf7fdb
feat: add inifinite pagination, rework campaigns page inner components
KirillKirill Apr 6, 2026
f548e78
feat: connect type and exchange filters to the backend
KirillKirill Apr 7, 2026
7d4ed72
chore: add util that handles numbers K notation
KirillKirill Apr 8, 2026
8d2ccea
feat: add JoinCampaign button
KirillKirill Apr 8, 2026
2a01cc5
chore: remove old JoinCampaign component
KirillKirill Apr 9, 2026
7c5f682
refactor: remove unused component, improve CampaignTimeline
KirillKirill Apr 10, 2026
f884f26
refactor: change 'All' option value to 'all' instead of empty string;…
KirillKirill Apr 10, 2026
a3c3754
feat: remember user's choice of campaigns view
KirillKirill Apr 10, 2026
1e520f5
feat: handle passing multiple statuses into filter query; rename togg…
KirillKirill Apr 10, 2026
d7524f5
chore: a minor styles changes, format long numbers in the table
KirillKirill Apr 13, 2026
78a5398
refactor: separate Campaign and JoinedCampaign type
KirillKirill Apr 13, 2026
0eef069
chore: add tooltip to the chain logo in the CampaignCard
KirillKirill Apr 13, 2026
ecd79ab
chore: add Clear All button to the filters dialog
KirillKirill Apr 14, 2026
1b4f90b
feat: take user to the docs api key section
KirillKirill Apr 14, 2026
3f948c6
feat: add empty state for the campaigns feed; refactor some components
KirillKirill Apr 15, 2026
a742be8
refactor: a connect wallet component
KirillKirill Apr 15, 2026
66dee4e
fix: minor style adjustment
KirillKirill Apr 16, 2026
398079c
chore: improve loading state of the CampaignsFeed
KirillKirill Apr 16, 2026
4410a8e
refactor: changed a content of the FAQ section
KirillKirill Apr 17, 2026
b092068
chore: increase container's maxWidth
KirillKirill Apr 17, 2026
91ce23f
fix: copy in FAQ section
KirillKirill Apr 20, 2026
674864f
fix: a styles of the dashboard widget
KirillKirill Apr 20, 2026
2e982fb
chore: add icon to the MobileBottomNav
KirillKirill Apr 20, 2026
6c0f749
fix: hydration error and incorrect svg prop
KirillKirill Apr 20, 2026
1868b8e
[Campaign Launcher UI] Redesign Campaign Details page (#788)
KirillKirill Apr 20, 2026
b6ec21e
chore: address feedback
KirillKirill Apr 20, 2026
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
3 changes: 0 additions & 3 deletions campaign-launcher/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@
"@tanstack/react-query": "^5.90.21",
"@walletconnect/ethereum-provider": "^2.23.5",
"axios": "^1.13.2",
"chart.js": "^4.5.1",
"chartjs-plugin-annotation": "^3.1.0",
"dayjs": "^1.11.19",
"ethers": "~6.16.0",
"jwt-decode": "^4.0.0",
"notistack": "^3.0.2",
"react": "^19.2.5",
"react-chartjs-2": "^5.3.1",
"react-dom": "^19.2.5",
"react-hook-form": "^7.68.0",
"react-number-format": "^5.4.4",
Expand Down
9 changes: 7 additions & 2 deletions campaign-launcher/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import Layout from '@/components/Layout';
import ProtectedRoute from '@/components/ProtectedRoute';
import WalletProtectedRoute from '@/components/WalletProtectedRoute';
import { ROUTES } from '@/constants';
import Campaign from '@/pages/Campaign';
Comment thread
dnechay marked this conversation as resolved.
import CampaignDetails from '@/pages/CampaignDetails';
import Campaigns from '@/pages/Campaigns';
import Dashboard from '@/pages/Dashboard';
import LaunchCampaignPage from '@/pages/LaunchCampaign';
import ManageApiKeysPage from '@/pages/ManageApiKeys';
Expand Down Expand Up @@ -46,9 +47,13 @@ const App: FC = () => {
path={ROUTES.DASHBOARD}
element={<Dashboard />}
/>
<Route
path={ROUTES.CAMPAIGNS}
element={<Campaigns />}
/>
<Route
path={ROUTES.CAMPAIGN_DETAILS}
element={<Campaign />}
element={<CampaignDetails />}
/>
<Route
path={ROUTES.MANAGE_API_KEYS}
Expand Down
2 changes: 1 addition & 1 deletion campaign-launcher/client/src/api/launcherApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class LauncherApiClient extends HttpClient {
}

async getCampaigns(
params: Record<string, string | number>,
params: Record<string, string | number | string[]>,
signal?: AbortSignal
): Promise<CampaignsResponse> {
const response = await this.get<CampaignsResponse>('/campaigns', {
Expand Down
19 changes: 15 additions & 4 deletions campaign-launcher/client/src/api/recordingApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import axios, { AxiosError } from 'axios';
import type {
EvmAddress,
ExchangeApiKeyData,
CampaignsResponse,
UserProgress,
CheckCampaignJoinStatusResponse,
JoinedCampaignsResponse,
LeaderboardResponseDto,
} from '@/types';
import { HttpClient, HttpError } from '@/utils/HttpClient';
import type { TokenData, TokenManager } from '@/utils/TokenManager';
Expand Down Expand Up @@ -162,10 +163,10 @@ export class RecordingApiClient extends HttpClient {
}

async getJoinedCampaigns(
params: Record<string, string | number>,
params: Record<string, string | number | string[]>,
signal?: AbortSignal
): Promise<CampaignsResponse> {
const response = await this.get<CampaignsResponse>('/campaigns', {
): Promise<JoinedCampaignsResponse> {
const response = await this.get<JoinedCampaignsResponse>('/campaigns', {
params,
signal,
});
Expand Down Expand Up @@ -219,4 +220,14 @@ export class RecordingApiClient extends HttpClient {

return response || null;
}

async getLeaderboard(
chain_id: ChainId,
campaign_address: string
): Promise<LeaderboardResponseDto> {
const response = await this.get<LeaderboardResponseDto>(
`/campaigns/${chain_id}-${campaign_address}/leaderboard`
);
return response;
}
}
68 changes: 68 additions & 0 deletions campaign-launcher/client/src/components/AboutHuFi/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type FC } from 'react';

import { Link, Stack, Typography } from '@mui/material';

import logo from '@/assets/logo.svg';
import { useIsMobile } from '@/hooks/useBreakpoints';
import { ArrowLeftIcon } from '@/icons';

const DOCS_URL = import.meta.env.VITE_APP_DOCS_URL;

const AboutHuFi: FC = () => {
const isMobile = useIsMobile();

return (
<Stack gap={{ xs: 2, md: 3 }}>
<Typography
variant={isMobile ? 'h6' : 'h5'}
color="white"
fontWeight={{ xs: 500, md: 800 }}
letterSpacing={{ xs: '0px', md: '-0.5px' }}
>
About HuFi
</Typography>
<Stack
px={2}
py={{ xs: 2, md: 4 }}
borderRadius="8px"
border="1px solid #433679"
bgcolor="#251d47"
>
{!isMobile && <img src={logo} alt="HuFi" width={125} />}
<Typography
variant="body2"
color="white"
fontWeight={600}
mt={{ xs: 0, md: 4 }}
mb={2}
>
HuFi is a decentralized tradeathon organizing platform where
communities earn rewards for holding, trading, and contributing to
activities that support the token&apos;s growth.
</Typography>
<Link
href={DOCS_URL}
target="_blank"
rel="noreferrer"
sx={{
display: 'inline-flex',
alignItems: 'center',
width: 'fit-content',
color: 'error.main',
fontSize: 16,
fontWeight: 500,
textDecoration: 'underline',
textDecorationColor: 'error.main',
}}
>
Learn More
<ArrowLeftIcon
sx={{ width: 24, height: 24, transform: 'rotate(135deg)' }}
/>
</Link>
</Stack>
</Stack>
);
};

export default AboutHuFi;

This file was deleted.

103 changes: 66 additions & 37 deletions campaign-launcher/client/src/components/AllCampaigns/index.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,82 @@
import type { FC } from 'react';
import { useEffect, useState, type FC } from 'react';

import { CircularProgress } from '@mui/material';
import { Box, Button } from '@mui/material';

import CampaignsTable from '@/components/CampaignsTable';
import CampaignsTablePagination from '@/components/CampaignsTablePagination';
import CampaignsEmptyState from '@/components/CampaignsEmptyState';
import CampaignsFeed from '@/components/CampaignsFeed';
import { useCampaigns } from '@/hooks/useCampaigns';
import usePagination from '@/hooks/usePagination';
import { useNetwork } from '@/providers/NetworkProvider';
import { CampaignStatus, type CampaignsQueryParams } from '@/types';
import { filterFalsyQueryParams } from '@/utils';
import { type Campaign, type CampaignsQueryParams } from '@/types';
import { appendUniqueCampaigns } from '@/utils';

type Props = {
showOnlyActiveCampaigns: boolean;
queryParams: CampaignsQueryParams;
hasActiveFilters: boolean;
isGridView: boolean;
isHistory: boolean;
setNextPage: () => void;
};

const AllCampaigns: FC<Props> = ({ showOnlyActiveCampaigns }) => {
const { appChainId } = useNetwork();
const { params, pagination, setPageSize, setNextPage, setPrevPage } =
usePagination();
const { limit, skip } = params;
const { page, pageSize } = pagination;

const queryParams = filterFalsyQueryParams({
chain_id: appChainId,
status: showOnlyActiveCampaigns ? CampaignStatus.ACTIVE : undefined,
limit,
skip,
}) as CampaignsQueryParams;
const AllCampaigns: FC<Props> = ({
queryParams,
hasActiveFilters,
isGridView,
isHistory,
setNextPage,
}) => {
const [campaigns, setCampaigns] = useState<Campaign[]>([]);

const { data, isLoading, isFetching } = useCampaigns(queryParams);

const currentSkip = queryParams.skip ?? 0;

useEffect(() => {
if (!data) return;

setCampaigns((prev) => {
if (currentSkip === 0) return data.results;
if (isFetching) return prev;
return appendUniqueCampaigns(prev, data.results);
});
}, [data, currentSkip, isFetching]);

const showLoadMore = isLoading || isFetching || data?.has_more;

const showEmptyState = !isLoading && !isFetching && campaigns.length === 0;

return (
<>
{isLoading && (
<CircularProgress sx={{ width: '40px', height: '40px', mx: 'auto' }} />
{showEmptyState ? (
<CampaignsEmptyState
view="all"
hasActiveFilters={hasActiveFilters}
isHistory={isHistory}
/>
) : (
<CampaignsFeed
data={campaigns}
isGridView={isGridView}
isLoading={isLoading}
isFetching={isFetching}
/>
)}
{!isLoading && (
<>
<CampaignsTable data={data?.results || []} isFetching={isFetching} />
<CampaignsTablePagination
page={page}
resultsLength={data?.results?.length || 0}
hasMore={data?.has_more}
pageSize={pageSize}
setPageSize={setPageSize}
setNextPage={setNextPage}
setPrevPage={setPrevPage}
/>
</>
{showLoadMore && (
<Box
display="flex"
justifyContent="center"
alignItems="center"
mx={{ xs: 0, md: 'auto' }}
mt={4}
>
<Button
variant="contained"
color="error"
disabled={isFetching || isLoading}
sx={{ width: { xs: '100%', md: '200px' } }}
onClick={setNextPage}
>
Load More
</Button>
</Box>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const AddressLink: FC<Props> = ({ address, chainId, size }) => {
size === 'small' ? '12px' : size === 'medium' ? '14px' : '16px',
color: 'text.primary',
textDecoration: 'underline',
textDecorationStyle: 'dotted',
textDecorationThickness: '12%',
fontWeight: 600,
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Paper, Skeleton, Stack } from '@mui/material';

const SkeletonCard = () => {
return (
<Paper
sx={{
display: 'flex',
p: 2,
gap: 1.5,
flexDirection: 'column',
bgcolor: '#251d47',
borderRadius: '8px',
border: '1px solid #433679',
boxShadow: 'none',
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Skeleton variant="text" width={140} height={28} />
<Skeleton variant="text" width={125} height={28} />
</Stack>
<Stack justifyContent="center" gap={1}>
<Skeleton variant="text" width={150} height={30} />
<Skeleton variant="text" width={150} height={20} />
</Stack>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
gap={2}
>
<Skeleton variant="rectangular" height={90} sx={{ flex: 1 }} />
<Skeleton variant="rectangular" height={90} sx={{ flex: 1 }} />
</Stack>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
gap={2}
>
<Skeleton variant="rectangular" height={44} sx={{ flex: 1 }} />
<Skeleton variant="rectangular" height={44} sx={{ flex: 1 }} />
</Stack>
</Paper>
);
};

export default SkeletonCard;
Loading