Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ const ActionButtons = () => {
)
: false;

// Server-derived role for the current viewer. Organizers and assigned
// judges cannot join their own hackathon; surfacing the role lets us
// show the right copy instead of a broken join button.
const viewerRole = (hackathon as any)?.viewerRole as
| 'organizer'
| 'judge'
| 'participant'
| 'guest'
| undefined;
const hasConflictingRole =
viewerRole === 'organizer' || viewerRole === 'judge';

const handleJoin = withAuth(async () => {
try {
await joinMutation.mutateAsync();
Expand Down Expand Up @@ -86,7 +98,13 @@ const ActionButtons = () => {

return (
<div className='flex w-full flex-col gap-3 md:w-auto md:flex-row md:items-center'>
{!isParticipant ? (
{hasConflictingRole ? (
<div className='flex h-12 w-full items-center justify-center rounded-xl border border-white/10 bg-white/5 px-8 text-sm font-medium text-gray-300 md:w-auto'>
{viewerRole === 'organizer'
? 'You are managing this hackathon'
: 'You are judging this hackathon'}
</div>
) : !isParticipant ? (
<BoundlessButton
className='s d bg-primary hover:bg-primary/90 h-12 w-full rounded-xl px-8 font-bold text-black disabled:bg-white/5 disabled:text-white/20 md:w-auto'
icon={!isRegistrationClosed && <IconUserPlus size={20} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ const FindTeam = () => {
!!hackathon?.id && hackathon.participantType !== 'INDIVIDUAL'
);

const teams = teamsResponse?.data?.teams || [];
const teams = (teamsResponse?.data?.teams || []).filter(
t => !myTeam || t.id !== myTeam.id
);

const handleJoin = (team: Team) => {
setSelectedTeam(team);
Expand All @@ -99,14 +101,6 @@ const FindTeam = () => {

const isIndividualOnly = hackathon.participantType === 'INDIVIDUAL';

if (myTeam) {
return (
<TabsContent value='team-formation' className='mt-0 w-full outline-none'>
<MyTeamView team={myTeam} hackathonSlug={slug} />
</TabsContent>
);
}

return (
<TabsContent
value='team-formation'
Expand All @@ -129,9 +123,18 @@ const FindTeam = () => {
</div>
) : (
<>
{myTeam && (
<div className='space-y-4'>
<MyTeamView team={myTeam} hackathonSlug={slug} />
<div className='h-px w-full bg-white/5' />
</div>
)}

<div className='flex items-center justify-between'>
<div>
<h2 className='text-2xl font-bold text-white'>Open Teams</h2>
<h2 className='text-2xl font-bold text-white'>
{myTeam ? 'Other Teams' : 'Open Teams'}
</h2>
<p className='mt-1 text-sm text-gray-500'>
Find builders to collaborate with on your project.
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const SubmissionCard = ({ submission }: SubmissionCardProps) => {
<img
src={banner}
alt={`${projectName} banner`}
className='h-full w-full object-cover transition-transform duration-300 group-hover:scale-105'
className='h-full w-full object-contain transition-transform duration-300 group-hover:scale-105'
/>
) : logo ? (
// eslint-disable-next-line @next/next/no-img-element
Expand All @@ -113,7 +113,7 @@ const SubmissionCard = ({ submission }: SubmissionCardProps) => {
<img
src={logo}
alt={`${projectName} logo`}
className='absolute bottom-3 left-3 h-12 w-12 rounded-lg border-2 border-[#0D0E10] bg-[#0D0E10] object-cover shadow-lg'
className='absolute bottom-3 left-3 h-12 w-12 rounded-lg border-2 border-[#0D0E10] bg-[#0D0E10] object-contain shadow-lg'
/>
)}
</div>
Expand All @@ -130,12 +130,9 @@ const SubmissionCard = ({ submission }: SubmissionCardProps) => {

{/* Tags/Categories */}
<div className='flex flex-wrap gap-2 pt-2'>
<span className='text-primary rounded-md bg-[#232B20]/50 px-2.5 py-1 text-[10px] font-bold tracking-wider uppercase'>
{category}
</span>
{submission.category && (
<span className='rounded-md bg-white/5 px-2.5 py-1 text-[10px] font-bold tracking-wider text-gray-400 uppercase'>
{submission.category}
{category && (
<span className='text-primary rounded-md bg-[#232B20]/50 px-2.5 py-1 text-[10px] font-bold tracking-wider uppercase'>
{category}
</span>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,14 @@ const MyTeamView = ({ team, hackathonSlug }: MyTeamViewProps) => {
</div>
<div>
<h2 className='text-xl font-bold text-white sm:text-2xl'>
{team.teamName}
<a
href={`/hackathons/${hackathonSlug}/teams/${team.id}`}
target='_blank'
rel='noopener noreferrer'
className='hover:text-primary transition-colors'
>
{team.teamName}
</a>
</h2>
<div className='mt-1 flex flex-wrap items-center gap-2 sm:gap-3'>
<span className='text-primary text-sm font-bold'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import React from 'react';
import React, { useCallback } from 'react';
import { useParams } from 'next/navigation';
import { cn } from '@/lib/utils';
import { Team, TeamMember } from '@/lib/api/hackathons/teams';
import GroupAvatar from '@/components/avatars/GroupAvatar';
Expand All @@ -14,6 +15,16 @@ interface TeamCardProps {
}

const TeamCard = ({ team, onJoin, onMessageLeader }: TeamCardProps) => {
const { slug } = useParams<{ slug: string }>();

const openTeamDetails = useCallback(() => {
if (!slug || !team.id) return;
window.open(
`/hackathons/${slug}/teams/${team.id}`,
'_blank',
'noopener,noreferrer'
);
}, [slug, team.id]);
const {
teamName,
description,
Expand All @@ -32,7 +43,18 @@ const TeamCard = ({ team, onJoin, onMessageLeader }: TeamCardProps) => {
};

return (
<div className='group hover:border-primary/20 flex h-full flex-col overflow-hidden rounded-2xl border border-white/5 bg-[#0A0B0D] p-8 transition-all hover:bg-[#0D0F12]'>
<div
role='link'
tabIndex={0}
onClick={openTeamDetails}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
openTeamDetails();
}
}}
className='group hover:border-primary/20 flex h-full cursor-pointer flex-col overflow-hidden rounded-2xl border border-white/5 bg-[#0A0B0D] p-8 transition-all hover:bg-[#0D0F12]'
>
<div className='mb-6 flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between'>
<div className='flex items-start gap-4'>
<div className='text-primary flex h-14 w-14 shrink-0 items-center justify-center rounded-xl bg-[#232B20] text-xl font-black'>
Expand Down Expand Up @@ -70,7 +92,10 @@ const TeamCard = ({ team, onJoin, onMessageLeader }: TeamCardProps) => {
variant='outline'
size='sm'
className='h-11 shrink-0 rounded-xl border-white/10 bg-white/5 px-4 text-sm font-bold text-white transition-all hover:bg-white/10'
onClick={e => onMessageLeader(team, e.currentTarget)}
onClick={e => {
e.stopPropagation();
onMessageLeader(team, e.currentTarget);
}}
aria-label='Message team leader'
>
<MessageCircle className='mr-2 h-4 w-4' />
Expand All @@ -81,7 +106,10 @@ const TeamCard = ({ team, onJoin, onMessageLeader }: TeamCardProps) => {
variant='outline'
size='sm'
className='border-primary/20 text-primary hover:bg-primary h-11 w-full rounded-xl bg-[#232B20]/30 px-6 text-sm font-bold transition-all hover:text-black sm:w-auto'
onClick={() => onJoin?.(team)}
onClick={e => {
e.stopPropagation();
onJoin?.(team);
}}
disabled={memberCount >= maxSize}
>
Join Team
Expand Down
23 changes: 18 additions & 5 deletions app/(landing)/hackathons/[slug]/components/tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import FindTeam from './contents/FindTeam';
import SponsorsTab from './contents/SponsorsTab';
import { useEffect, useState, useMemo } from 'react';
import { useHackathonData } from '@/lib/providers/hackathonProvider';
import { useHackathonAnnouncements } from '@/hooks/hackathon/use-hackathon-queries';
import {
useHackathonAnnouncements,
useHackathonTeams,
} from '@/hooks/hackathon/use-hackathon-queries';
import { useCommentSystem } from '@/hooks/use-comment-system';
import { CommentEntityType } from '@/types/comment';
import { Megaphone } from 'lucide-react';
Expand Down Expand Up @@ -46,6 +49,17 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
enabled: !!currentHackathon?.id,
});

const participantType = currentHackathon?.participantType;
const isTeamHackathon =
participantType === 'TEAM' || participantType === 'TEAM_OR_INDIVIDUAL';

const { data: teamsCountResponse } = useHackathonTeams(
slug,
{ page: 1, limit: 1 },
!!slug && isTeamHackathon
);
const teamsTotal = teamsCountResponse?.data?.pagination?.total ?? 0;

const [activeTab, setActiveTab] = useState('overview');

const hackathonTabs = useMemo(() => {
Expand All @@ -55,10 +69,6 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
const hasWinners = !!(winners && winners.length > 0);
const hasAnnouncements = announcements.length > 0;

const participantType = currentHackathon.participantType;
const isTeamHackathon =
participantType === 'TEAM' || participantType === 'TEAM_OR_INDIVIDUAL';

const tabs = [
{ id: 'overview', label: 'Overview' },
...(hasParticipants
Expand Down Expand Up @@ -105,6 +115,7 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
tabs.push({
id: 'team-formation',
label: 'Find Team',
badge: teamsTotal,
});
}

Expand Down Expand Up @@ -159,6 +170,8 @@ const HackathonTabs = ({ sidebar }: HackathonTabsProps) => {
announcements,
announcementsLoading,
generalLoading,
isTeamHackathon,
teamsTotal,
]);

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions app/(landing)/hackathons/[slug]/submit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export default function SubmitProjectPage({
category: mySubmission.category,
description: mySubmission.description,
logo: mySubmission.logo,
banner: mySubmission.banner,
videoUrl: mySubmission.videoUrl,
introduction: mySubmission.introduction,
links: mySubmission.links,
Expand Down
Loading
Loading