- {new Date(participant.submission.submissionDate).toLocaleDateString(
- 'en-US',
- {
- day: 'numeric',
- month: 'short',
- year: 'numeric',
- }
- )}
+ {new Date(
+ participant.submission.submissionDate ??
+ participant.submission.submittedAt ??
+ ''
+ ).toLocaleDateString('en-US', {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ })}
{isShortlisted && organizationId && hackathonId && (
diff --git a/components/organization/hackathons/details/HackathonSelector.tsx b/components/organization/hackathons/details/HackathonSelector.tsx
index e67f63a0..1e044c28 100644
--- a/components/organization/hackathons/details/HackathonSelector.tsx
+++ b/components/organization/hackathons/details/HackathonSelector.tsx
@@ -83,6 +83,9 @@ export default function HackathonSelector({
const handleNewHackathonClick = () => {
if (!organizationId) return;
+ setIsNavigating(true);
+ setIsOpen(false);
+ onToggle?.(false);
router.push(`/organizations/${organizationId}/hackathons/new`);
};
@@ -161,14 +164,18 @@ export default function HackathonSelector({
))}
-
-
-
- New Hackathon
-
+ {organizationId && (
+ <>
+
+
+
+ New Hackathon
+
+ >
+ )}
);
diff --git a/components/profile/ProfileDataClient.tsx b/components/profile/ProfileDataClient.tsx
index b5507cf9..3d8fbed9 100644
--- a/components/profile/ProfileDataClient.tsx
+++ b/components/profile/ProfileDataClient.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useState } from 'react';
+import React, { useState } from 'react';
import { Filter } from 'lucide-react';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import ProfileOverview from './ProfileOverview';
@@ -33,7 +33,7 @@ const FILTER_OPTIONS = [
'All Time',
];
-export default function ProfileDataClient({ user }: ProfileDataClientProps) {
+const ProfileDataClient: React.FC
= ({ user }) => {
const [selectedFilter, setSelectedFilter] = useState('All');
// Get current session user to determine if it's the user's own profile
const { data: session } = authClient.useSession();
@@ -50,6 +50,10 @@ export default function ProfileDataClient({ user }: ProfileDataClientProps) {
avatarUrl: org.organization?.logo || '/blog1.jpg',
})) || [];
+ const handleSelectFilter = (filter: string) => {
+ setSelectedFilter(filter);
+ };
+
return (
(
setSelectedFilter(filter)}
+ onClick={() => handleSelectFilter(filter)}
className={
selectedFilter === filter
? 'bg-zinc-800'
- : 'hover:!bg-zinc-600/50 hover:!text-white'
+ : 'hover:bg-zinc-600/50! hover:text-white!'
}
>
{filter}
@@ -136,4 +140,6 @@ export default function ProfileDataClient({ user }: ProfileDataClientProps) {
);
-}
+};
+
+export default ProfileDataClient;
diff --git a/components/profile/ProfileOverview.tsx b/components/profile/ProfileOverview.tsx
index 3113e36f..d84a99fa 100644
--- a/components/profile/ProfileOverview.tsx
+++ b/components/profile/ProfileOverview.tsx
@@ -24,8 +24,7 @@ export default function ProfileOverview({
const nameParts = user.name?.split(' ') || [];
const profileData: UserProfile = {
username: user.username,
- displayName:
- `${nameParts[0] || ''} ${nameParts.slice(1).join(' ') || ''}`.trim(),
+ displayName: `${nameParts[0] || ''} ${nameParts.slice(1).join(' ')}`.trim(),
bio: user.profile?.bio || 'No bio available',
avatarUrl: user.image || '/',
socialLinks: user.profile?.socialLinks || {},
diff --git a/components/profile/PublicEarningsTab.tsx b/components/profile/PublicEarningsTab.tsx
index fa50f6ae..a09dd461 100644
--- a/components/profile/PublicEarningsTab.tsx
+++ b/components/profile/PublicEarningsTab.tsx
@@ -18,6 +18,7 @@ import {
} from '@/types/earnings';
import { getPublicEarnings } from '@/lib/api/earnings';
import EmptyState from '@/components/EmptyState';
+import clsx from 'clsx';
interface PublicEarningsTabProps {
username: string;
@@ -25,12 +26,32 @@ interface PublicEarningsTabProps {
const SOURCE_CONFIG: Record<
EarningSource,
- { label: string; icon: typeof Trophy; color: string }
+ { label: string; icon: typeof Trophy; color: string; bgColor: string }
> = {
- hackathons: { label: 'Hackathons', icon: Trophy, color: 'text-amber-400' },
- grants: { label: 'Grants', icon: Award, color: 'text-green-400' },
- crowdfunding: { label: 'Crowdfunding', icon: Users, color: 'text-blue-400' },
- bounties: { label: 'Bounties', icon: Target, color: 'text-purple-400' },
+ hackathons: {
+ label: 'Hackathons',
+ icon: Trophy,
+ color: 'text-amber-400',
+ bgColor: 'bg-amber-400',
+ },
+ grants: {
+ label: 'Grants',
+ icon: Award,
+ color: 'text-green-400',
+ bgColor: 'bg-green-400',
+ },
+ crowdfunding: {
+ label: 'Crowdfunding',
+ icon: Users,
+ color: 'text-blue-400',
+ bgColor: 'bg-blue-400',
+ },
+ bounties: {
+ label: 'Bounties',
+ icon: Target,
+ color: 'text-purple-400',
+ bgColor: 'bg-purple-400',
+ },
};
const formatCurrency = (amount: number, currency = 'USD'): string => {
@@ -55,7 +76,10 @@ const EarningActivityItem = ({
return (
@@ -91,7 +115,10 @@ const PublicEarningsTab = ({
try {
setLoading(true);
setError(null);
- const response = await getPublicEarnings({ username });
+ const response = await getPublicEarnings({
+ username,
+ signal: controller.signal,
+ });
if (!controller.signal.aborted) {
setEarnings(response.data);
}
@@ -128,7 +155,6 @@ const PublicEarningsTab = ({
type='compact'
title='No Earnings Data'
description={error || 'No earnings data available for this user yet.'}
- action={<>>}
/>
);
}
@@ -187,7 +213,10 @@ const PublicEarningsTab = ({
return (
{config.label}
diff --git a/hooks/hackathon/use-hackathon-transform.ts b/hooks/hackathon/use-hackathon-transform.ts
index 802d9a0a..c9236997 100644
--- a/hooks/hackathon/use-hackathon-transform.ts
+++ b/hooks/hackathon/use-hackathon-transform.ts
@@ -107,7 +107,11 @@ export function useHackathonTransform() {
let prizeCurrency = 'USDC';
if (hackathon.prizeTiers && hackathon.prizeTiers.length > 0) {
prizePoolTotal = hackathon.prizeTiers.reduce(
- (sum, tier) => sum + Number(tier.prizeAmount || 0),
+ (sum, tier) =>
+ sum +
+ Number(
+ tier.prizeAmount ?? (tier as { amount?: string }).amount ?? 0
+ ),
0
);
prizeCurrency = hackathon.prizeTiers[0]?.currency || 'USDC';
diff --git a/hooks/hackathon/use-hackathons-list.ts b/hooks/hackathon/use-hackathons-list.ts
index b52d3e79..fe9ce007 100644
--- a/hooks/hackathon/use-hackathons-list.ts
+++ b/hooks/hackathon/use-hackathons-list.ts
@@ -79,7 +79,11 @@ export const useHackathonsList: (
(hackathon: Hackathon): number => {
if (hackathon?.prizeTiers && hackathon?.prizeTiers.length > 0) {
return hackathon?.prizeTiers.reduce(
- (sum, tier) => sum + Number(tier.prizeAmount || 0),
+ (sum, tier) =>
+ sum +
+ Number(
+ tier.prizeAmount ?? (tier as { amount?: string }).amount ?? 0
+ ),
0
);
}
diff --git a/hooks/hackathon/use-organizer-submissions.ts b/hooks/hackathon/use-organizer-submissions.ts
index c5f41df0..504b6eb0 100644
--- a/hooks/hackathon/use-organizer-submissions.ts
+++ b/hooks/hackathon/use-organizer-submissions.ts
@@ -15,10 +15,30 @@ const DEFAULT_PAGINATION = (limit: number) => ({
totalPages: 0,
});
+export interface UseOrganizerSubmissionsReturn {
+ submissions: ParticipantSubmission[];
+ pagination: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ };
+ filters: OrganizerSubmissionFilters;
+ updateFilters: (next: OrganizerSubmissionFilters) => void;
+ loading: boolean;
+ error: string | null;
+ fetchSubmissions: (
+ page?: number,
+ filterOverrides?: OrganizerSubmissionFilters
+ ) => Promise
;
+ goToPage: (page: number) => void;
+ refresh: () => void;
+}
+
export function useOrganizerSubmissions(
hackathonId: string,
initialLimit = 12
-) {
+): UseOrganizerSubmissionsReturn {
const [submissions, setSubmissions] = useState([]);
const [pagination, setPagination] = useState(() =>
DEFAULT_PAGINATION(initialLimit)
diff --git a/hooks/hackathon/use-submissions.ts b/hooks/hackathon/use-submissions.ts
index 2e192228..76430ec1 100644
--- a/hooks/hackathon/use-submissions.ts
+++ b/hooks/hackathon/use-submissions.ts
@@ -23,10 +23,10 @@ export function useSubmissions() {
// If we're an organizer, we might want to see the full list of private submissions.
const allSubmissions =
exploreSubmissions.length > 0 ? exploreSubmissions : privateSubmissions;
-
+ console.log({ exploreSubmissions });
const submissions = useMemo(() => {
if (isOrganizer) return allSubmissions;
-
+ console.log({ allSubmissions });
let filtered = allSubmissions;
// Check who can view submissions
@@ -86,6 +86,7 @@ export function useSubmissions() {
}, [submissions]);
const filteredAndSortedSubmissions = useMemo(() => {
+ console.log({ submissions });
let filtered = submissions;
// Filter by search term
diff --git a/hooks/use-auth.ts b/hooks/use-auth.ts
index fa402b30..6bd9dbee 100644
--- a/hooks/use-auth.ts
+++ b/hooks/use-auth.ts
@@ -2,6 +2,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useCallback, useState } from 'react';
import { authClient } from '@/lib/auth-client';
import { getMe } from '@/lib/api/auth';
+import { GetMeResponse } from '@/lib/api/types';
export function useAuth(requireAuth = true) {
const {
@@ -11,8 +12,7 @@ export function useAuth(requireAuth = true) {
} = authClient.useSession();
const router = useRouter();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const [userProfile, setUserProfile] = useState(null);
+ const [userProfile, setUserProfile] = useState(null);
const [profileLoading, setProfileLoading] = useState(false);
const user = useMemo(() => {
@@ -98,8 +98,7 @@ export function useOptionalAuth() {
export function useAuthStatus() {
const { data: session, isPending: sessionPending } = authClient.useSession();
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const [userProfile, setUserProfile] = useState(null);
+ const [userProfile, setUserProfile] = useState(null);
const [profileLoading, setProfileLoading] = useState(false);
const user = useMemo(() => {
diff --git a/hooks/use-participant-submission.ts b/hooks/use-participant-submission.ts
index 782752ea..8f536296 100644
--- a/hooks/use-participant-submission.ts
+++ b/hooks/use-participant-submission.ts
@@ -92,9 +92,12 @@ export const transformParticipantToSubmission = (
projectName: participant.submission.projectName,
category: participant.submission.category,
description: participant.submission.description,
- votes: votesCount,
- comments: commentsCount,
- submissionDate: participant.submission.submissionDate,
+ votes: votesCount ?? 0,
+ comments: commentsCount ?? 0,
+ submissionDate:
+ participant.submission.submissionDate ??
+ participant.submission.submittedAt ??
+ '',
videoUrl: participant.submission.videoUrl,
introduction: participant.submission.introduction,
logo: participant.submission.logo,
diff --git a/lib/api/api.ts b/lib/api/api.ts
index fec7050f..6d3ebba7 100644
--- a/lib/api/api.ts
+++ b/lib/api/api.ts
@@ -32,6 +32,7 @@ export interface ApiError {
export interface RequestConfig {
headers?: Record;
timeout?: number;
+ signal?: AbortSignal;
}
const createClientApi = (): AxiosInstance => {
@@ -171,12 +172,13 @@ const convertRequestConfig = (config?: RequestConfig): AxiosRequestConfig => {
...config?.headers, // Custom headers override defaults
};
- const axiosConfig = {
+ const axiosConfig: AxiosRequestConfig = {
headers: mergedHeaders,
timeout: config?.timeout,
+ signal: config?.signal,
// Credentials are handled by Next.js proxy automatically
withCredentials: false,
- } as AxiosRequestConfig;
+ };
return axiosConfig;
};
diff --git a/lib/api/earnings.ts b/lib/api/earnings.ts
index db540bfc..8619f388 100644
--- a/lib/api/earnings.ts
+++ b/lib/api/earnings.ts
@@ -5,14 +5,20 @@ export interface GetPublicEarningsParams {
username: string;
limit?: number;
offset?: number;
+ signal?: AbortSignal;
}
export const getPublicEarnings = async ({
username,
limit = 100,
offset = 0,
+ signal,
}: GetPublicEarningsParams): Promise> => {
- if (!username || typeof username !== 'string') {
+ if (typeof username !== 'string') {
+ throw new Error('Username is required and must be a string');
+ }
+ const trimmedUsername = username.trim();
+ if (!trimmedUsername) {
throw new Error('Username is required and must be a string');
}
@@ -20,12 +26,13 @@ export const getPublicEarnings = async ({
const sanitizedOffset = Math.max(0, offset);
const params = new URLSearchParams({
- username: username.trim(),
+ username: trimmedUsername,
limit: sanitizedLimit.toString(),
offset: sanitizedOffset.toString(),
});
return api.get(
- `/users/earnings/public?${params.toString()}`
+ `/users/earnings/public?${params.toString()}`,
+ { signal }
);
};
diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts
index 2efa459d..2ec62463 100644
--- a/lib/api/hackathons.ts
+++ b/lib/api/hackathons.ts
@@ -109,11 +109,14 @@ export interface HackathonParticipation {
// Rewards Tab Types
export interface PrizeTier {
id?: string;
+ name?: string;
place?: string; // Changed from position to place
currency?: string;
passMark?: number; // 0-100
description?: string;
prizeAmount?: string; // Changed from number to string to match API
+ /** @deprecated Use prizeAmount. Kept for API compatibility. */
+ amount?: string;
}
export interface HackathonRewards {
@@ -683,10 +686,11 @@ export interface ParticipantSubmission {
videoUrl?: string;
introduction?: string;
links?: Array<{ type: string; url: string }>;
- votes: number | ParticipantVote[]; // Can be a number or array of vote objects
- comments: number | ParticipantComment[]; // Can be a number or array of comment objects
- submissionDate: string;
- status: 'submitted' | 'shortlisted' | 'disqualified';
+ votes?: number | ParticipantVote[];
+ comments?: number | ParticipantComment[];
+ submissionDate?: string;
+ submittedAt?: string;
+ status: 'submitted' | 'shortlisted' | 'disqualified' | string;
disqualificationReason?: string | null;
reviewedBy?: {
id: string;
@@ -708,8 +712,8 @@ export interface ExploreSubmissionsResponse {
participantId: string;
organizationId: string;
participationType: 'INDIVIDUAL' | 'TEAM' | 'TEAM_OR_INDIVIDUAL';
- teamId?: string;
- teamName?: string;
+ teamId?: string | null;
+ teamName?: string | null;
teamMembers?: Array<{
userId: string;
name: string;
@@ -720,25 +724,33 @@ export interface ExploreSubmissionsResponse {
projectName: string;
category: string;
description: string;
- logo?: string;
- videoUrl?: string;
- introduction?: string;
- links: Array<{
- type: string;
- url: string;
- }>;
- socialLinks: {
- github?: string;
- telegram?: string;
- twitter?: string;
- email?: string;
- };
+ logo?: string | null;
+ videoUrl?: string | null;
+ introduction?: string | null;
+ links?: Array<{ type: string; url: string }>;
+ socialLinks?: Record;
+ comments?: number;
+ submissionDate?: string;
status: string;
- rank?: number;
+ disqualificationReason?: string | null;
+ reviewedById?: string | null;
+ rank?: number | null;
registeredAt: string;
submittedAt: string;
createdAt: string;
updatedAt: string;
+ project?: {
+ id: string;
+ title: string;
+ banner?: string | null;
+ logo?: string | null;
+ };
+ participant?: {
+ id: string;
+ name: string;
+ username: string;
+ image?: string;
+ };
}
export interface Participant {
@@ -1690,6 +1702,24 @@ export const getHackathonSubmissions = async (
return res.data;
};
+/** API response envelope for explore submissions endpoint. */
+export interface ExploreSubmissionsApiResponse {
+ success: boolean;
+ message: string;
+ data: {
+ submissions: ExploreSubmissionsResponse[];
+ pagination: {
+ page: number;
+ limit: number;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+ };
+ meta?: { timestamp?: string; requestId?: string };
+}
+
/**
* Explore hackathon submissions (Public showcase, for normal users)
*
@@ -1709,11 +1739,13 @@ export const getExploreSubmissions = async (
if (page) params.append('page', page.toString());
if (limit) params.append('limit', limit.toString());
- const res = await api.get(
+ const res = await api.get(
`/hackathons/${hackathonId}/submissions/explore${params.toString() ? `?${params.toString()}` : ''}`
);
- return res.data;
+ const body = res.data;
+ if (!body?.data?.submissions) return [];
+ return body.data.submissions;
};
/**
diff --git a/lib/api/types.ts b/lib/api/types.ts
index 3c6d39a2..77a50976 100644
--- a/lib/api/types.ts
+++ b/lib/api/types.ts
@@ -1,4 +1,5 @@
import { CrowdfundingProject, Crowdfunding } from '@/features/projects/types';
+import type { Hackathon } from '@/types/hackathon/core';
// Backend API Response Structure
export interface ApiResponse {
@@ -112,14 +113,14 @@ export interface User {
rank?: number | null;
submittedAt: string;
hackathonId: string;
- hackathon?: any;
+ hackathon?: Hackathon;
}>;
joinedHackathons?: Array<{
id: string;
userId: string;
hackathonId: string;
registrationDate: string;
- hackathon?: any;
+ hackathon?: Hackathon;
}>;
profile?: Record;
stats?: {
@@ -234,6 +235,12 @@ export type GoogleAuthResponse = AuthTokens;
// GetMe
export interface GetMeResponse {
user: User;
+ /** Optional profile display fields (may be populated from user or nested profile). */
+ firstName?: string;
+ lastName?: string;
+ username?: string;
+ image?: string;
+ isVerified?: boolean;
stats: {
projectsCreated: number;
projectsFunded: number;
@@ -266,7 +273,7 @@ export interface GetMeResponse {
hackathonId: string;
participantId: string;
status: string;
- hackathon?: any;
+ hackathon?: Hackathon;
}>;
}
diff --git a/lib/providers/hackathonProvider.tsx b/lib/providers/hackathonProvider.tsx
index ad17f6eb..edab6361 100644
--- a/lib/providers/hackathonProvider.tsx
+++ b/lib/providers/hackathonProvider.tsx
@@ -249,13 +249,20 @@ export function HackathonDataProvider({
projectName: sub.projectName,
description: sub.description,
submitterName:
- sub.teamName || sub.teamMembers?.[0]?.name || 'Unknown Participant',
- submitterAvatar: sub.teamMembers?.[0]?.avatar || sub.logo || '',
+ sub.participant?.name ??
+ sub.teamName ??
+ sub.teamMembers?.[0]?.name ??
+ 'Unknown Participant',
+ submitterAvatar:
+ sub.participant?.image ??
+ sub.teamMembers?.[0]?.avatar ??
+ sub.logo ??
+ '',
category: sub.category,
status: mapSubmissionStatus(sub.status),
upvotes: 0,
- submittedDate: sub.submittedAt,
- image: sub.logo || '/placeholder.svg',
+ submittedDate: sub.submittedAt ?? sub.submissionDate ?? '',
+ logo: sub.logo ?? '/placeholder.svg',
}));
setExploreSubmissions(mappedSubmissions);
} catch {
diff --git a/package.json b/package.json
index adb311d2..4b6aa8a6 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
+ "dev:webpack": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint .",
diff --git a/types/earnings.ts b/types/earnings.ts
index 760c2da0..70b733eb 100644
--- a/types/earnings.ts
+++ b/types/earnings.ts
@@ -12,12 +12,7 @@ export interface EarningActivity {
occurredAt: string;
}
-export interface EarningsBreakdown {
- hackathons: number;
- grants: number;
- crowdfunding: number;
- bounties: number;
-}
+export type EarningsBreakdown = Record;
export interface PublicEarningsResponse {
summary: {
diff --git a/types/hackathon/core.ts b/types/hackathon/core.ts
index 2bb0f22f..ea5acec7 100644
--- a/types/hackathon/core.ts
+++ b/types/hackathon/core.ts
@@ -102,11 +102,14 @@ export interface HackathonParticipation {
export interface PrizeTier {
id?: string;
+ name?: string;
place?: string;
currency?: string;
passMark?: number; // 0-100
description?: string;
prizeAmount?: string;
+ /** @deprecated Use prizeAmount. Kept for API compatibility. */
+ amount?: string;
}
export interface HackathonRewards {