Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
33 changes: 22 additions & 11 deletions app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,9 @@ export default function HackathonPageClient() {
const participantType = currentHackathon?.participantType;
const isTeamHackathon =
participantType === 'TEAM' || participantType === 'TEAM_OR_INDIVIDUAL';
const isTabEnabled =
currentHackathon?.enabledTabs?.includes('joinATeamTab') !== false;

const hasWinners = winners && winners.length > 0;

const isWinnersTabEnabled =
currentHackathon?.enabledTabs?.includes('winnersTab') !== false;

const tabs = [
{ id: 'overview', label: 'Overview' },
...(hasParticipants
Expand Down Expand Up @@ -152,15 +147,15 @@ export default function HackathonPageClient() {
},
];

if (isTeamHackathon && isTabEnabled) {
if (isTeamHackathon) {
tabs.push({
id: 'team-formation',
label: 'Find Team',
badge: teamPosts.length,
});
}

if (hasWinners && isWinnersTabEnabled) {
if (hasWinners) {
tabs.push({
id: 'winners',
label: 'Winners',
Expand All @@ -170,7 +165,10 @@ export default function HackathonPageClient() {
// Filter tabs against enabledTabs so only explicitly enabled tabs are shown.
// 'overview' is always kept as it is the default fallback tab.
// If enabledTabs is undefined/null (not configured), all tabs are shown as before.
// Map UI tab ids to backend enabledTabs keys where they differ.
//
// IMPORTANT: Any new tab id added to the tabs array above must have a corresponding
// entry in tabIdToEnabledKey below; otherwise it falls back to tab.id and may be
// hidden when enabledTabs is set.
const tabIdToEnabledKey: Record<string, string> = {
'team-formation': 'joinATeamTab',
winners: 'winnersTab',
Expand All @@ -181,6 +179,7 @@ export default function HackathonPageClient() {
discussions: 'discussionTab',
};

/** Backend enabledTabs entry type; keys in tabIdToEnabledKey must align with this. */
type EnabledTab = NonNullable<
typeof currentHackathon
>['enabledTabs'][number];
Expand All @@ -191,7 +190,17 @@ export default function HackathonPageClient() {
return tabs.filter(tab => {
if (tab.id === 'overview') return true;
const key = (tabIdToEnabledKey[tab.id] ?? tab.id) as EnabledTab;
return enabledSet.has(key);
const isVisible = enabledSet.has(key);
if (
!isVisible &&
process.env.NODE_ENV === 'development' &&
currentHackathon?.enabledTabs
) {
console.warn(
`[HackathonPageClient] Tab "${tab.id}" (enabled key: ${key}) is not in currentHackathon.enabledTabs and will be hidden. Add the tab id to tabIdToEnabledKey and ensure the backend includes the key in enabledTabs when the tab should be visible.`
);
}
return isVisible;
});
}
return tabs;
Expand Down Expand Up @@ -303,6 +312,8 @@ export default function HackathonPageClient() {
// Now also defaults to 'overview' if the URL tab is not in the filtered hackathonTabs list.
// This handles direct URL access to a disabled tab — user is silently redirected to overview.
useEffect(() => {
if (loading || !currentHackathon) return;

const tabFromUrl = searchParams.get('tab');

// No tab in URL — default to overview
Expand All @@ -322,7 +333,7 @@ export default function HackathonPageClient() {
const queryParams = new URLSearchParams(searchParams.toString());
queryParams.set('tab', 'overview');
router.replace(`?${queryParams.toString()}`, { scroll: false });
}, [searchParams, hackathonTabs, router]);
}, [searchParams, hackathonTabs, router, loading, currentHackathon]);

const handleTabChange = (tabId: string) => {
setActiveTab(tabId);
Expand Down Expand Up @@ -354,7 +365,7 @@ export default function HackathonPageClient() {

// Helper: checks if a tab id is present in the filtered hackathonTabs array.
// Used below to guard each tab's content from rendering if the tab is disabled.
const isTabVisible = (tabId: string) =>
const isTabVisible = (tabId: string): boolean =>
hackathonTabs.some(tab => tab.id === tabId);

// Shared props for banner and sticky card
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export default function HackathonPage() {
{phase.description}
</p>
</div>
<div className='flex-shrink-0 text-xs whitespace-nowrap text-white/60 sm:text-sm'>
<div className='shrink-0 text-xs whitespace-nowrap text-white/60 sm:text-sm'>
{new Date(phase.date).toLocaleDateString()}
</div>
</div>
Expand Down
41 changes: 24 additions & 17 deletions app/(landing)/profile/[username]/profile-data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { clsx } from 'clsx';
import ActivityTab from '@/components/profile/ActivityTab';
import OrganizationsTab from '@/components/profile/OrganizationsTab';
import ProfileOverviewPublic from '@/components/profile/ProfileOverviewPublic';
Expand All @@ -36,9 +37,9 @@ const FILTER_OPTIONS = [
const TAB_CLASS =
'data-[state=active]:border-b-primary/45 rounded-none border-b-2 border-transparent bg-transparent px-0 py-3 text-sm font-medium text-zinc-500 data-[state=active]:text-white';

export function ProfileData({
export const ProfileData = ({
username,
}: PublicProfileDataProps): React.ReactElement {
}: PublicProfileDataProps): React.ReactElement => {
const [userData, setUserData] = useState<PublicUserProfile | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand All @@ -47,7 +48,7 @@ export function ProfileData({
const [isAuthenticated, setIsAuthenticated] = useState(false);

useEffect(() => {
async function loadProfile(): Promise<void> {
const loadProfile = async (): Promise<void> => {
try {
setLoading(true);
const { data: session } = await authClient.getSession();
Expand All @@ -60,11 +61,15 @@ export function ProfileData({
} finally {
setLoading(false);
}
}
};

loadProfile();
}, [username]);

const handleFilterSelect = (filter: string) => {
setSelectedFilter(filter);
};

if (loading) {
return (
<section className='flex min-h-screen items-center justify-center'>
Expand Down Expand Up @@ -118,12 +123,14 @@ export function ProfileData({
<TabsTrigger value='earnings' className={TAB_CLASS}>
Earnings
</TabsTrigger>
<TabsTrigger
value='organizations'
className={`${TAB_CLASS} md:hidden`}
>
Organizations
</TabsTrigger>
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS, 'md:hidden')}
>
Organizations
</TabsTrigger>
)}
Comment on lines +126 to +133
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Organizations tab becomes unreachable on desktop.

Line 129 adds md:hidden, so on md+ screens there is no trigger for the organizations tab, even though its TabsContent still renders (Line 180 onward).

Proposed fix
- className={clsx(TAB_CLASS, 'md:hidden')}
+ className={TAB_CLASS}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS, 'md:hidden')}
>
Organizations
</TabsTrigger>
)}
{isAuthenticated && isOwnProfile && (
<TabsTrigger
value='organizations'
className={clsx(TAB_CLASS)}
>
Organizations
</TabsTrigger>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/`(landing)/profile/[username]/profile-data.tsx around lines 126 - 133,
The Organizations tab trigger (TabsTrigger with value 'organizations') is hidden
on md+ screens due to the 'md:hidden' class, making the tab unreachable on
desktop while its TabsContent still renders; remove the 'md:hidden' responsive
utility (or change it so the trigger is visible on md+ e.g., use no responsive
hide or use 'hidden md:block' if you intended mobile-only) from the TabsTrigger
that uses TAB_CLASS so the 'organizations' TabsTrigger is available when
isAuthenticated && isOwnProfile.

</TabsList>
</div>

Expand All @@ -148,12 +155,12 @@ export function ProfileData({
{FILTER_OPTIONS.map(filter => (
<DropdownMenuItem
key={filter}
onClick={() => setSelectedFilter(filter)}
className={
selectedFilter === filter
? 'bg-zinc-800'
: 'hover:!bg-zinc-600/50 hover:!text-white'
}
onClick={() => handleFilterSelect(filter)}
className={clsx({
'bg-zinc-800': selectedFilter === filter,
'hover:bg-zinc-600/50! hover:text-white!':
selectedFilter !== filter,
})}
>
{filter}
</DropdownMenuItem>
Expand All @@ -180,4 +187,4 @@ export function ProfileData({
</div>
</section>
);
}
};
2 changes: 2 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ body {
--popover-foreground: oklch(0.145 0 0);
--primary: #a7f950;
--primary-foreground: oklch(0.205 0 0);
--primary-shadow: 0 2px 8px rgba(167, 249, 80, 0.2);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
Expand Down Expand Up @@ -290,6 +291,7 @@ body {
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--primary-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
Expand Down
6 changes: 2 additions & 4 deletions app/me/earnings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ const BreakdownItem: React.FC<BreakdownItemProps> = ({
</div>
<span className='font-medium'>{label}</span>
</div>
<span className='font-semibold'>
${(Number(value) || 0).toLocaleString()}
</span>
<span className='font-semibold'>${(value ?? 0).toLocaleString()}</span>
</div>
);

Expand All @@ -109,7 +107,7 @@ const ActivityItem: React.FC<ActivityItemProps> = ({ activity }) => (
</div>
<div className='text-right'>
<p className='text-lg font-bold'>
${(Number(activity.amount) || 0).toLocaleString()}
${(activity.amount ?? 0).toLocaleString()}
</p>
{activity.currency && (
<p className='text-muted-foreground text-xs'>{activity.currency}</p>
Expand Down
Loading
Loading