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
27 changes: 26 additions & 1 deletion app/(landing)/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,29 @@ function mapSubmissionToCrowdfunding(

const projectId = subData.id || subData._id || '';

// Map raw submission status to a friendly display label so the sidebar
// badge reads "Shortlisted" instead of "SHORTLISTED". The raw value is
// also preserved on submissionStatus so anything that needs the original
// enum value can still get it.
const submissionRank: number | null =
typeof subData.rank === 'number' ? subData.rank : null;
const rawSubmissionStatus: string =
typeof subData.status === 'string' ? subData.status : '';
const friendlySubmissionStatus = (() => {
switch (rawSubmissionStatus.toUpperCase()) {
case 'SUBMITTED':
return 'Submitted';
case 'SHORTLISTED':
return 'Shortlisted';
case 'DISQUALIFIED':
return 'Disqualified';
case 'WITHDRAWN':
return 'Withdrawn';
default:
return rawSubmissionStatus || 'pending';
}
})();

// Find demo video in links if not provided directly
let demoVideoUrl = subData.videoUrl || '';
if (!demoVideoUrl && socialLinks.length > 0) {
Expand Down Expand Up @@ -320,7 +343,9 @@ function mapSubmissionToCrowdfunding(
vision: null,
details: null,
category: subData.category || 'General',
status: subData.status || 'pending',
status: friendlySubmissionStatus,
submissionRank,
submissionStatus: rawSubmissionStatus || null,
creatorId: subData.participantId || subData.userId || '',
organizationId: subData.organizationId || null,
teamMembers: teamMembers,
Expand Down
2 changes: 1 addition & 1 deletion components/landing-page/blog/BlogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const BlogCard = ({ post, onCardClick }: BlogCardProps) => {
src={post.coverImage}
alt={post.title}
fill
className='object-contain transition-transform duration-500 group-hover:scale-105'
className='object-cover transition-transform duration-500 group-hover:scale-105'
sizes='(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'
/>
{/* Gradient Overlay */}
Expand Down
2 changes: 1 addition & 1 deletion components/landing-page/blog/BlogPostDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const BlogPostDetails: React.FC<BlogPostDetailsProps> = ({
src={post.coverImage}
alt={post.title}
fill
className='rounded-lg object-contain'
className='rounded-lg object-cover'
priority
sizes='(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 70vw'
/>
Expand Down
14 changes: 10 additions & 4 deletions components/project-details/comment-section/project-comments.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { useOptionalAuth } from '@/hooks/use-auth';
import { useCommentSystem } from '@/hooks/use-comment-system';
import { useCommentRealtime } from '@/hooks/use-comment-realtime';
Expand All @@ -17,6 +18,11 @@ interface ProjectCommentsProps {

export function ProjectComments({ projectId }: ProjectCommentsProps) {
const { user } = useOptionalAuth();
const searchParams = useSearchParams();
const entityType =
searchParams.get('type') === 'submission'
? CommentEntityType.HACKATHON_SUBMISSION
: CommentEntityType.PROJECT;
const [sortBy, setSortBy] = useState<
'createdAt' | 'updatedAt' | 'totalReactions'
>('createdAt');
Expand All @@ -28,7 +34,7 @@ export function ProjectComments({ projectId }: ProjectCommentsProps) {

// Initialize the comment system for this project
const commentSystem = useCommentSystem({
entityType: CommentEntityType.PROJECT,
entityType,
entityId: projectId,
page: 1,
limit: 20,
Expand All @@ -38,7 +44,7 @@ export function ProjectComments({ projectId }: ProjectCommentsProps) {
// Real-time updates
useCommentRealtime(
{
entityType: CommentEntityType.PROJECT,
entityType,
entityId: projectId,
userId: currentUserId,
enabled: true,
Expand Down Expand Up @@ -71,7 +77,7 @@ export function ProjectComments({ projectId }: ProjectCommentsProps) {
try {
await commentSystem.createComment.createComment({
content,
entityType: CommentEntityType.PROJECT,
entityType,
entityId: projectId,
});
} catch (error) {
Expand All @@ -84,7 +90,7 @@ export function ProjectComments({ projectId }: ProjectCommentsProps) {
await commentSystem.createComment.createComment({
content,
parentId: parentCommentId,
entityType: CommentEntityType.PROJECT,
entityType,
entityId: projectId,
} as any);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import { useSearchParams } from 'next/navigation';
import {
ArrowUp,
DollarSign,
Expand All @@ -24,6 +25,8 @@ export function ProjectSidebarActions({
crowdfund,
}: ProjectSidebarActionsProps) {
const [isSharePopupOpen, setIsSharePopupOpen] = useState(false);
const searchParams = useSearchParams();
const isSubmission = searchParams.get('type') === 'submission';

const handleShareClick = () => {
setIsSharePopupOpen(true);
Expand Down Expand Up @@ -109,13 +112,15 @@ export function ProjectSidebarActions({
</BoundlessButton>
)}

<div className='flex-1'>
<FollowButton
entityType='PROJECT'
entityId={project.id}
className='h-12 w-full'
/>
</div>
{!isSubmission && (
<div className='flex-1'>
<FollowButton
entityType='PROJECT'
entityId={project.id}
className='h-12 w-full'
/>
</div>
)}

<div className='relative'>
<BoundlessButton
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';

import { Calendar } from 'lucide-react';
import { Calendar, Trophy } from 'lucide-react';
import Image from 'next/image';
import { ProjectSidebarHeaderProps } from './types';

export function ProjectSidebarHeader({
project,
projectStatus,
}: ProjectSidebarHeaderProps) {
const submissionRank = project.submissionRank ?? null;
const getStatusStyles = () => {
switch (projectStatus) {
case 'CAMPAIGNING':
Expand All @@ -21,11 +22,16 @@ export function ProjectSidebarHeader({
case 'idea':
return 'bg-warning-75 border-warning-600 text-warning-600';
case 'Validated':
case 'Shortlisted':
return 'bg-success-75 border-success-600 text-success-600';
case 'Rejected':
case 'Disqualified':
return 'bg-red-900/30 border-red-600 text-red-400';
case 'Withdrawn':
return 'bg-gray-800/60 border-gray-700 text-gray-300';
case 'pending':
case 'SUBMITTED':
case 'Submitted':
return 'bg-gray-800 border-gray-700 text-white';
default:
return 'text-white border-gray-700';
Expand Down Expand Up @@ -60,6 +66,14 @@ export function ProjectSidebarHeader({
>
{projectStatus}
</div>
{submissionRank != null && (
<div className='flex items-center gap-1 rounded-lg border border-yellow-500/40 bg-yellow-500/10 px-2.5 py-1 text-xs font-semibold text-yellow-400'>
<Trophy className='h-3.5 w-3.5' />
<span>
{submissionRank === 1 ? 'Winner' : `Rank #${submissionRank}`}
</span>
</div>
)}
</div>
</div>
</div>
Expand Down
9 changes: 3 additions & 6 deletions components/project-details/project-sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function ProjectSidebar({
// Real-time vote updates
useVoteRealtime(
{
entityType: VoteEntityType.CROWDFUNDING_CAMPAIGN,
entityType,
entityId: projectId || '',
enabled: !!projectId,
},
Expand Down Expand Up @@ -82,10 +82,7 @@ export function ProjectSidebar({

const fetchVoteCounts = async () => {
try {
const response: any = await getVoteCounts(
projectId,
VoteEntityType.CROWDFUNDING_CAMPAIGN
);
const response: any = await getVoteCounts(projectId, entityType);
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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getVoteCounts signatures and current explicit-any call sites
rg -n -C3 "\bgetVoteCounts\b" --type=ts --type=tsx
rg -n -C2 ":\s*any\s*=\s*await\s+getVoteCounts\(" --type=ts --type=tsx

Repository: boundlessfi/boundless

Length of output: 121


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find getVoteCounts usages and inspect nearby code (ts/tsx only)
rg -n -C3 "\bgetVoteCounts\b" -g'*.ts' -g'*.tsx' || true

# Find the specific explicit-any line (ts/tsx only)
rg -n -C2 ":\s*any\s*=\s*await\s+getVoteCounts\(" -g'*.ts' -g'*.tsx' || true

# Locate getVoteCounts definition
rg -n "getVoteCounts\s*=\s*async|function\s+getVoteCounts|getVoteCounts\s*\(|export\s+async\s+function\s+getVoteCounts" -g'*.ts' -g'*.tsx' || true

Repository: boundlessfi/boundless

Length of output: 4390


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect getVoteCounts implementation
sed -n '1,140p' lib/api/votes.ts | cat -n
sed -n '140,260p' lib/api/votes.ts | cat -n

# Inspect VoteCountResponse and related types
rg -n "export (type|interface) VoteCountResponse" -S --hidden --glob='*.ts' --glob='*.tsx' .
rg -n "VoteCountResponse" -S --hidden --glob='*.ts' --glob='*.tsx' types . | head -n 50 || true
# If types file is not under /types, search by filename
fd -a -t f "votes.ts" . || true
fd -a -t f "*votes*" . | head -n 50

# Print likely votes types file(s)
for f in $(fd -a -t f "votes.ts" . | tr '\n' ' '); do
  echo "===== $f ====="
  sed -n '1,200p' "$f" | cat -n
done

# Inspect sidebar component around fetchVoteCounts and state
sed -n '1,220p' components/project-details/project-sidebar/index.tsx | cat -n

Repository: boundlessfi/boundless

Length of output: 9929


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show VoteCountResponse definition
sed -n '1,220p' types/votes.ts | cat -n | sed -n '35,140p'

# Show sidebar index state + fetchVoteCounts block around line ~70-100
sed -n '1,220p' components/project-details/project-sidebar/index.tsx | cat -n | sed -n '1,160p'

# Also show sidebar types (prop shape)
sed -n '1,120p' components/project-details/project-sidebar/types.ts | cat -n

Repository: boundlessfi/boundless

Length of output: 10908


Remove any and simplify vote-count fetching

In components/project-details/project-sidebar/index.tsx line 85, getVoteCounts is typed as Promise<VoteCountResponse> and returns res.data directly, so const response: any and response.data || response are unnecessary—use const response = await getVoteCounts(projectId, entityType); and setVoteCounts(response), and remove the now-incorrect comment about { success, data }.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/project-details/project-sidebar/index.tsx` at line 85, Replace the
loose any-typed response and unnecessary branching when fetching votes: call
getVoteCounts(projectId, entityType) directly into a properly typed variable (no
`any`) e.g. `const response: VoteCountResponse = await getVoteCounts(...)`, then
call setVoteCounts(response) (remove `response.data || response` and the
incorrect `{ success, data }` comment). Update the local variable name and
typing where used to reflect VoteCountResponse so the code and types are
consistent with getVoteCounts and setVoteCounts.

// The API returns { success: true, message: '...', data: { ... } }
setVoteCounts(response.data || response);
} catch {
Expand All @@ -94,7 +91,7 @@ export function ProjectSidebar({
};

fetchVoteCounts();
}, [projectId]);
}, [projectId, entityType]);

const handleVote = async (value: 1 | -1) => {
if (isVoting) return;
Expand Down
8 changes: 7 additions & 1 deletion components/project-details/project-sidebar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@ export type ProjectStatus =
| 'Funding'
| 'idea'
| 'pending'
| 'SUBMITTED';
| 'SUBMITTED'
// Hackathon-submission display labels emitted by the submission mapper
// in app/(landing)/projects/[slug]/page.tsx.
| 'Submitted'
| 'Shortlisted'
| 'Disqualified'
| 'Withdrawn';
19 changes: 11 additions & 8 deletions components/project-details/project-voters/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import Empty from './Empty';
import Image from 'next/image';
import { CrowdfundingProject, Crowdfunding } from '@/features/projects/types';
Expand Down Expand Up @@ -40,6 +41,11 @@ interface ProjectVotersProps {

const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {
const { user } = useOptionalAuth();
const searchParams = useSearchParams();
const isSubmission = searchParams.get('type') === 'submission';
const entityType = isSubmission
? VoteEntityType.HACKATHON_SUBMISSION
: VoteEntityType.CROWDFUNDING_CAMPAIGN;
const [voteStats, setVoteStats] = useState<VoteStats>({
upvotes: 0,
downvotes: 0,
Expand All @@ -63,7 +69,7 @@ const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {

useVoteRealtime(
{
entityType: VoteEntityType.CROWDFUNDING_CAMPAIGN,
entityType,
entityId: projectId || '',
enabled: !!projectId,
},
Expand Down Expand Up @@ -116,7 +122,7 @@ const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {
const response = await getProjectVotes(projectId, {
limit: 20,
offset: 0,
entityType: VoteEntityType.CROWDFUNDING_CAMPAIGN,
entityType,
includeVoters: true,
});

Expand All @@ -137,10 +143,7 @@ const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {
});
setVoters(response.data.voters);
} else {
const countsResponse = await apiGetVoteCounts(
projectId,
VoteEntityType.CROWDFUNDING_CAMPAIGN
);
const countsResponse = await apiGetVoteCounts(projectId, entityType);
setVoteStats({
upvotes: countsResponse.upvotes,
downvotes: countsResponse.downvotes,
Expand All @@ -160,7 +163,7 @@ const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {
};

fetchInitialVoteData();
}, [projectId]);
}, [projectId, entityType]);

const handleVote = async (voteType: VoteType) => {
if (!projectId || voting) return;
Expand All @@ -171,7 +174,7 @@ const ProjectVoters = ({ project, crowdfund }: ProjectVotersProps) => {
try {
await createVote({
projectId,
entityType: VoteEntityType.CROWDFUNDING_CAMPAIGN,
entityType,
voteType,
});

Expand Down
Loading
Loading