Skip to content
Merged
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>
)}
</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