diff --git a/components/landing-page/hackathon/HackathonCard.tsx b/components/landing-page/hackathon/HackathonCard.tsx index c14435d3b..9d0dd1da1 100644 --- a/components/landing-page/hackathon/HackathonCard.tsx +++ b/components/landing-page/hackathon/HackathonCard.tsx @@ -20,6 +20,7 @@ type HackathonCardProps = { categories: string[]; location?: string; venueType?: 'virtual' | 'physical'; + participantType?: 'individual' | 'team' | 'team_or_individual'; participants?: { current: number; goal?: number; @@ -76,26 +77,97 @@ function HackathonCard({ }; const getDeadlineInfo = () => { - if (deadlineInDays <= 0) + // Handle completed or cancelled status + if (status === 'Completed') { return { text: 'Ended', className: 'text-gray-500' }; - if (deadlineInDays <= 3) - return { text: `${deadlineInDays} days left`, className: 'text-red-400' }; - if (deadlineInDays <= 15) + } + if (status === 'Cancelled') { + return { text: 'Cancelled', className: 'text-gray-500' }; + } + + // Handle cases where deadline has passed + if (deadlineInDays <= 0) { + if (status === 'Ongoing') { + return { text: 'Ending soon', className: 'text-red-400' }; + } + return { text: 'Started', className: 'text-gray-500' }; + } + + // Handle different statuses with contextual messages + if (status === 'Published') { + // Upcoming hackathons - show "starting in X days" + if (deadlineInDays <= 3) { + return { + text: `starting in ${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''}`, + className: 'text-red-400', + }; + } + if (deadlineInDays <= 15) { + return { + text: `starting in ${deadlineInDays} days`, + className: 'text-yellow-400', + }; + } + return { + text: `starting in ${deadlineInDays} days`, + className: 'text-green-400', + }; + } + + if (status === 'Ongoing') { + // Ongoing hackathons - show "ending in X days" + if (deadlineInDays <= 3) { + return { + text: `ending in ${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''}`, + className: 'text-red-400', + }; + } + if (deadlineInDays <= 15) { + return { + text: `ending in ${deadlineInDays} days`, + className: 'text-yellow-400', + }; + } + return { + text: `ending in ${deadlineInDays} days`, + className: 'text-green-400', + }; + } + + // Fallback for any other status + if (deadlineInDays <= 3) { + return { + text: `${deadlineInDays} day${deadlineInDays !== 1 ? 's' : ''} left`, + className: 'text-red-400', + }; + } + if (deadlineInDays <= 15) { return { text: `${deadlineInDays} days left`, className: 'text-yellow-400', }; - return { text: `${deadlineInDays} days left`, className: 'text-green-400' }; + } + return { + text: `${deadlineInDays} days left`, + className: 'text-green-400', + }; }; const deadlineInfo = getDeadlineInfo(); - const locationText = - location || - (venueType === 'virtual' - ? 'Virtual' - : venueType === 'physical' - ? 'Physical' - : 'TBD'); + const locationText = (() => { + // If location is provided (from transform hook), use it + if (location) { + return location; + } + // Otherwise, fall back to venueType + if (venueType === 'virtual') { + return 'Virtual'; + } + if (venueType === 'physical') { + return 'Physical'; + } + return undefined; + })(); const CategoriesDisplay = ({ categoriesList, @@ -211,7 +283,9 @@ function HackathonCard({
- + {deadlineInfo.text} {participants?.goal && ( diff --git a/hooks/hackathon/use-hackathon-transform.ts b/hooks/hackathon/use-hackathon-transform.ts index 3a386b5d1..7d380cc96 100644 --- a/hooks/hackathon/use-hackathon-transform.ts +++ b/hooks/hackathon/use-hackathon-transform.ts @@ -25,6 +25,7 @@ interface TransformedHackathon { categories: string[]; location?: string; venueType?: 'virtual' | 'physical'; + participantType?: 'individual' | 'team' | 'team_or_individual'; participants?: { current: number; goal?: number; @@ -76,10 +77,20 @@ export function useHackathonTransform() { const venue = hackathon.information?.venue; let locationText: string | undefined; if (venue) { - if (venue.type === 'physical' && venue.city && venue.country) { - locationText = `${venue.city}, ${venue.country}`; - } else if (venue.type === 'physical' && venue.country) { - locationText = venue.country; + if (venue.type === 'physical') { + // For physical venues, prioritize city + country, then country, then state, then venue name + if (venue.city && venue.country) { + locationText = `${venue.city}, ${venue.country}`; + } else if (venue.country) { + locationText = venue.country; + } else if (venue.state) { + locationText = venue.state; + } else if (venue.venueName) { + locationText = venue.venueName; + } else if (venue.venueAddress) { + locationText = venue.venueAddress; + } + // If still no location, we'll rely on venueType in the card component } else if (venue.type === 'virtual') { locationText = 'Virtual'; } @@ -136,6 +147,9 @@ export function useHackathonTransform() { categories.push('Other'); } + // Extract participantType + const participantType = hackathon.participation?.participantType; + return { hackathonId: hackathon._id, organizationName: orgName, @@ -154,7 +168,14 @@ export function useHackathonTransform() { deadlineInDays: Math.max(0, deadlineInDays), categories: categories, location: locationText, - venueType: venue?.type, + venueType: venue?.type + ? venue.type === 'virtual' + ? 'virtual' + : 'physical' + : undefined, + participantType: participantType + ? (participantType as 'individual' | 'team' | 'team_or_individual') + : undefined, participants: { current: extendedHackathon.participants || 0, // Use participants from public API if available // goal could be calculated from participation settings diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts index 81439687a..a0471861c 100644 --- a/lib/api/hackathons.ts +++ b/lib/api/hackathons.ts @@ -44,7 +44,7 @@ export interface HackathonInformation { description: string; category?: HackathonCategory; // Legacy format (single category) categories?: HackathonCategory[]; // New format (array of categories) - venue: HackathonVenue; + venue?: HackathonVenue; } // Timeline Tab Types @@ -84,7 +84,7 @@ export interface TabVisibility { } export interface HackathonParticipation { - participantType: ParticipantType; + participantType?: ParticipantType; teamMin?: number; teamMax?: number; about?: string; @@ -654,6 +654,15 @@ export interface PublicHackathon { organizer: string; featured: boolean; resources: string[]; + venue?: { + type: 'virtual' | 'physical'; + country?: string; + state?: string; + city?: string; + venueName?: string; + venueAddress?: string; + }; + participantType?: 'individual' | 'team' | 'team_or_individual'; } export interface PublicHackathonsListData { @@ -778,9 +787,7 @@ const transformHackathonResponse = ( ? [flat.category as HackathonCategory] : [HackathonCategory.OTHER], category: (flat.category as HackathonCategory) || HackathonCategory.OTHER, - venue: flat.venue || { - type: VenueType.VIRTUAL, - }, + venue: flat.venue, }, timeline: { startDate: flat.startDate || '', @@ -791,8 +798,7 @@ const transformHackathonResponse = ( phases: flat.phases || [], }, participation: { - participantType: - (flat.participantType as ParticipantType) || ParticipantType.INDIVIDUAL, + participantType: flat.participantType as ParticipantType | undefined, teamMin: flat.teamMin, teamMax: flat.teamMax, about: flat.about, @@ -1348,11 +1354,20 @@ export const transformPublicHackathonToHackathon = ( const prizePoolAmount = parseFloat(publicHackathon.totalPrizePool.replace(/,/g, '')) || 0; - // Determine venue type from location or default to virtual - // Since API doesn't provide venue details, we'll default to virtual - const venue: HackathonVenue = { - type: VenueType.VIRTUAL, - }; + // Extract venue from API response or default to virtual + const venue: HackathonVenue | undefined = publicHackathon.venue + ? { + type: + publicHackathon.venue.type === 'physical' + ? VenueType.PHYSICAL + : VenueType.VIRTUAL, + country: publicHackathon.venue.country, + state: publicHackathon.venue.state, + city: publicHackathon.venue.city, + venueName: publicHackathon.venue.venueName, + venueAddress: publicHackathon.venue.venueAddress, + } + : undefined; // Map API status to internal status // API uses: upcoming, ongoing, ended @@ -1406,7 +1421,13 @@ export const transformPublicHackathonToHackathon = ( phases: [], }, participation: { - participantType: ParticipantType.TEAM_OR_INDIVIDUAL, + participantType: publicHackathon.participantType + ? publicHackathon.participantType === 'individual' + ? ParticipantType.INDIVIDUAL + : publicHackathon.participantType === 'team' + ? ParticipantType.TEAM + : ParticipantType.TEAM_OR_INDIVIDUAL + : undefined, teamMin: undefined, teamMax: undefined, about: publicHackathon.subtitle,