Skip to content

Commit 697857a

Browse files
authored
Feat/hackthon features (#344)
* feat: implemented hackathon features * fix: update * fix: added the staging api * fix: update API base URL to use environment variable * fix: correct API_BASE_URL assignment and comment out unused variables * feat: enhance infoSchema to support multiple categories and validation * feat: add participantType support and enhance deadline handling in HackathonCard
1 parent 07ca664 commit 697857a

3 files changed

Lines changed: 147 additions & 31 deletions

File tree

components/landing-page/hackathon/HackathonCard.tsx

Lines changed: 87 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type HackathonCardProps = {
2020
categories: string[];
2121
location?: string;
2222
venueType?: 'virtual' | 'physical';
23+
participantType?: 'individual' | 'team' | 'team_or_individual';
2324
participants?: {
2425
current: number;
2526
goal?: number;
@@ -76,26 +77,97 @@ function HackathonCard({
7677
};
7778

7879
const getDeadlineInfo = () => {
79-
if (deadlineInDays <= 0)
80+
// Handle completed or cancelled status
81+
if (status === 'Completed') {
8082
return { text: 'Ended', className: 'text-gray-500' };
81-
if (deadlineInDays <= 3)
82-
return { text: `${deadlineInDays} days left`, className: 'text-red-400' };
83-
if (deadlineInDays <= 15)
83+
}
84+
if (status === 'Cancelled') {
85+
return { text: 'Cancelled', className: 'text-gray-500' };
86+
}
87+
88+
// Handle cases where deadline has passed
89+
if (deadlineInDays <= 0) {
90+
if (status === 'Ongoing') {
91+
return { text: 'Ending soon', className: 'text-red-400' };
92+
}
93+
return { text: 'Started', className: 'text-gray-500' };
94+
}
95+
96+
// Handle different statuses with contextual messages
97+
if (status === 'Published') {
98+
// Upcoming hackathons - show "starting in X days"
99+
if (deadlineInDays <= 3) {
100+
return {
101+
text: `starting in ${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''}`,
102+
className: 'text-red-400',
103+
};
104+
}
105+
if (deadlineInDays <= 15) {
106+
return {
107+
text: `starting in ${deadlineInDays} days`,
108+
className: 'text-yellow-400',
109+
};
110+
}
111+
return {
112+
text: `starting in ${deadlineInDays} days`,
113+
className: 'text-green-400',
114+
};
115+
}
116+
117+
if (status === 'Ongoing') {
118+
// Ongoing hackathons - show "ending in X days"
119+
if (deadlineInDays <= 3) {
120+
return {
121+
text: `ending in ${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''}`,
122+
className: 'text-red-400',
123+
};
124+
}
125+
if (deadlineInDays <= 15) {
126+
return {
127+
text: `ending in ${deadlineInDays} days`,
128+
className: 'text-yellow-400',
129+
};
130+
}
131+
return {
132+
text: `ending in ${deadlineInDays} days`,
133+
className: 'text-green-400',
134+
};
135+
}
136+
137+
// Fallback for any other status
138+
if (deadlineInDays <= 3) {
139+
return {
140+
text: `${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''} left`,
141+
className: 'text-red-400',
142+
};
143+
}
144+
if (deadlineInDays <= 15) {
84145
return {
85146
text: `${deadlineInDays} days left`,
86147
className: 'text-yellow-400',
87148
};
88-
return { text: `${deadlineInDays} days left`, className: 'text-green-400' };
149+
}
150+
return {
151+
text: `${deadlineInDays} days left`,
152+
className: 'text-green-400',
153+
};
89154
};
90155

91156
const deadlineInfo = getDeadlineInfo();
92-
const locationText =
93-
location ||
94-
(venueType === 'virtual'
95-
? 'Virtual'
96-
: venueType === 'physical'
97-
? 'Physical'
98-
: 'TBD');
157+
const locationText = (() => {
158+
// If location is provided (from transform hook), use it
159+
if (location) {
160+
return location;
161+
}
162+
// Otherwise, fall back to venueType
163+
if (venueType === 'virtual') {
164+
return 'Virtual';
165+
}
166+
if (venueType === 'physical') {
167+
return 'Physical';
168+
}
169+
return undefined;
170+
})();
99171

100172
const CategoriesDisplay = ({
101173
categoriesList,
@@ -211,7 +283,9 @@ function HackathonCard({
211283
</div>
212284

213285
<div className='flex items-center justify-between border-t border-neutral-800 px-4 py-3 sm:px-5'>
214-
<span className={`text-xs font-medium ${deadlineInfo.className}`}>
286+
<span
287+
className={`text-xs font-medium capitalize ${deadlineInfo.className}`}
288+
>
215289
{deadlineInfo.text}
216290
</span>
217291
{participants?.goal && (

hooks/hackathon/use-hackathon-transform.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface TransformedHackathon {
2525
categories: string[];
2626
location?: string;
2727
venueType?: 'virtual' | 'physical';
28+
participantType?: 'individual' | 'team' | 'team_or_individual';
2829
participants?: {
2930
current: number;
3031
goal?: number;
@@ -76,10 +77,20 @@ export function useHackathonTransform() {
7677
const venue = hackathon.information?.venue;
7778
let locationText: string | undefined;
7879
if (venue) {
79-
if (venue.type === 'physical' && venue.city && venue.country) {
80-
locationText = `${venue.city}, ${venue.country}`;
81-
} else if (venue.type === 'physical' && venue.country) {
82-
locationText = venue.country;
80+
if (venue.type === 'physical') {
81+
// For physical venues, prioritize city + country, then country, then state, then venue name
82+
if (venue.city && venue.country) {
83+
locationText = `${venue.city}, ${venue.country}`;
84+
} else if (venue.country) {
85+
locationText = venue.country;
86+
} else if (venue.state) {
87+
locationText = venue.state;
88+
} else if (venue.venueName) {
89+
locationText = venue.venueName;
90+
} else if (venue.venueAddress) {
91+
locationText = venue.venueAddress;
92+
}
93+
// If still no location, we'll rely on venueType in the card component
8394
} else if (venue.type === 'virtual') {
8495
locationText = 'Virtual';
8596
}
@@ -136,6 +147,9 @@ export function useHackathonTransform() {
136147
categories.push('Other');
137148
}
138149

150+
// Extract participantType
151+
const participantType = hackathon.participation?.participantType;
152+
139153
return {
140154
hackathonId: hackathon._id,
141155
organizationName: orgName,
@@ -154,7 +168,14 @@ export function useHackathonTransform() {
154168
deadlineInDays: Math.max(0, deadlineInDays),
155169
categories: categories,
156170
location: locationText,
157-
venueType: venue?.type,
171+
venueType: venue?.type
172+
? venue.type === 'virtual'
173+
? 'virtual'
174+
: 'physical'
175+
: undefined,
176+
participantType: participantType
177+
? (participantType as 'individual' | 'team' | 'team_or_individual')
178+
: undefined,
158179
participants: {
159180
current: extendedHackathon.participants || 0, // Use participants from public API if available
160181
// goal could be calculated from participation settings

lib/api/hackathons.ts

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export interface HackathonInformation {
4444
description: string;
4545
category?: HackathonCategory; // Legacy format (single category)
4646
categories?: HackathonCategory[]; // New format (array of categories)
47-
venue: HackathonVenue;
47+
venue?: HackathonVenue;
4848
}
4949

5050
// Timeline Tab Types
@@ -84,7 +84,7 @@ export interface TabVisibility {
8484
}
8585

8686
export interface HackathonParticipation {
87-
participantType: ParticipantType;
87+
participantType?: ParticipantType;
8888
teamMin?: number;
8989
teamMax?: number;
9090
about?: string;
@@ -654,6 +654,15 @@ export interface PublicHackathon {
654654
organizer: string;
655655
featured: boolean;
656656
resources: string[];
657+
venue?: {
658+
type: 'virtual' | 'physical';
659+
country?: string;
660+
state?: string;
661+
city?: string;
662+
venueName?: string;
663+
venueAddress?: string;
664+
};
665+
participantType?: 'individual' | 'team' | 'team_or_individual';
657666
}
658667

659668
export interface PublicHackathonsListData {
@@ -778,9 +787,7 @@ const transformHackathonResponse = (
778787
? [flat.category as HackathonCategory]
779788
: [HackathonCategory.OTHER],
780789
category: (flat.category as HackathonCategory) || HackathonCategory.OTHER,
781-
venue: flat.venue || {
782-
type: VenueType.VIRTUAL,
783-
},
790+
venue: flat.venue,
784791
},
785792
timeline: {
786793
startDate: flat.startDate || '',
@@ -791,8 +798,7 @@ const transformHackathonResponse = (
791798
phases: flat.phases || [],
792799
},
793800
participation: {
794-
participantType:
795-
(flat.participantType as ParticipantType) || ParticipantType.INDIVIDUAL,
801+
participantType: flat.participantType as ParticipantType | undefined,
796802
teamMin: flat.teamMin,
797803
teamMax: flat.teamMax,
798804
about: flat.about,
@@ -1348,11 +1354,20 @@ export const transformPublicHackathonToHackathon = (
13481354
const prizePoolAmount =
13491355
parseFloat(publicHackathon.totalPrizePool.replace(/,/g, '')) || 0;
13501356

1351-
// Determine venue type from location or default to virtual
1352-
// Since API doesn't provide venue details, we'll default to virtual
1353-
const venue: HackathonVenue = {
1354-
type: VenueType.VIRTUAL,
1355-
};
1357+
// Extract venue from API response or default to virtual
1358+
const venue: HackathonVenue | undefined = publicHackathon.venue
1359+
? {
1360+
type:
1361+
publicHackathon.venue.type === 'physical'
1362+
? VenueType.PHYSICAL
1363+
: VenueType.VIRTUAL,
1364+
country: publicHackathon.venue.country,
1365+
state: publicHackathon.venue.state,
1366+
city: publicHackathon.venue.city,
1367+
venueName: publicHackathon.venue.venueName,
1368+
venueAddress: publicHackathon.venue.venueAddress,
1369+
}
1370+
: undefined;
13561371

13571372
// Map API status to internal status
13581373
// API uses: upcoming, ongoing, ended
@@ -1406,7 +1421,13 @@ export const transformPublicHackathonToHackathon = (
14061421
phases: [],
14071422
},
14081423
participation: {
1409-
participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
1424+
participantType: publicHackathon.participantType
1425+
? publicHackathon.participantType === 'individual'
1426+
? ParticipantType.INDIVIDUAL
1427+
: publicHackathon.participantType === 'team'
1428+
? ParticipantType.TEAM
1429+
: ParticipantType.TEAM_OR_INDIVIDUAL
1430+
: undefined,
14101431
teamMin: undefined,
14111432
teamMax: undefined,
14121433
about: publicHackathon.subtitle,

0 commit comments

Comments
 (0)