- {projects.map(project => (
-
- {
+ // Find if this project is a hackathon submission
+ // We need to check if the user object has submissions
+ const submission =
+ user.user.hackathonSubmissionsAsParticipant?.find(
+ s => s.projectId === project.id
+ );
+
+ // If it's a submission, use the submission ID for the URL and add ?type=submission
+ const displayId = submission?.id || project.id;
+ const href = submission
+ ? `/projects/${displayId}?type=submission`
+ : `/projects/${displayId}`;
+
+ return (
+
+
-
- ))}
+ },
+ isSubmission: !!submission,
+ submissionStatus: submission?.status,
+ }}
+ />
+
+ );
+ })}
{!hasMore && projects.length > 0 && (
diff --git a/components/profile/ProjectsTabPublic.tsx b/components/profile/ProjectsTabPublic.tsx
index 478c1fafd..ccbf7194a 100644
--- a/components/profile/ProjectsTabPublic.tsx
+++ b/components/profile/ProjectsTabPublic.tsx
@@ -33,14 +33,26 @@ export default function ProjectsTabPublic({ user }: ProjectsTabProps) {
- {user.projects.map(project => (
-
- {
+ // Find if this project is a hackathon submission
+ const submission = user.hackathonSubmissionsAsParticipant?.find(
+ s => s.projectId === project.id
+ );
+
+ // If it's a submission, use the submission ID for the URL and add ?type=submission
+ const displayId = submission?.id || project.id;
+ const href = submission
+ ? `/projects/${displayId}?type=submission`
+ : `/projects/${displayId}`;
+
+ return (
+
+
-
- ))}
+ // Add a custom property to indicate it's a submission for ProjectCard
+ isSubmission: !!submission,
+ submissionStatus: submission?.status,
+ }}
+ />
+
+ );
+ })}
);
diff --git a/components/ui/expandable-screen.tsx b/components/ui/expandable-screen.tsx
index 7eaf54e96..4894a27b7 100644
--- a/components/ui/expandable-screen.tsx
+++ b/components/ui/expandable-screen.tsx
@@ -34,6 +34,10 @@ function useExpandableScreen() {
return context;
}
+export function useOptionalExpandableScreen() {
+ return useContext(ExpandableScreenContext);
+}
+
// Root Component
interface ExpandableScreenProps {
children: ReactNode;
diff --git a/content/blog/what-do-you-know-about-boundless-on-x-challenge.mdx b/content/blog/what-do-you-know-about-boundless-on-x-challenge.mdx
new file mode 100644
index 000000000..baf6dc81f
--- /dev/null
+++ b/content/blog/what-do-you-know-about-boundless-on-x-challenge.mdx
@@ -0,0 +1,157 @@
+---
+title: 'What Do You Know About Boundless? The X Challenge'
+excerpt: 'Whether you’re a builder, creator, or meme-lover, the Boundless on X Challenge is your chance to explain the platform in your own words. Share your thoughts and win a share of the $100 USDC prize pool!'
+coverImage: 'https://pbs.twimg.com/media/HCwbCFTWoAAdJkq?format=jpg&name=900x900'
+publishedAt: '2026-03-09'
+author:
+ name: 'Boundless Team'
+ image: ''
+categories: ['Community', 'Challenge', 'Events']
+tags:
+ ['boundless', 'challenge', 'x', 'twitter', 'community', 'stellar', 'rewards']
+readingTime: 3
+isFeatured: true
+seoTitle: 'What Do You Know About Boundless? Join the X Challenge'
+seoDescription: 'Join the Boundless on X Challenge! Explain the platform in your own words through a tweet, thread, or meme, and win your share of $100 USDC.'
+---
+
+# What Do You Know About Boundless?
+
+We want to hear your take.
+
+Whether you’re a builder, creator, or meme-lover, the **Boundless on X Challenge** is your chance to explain the platform in your own words. Share a tweet, thread, or visual post, and the best entries will help shape how the world understands Boundless — and win some **USDC**!
+
+---
+
+## So, What’s the Boundless on X Challenge All About?
+
+Our mission at **Boundless** is simple: to empower anyone, anywhere, to transform bold ideas into impactful projects with **transparency, community, and accountability** at the core.
+
+Ecosystems grow through conversation. The more people understand a platform, the easier it is for new builders to join and for great ideas to gain traction. The **Boundless on X Challenge** is your chance to contribute by telling our story in your own way.
+
+If you’re reading this, you’re probably researching to participate in the challenge.
+
+Awesome — we’re thrilled to have you. Stick around until the end of this article and you’ll get some **insider tips** to help your post stand out.
+
+---
+
+# Finish the Challenge in Three Simple Steps
+
+Completing this challenge only takes **three steps** and a couple of minutes.
+
+## 1. Register and Follow Boundless
+
+To participate, go to **[boundlesfi.xyz](https://www.boundlessfi.xyz/hackathons/boundless-on-x-test?tab=participants)** and register for **Boundless on X**.
+
+This is a community challenge, so join the community on **X (formerly Twitter)** `@boundless_fi` and on **Discord** to meet the community.
+
+---
+
+## 2. Choose Your Category and Create Your Post
+
+Share an impactful and/or entertaining **tweet, thread, or meme** about Boundless.
+
+**Must-haves:**
+
+- Your post must include **#BoundlessBuild**
+- Tag both **@boundless_fi** and **@BuildOnStellar**
+
+---
+
+## 3. Submit Your Entry
+
+Copy the link to your post and submit it through the **challenge page** with a short description.
+
+**Done.**
+
+---
+
+# Categories You Can Join
+
+You can join the challenge by participating in **any of these categories**. Pick the format that best suits your creative style.
+
+## Threads
+
+Share a short, engaging **thread of 3–5 tweets** that explains Boundless or tells a story about the platform.
+
+## Single Tweets
+
+Write **one clear, impactful, concise, or clever tweet** that captures what Boundless is all about.
+
+## Memes or Visuals
+
+Get creative with an **image, graphic, or short video** that explains or promotes Boundless in a fun way.
+
+Winners will be selected **in each category**, along with **one overall standout entry** that really captures the spirit of the challenge.
+
+---
+
+# What Are the Best Participants Creating?
+
+Top entries are the ones that:
+
+- Explain **Boundless** like you’re telling a friend who’s never heard of it.
+- Highlight **why Boundless’ mission matters**.
+- Share **what excites you most about the ecosystem**.
+- Bring the idea to life with **humor, visuals, or storytelling**.
+
+No single approach is _“correct,”_ but these tips can help your post stand out:
+
+- **Keep it personal.** Your voice matters more than AI-generated material — use it sparingly or not at all.
+- **Clarity over virality.** Likes and retweets are nice, but a clear, compelling explanation carries the most weight.
+- **Be authentic.** Show your enthusiasm and perspective; genuine posts resonate more with the community.
+
+---
+
+# Prizes and Key Dates
+
+The challenge features a **$100 USDC prize pool**, shared by the top entries selected from across all categories.
+
+Winning entries may also be **highlighted across the Boundless community**.
+
+The challenge runs for a short period, so make sure to submit your entry before the deadline:
+
+- **Challenge kicks off:** Saturday, March 7, 2026
+- **Last chance to submit:** Wednesday, March 11, 2026 at **12:00 PM UTC**
+
+---
+
+# Got Questions? We’ve Got Answers
+
+## Who can participate?
+
+Anyone can join. If you have an **X account** and want to share something about Boundless, you’re welcome to take part.
+
+## Do I need to be a developer?
+
+Not at all. The challenge is open to **builders, creators, writers, designers, meme makers**, and anyone interested in the ecosystem.
+
+## Can I participate in more than one category?
+
+Yes. You can submit entries in **multiple categories**.
+
+For example, you might create a thread explaining Boundless and also submit a meme or visual. Each entry should be **submitted separately** with its own link and description.
+
+## What’s off-limits?
+
+**NSFW, offensive, or harmful content** is a no-go. Stick to posts that **inform, entertain, or inspire** in a way everyone can enjoy.
+
+## Want to ask more?
+
+If you have questions that aren’t answered here, don’t be shy.
+
+**[Tag us on X](https://x.com/boundless_fi)** or **[Start a conversation in Discord](https://discord.gg/tgpFpSHG)**. We love hearing from the community.
+
+---
+
+# Ready to Jump In?
+
+Your voice matters.
+
+Create your post, submit it, and share your excitement about **Boundless** — for the love of the game, for a bit of glory, and for the chance to win some fun rewards too!
+
+Finally, as promised, here are some **insider resources** to help your post stand out and guide you through the challenge:
+
+- **[Quick start guide to join Boundless](https://docs.boundlessfi.xyz/getting-started/quick-start)**
+- **[How Boundless works](https://docs.boundlessfi.xyz/concepts/how-boundless-works)**
+- **[General FAQs](https://docs.boundlessfi.xyz/faq/general)**
diff --git a/features/projects/components/ProjectCard.tsx b/features/projects/components/ProjectCard.tsx
index 8dd194c84..155c4ec3a 100644
--- a/features/projects/components/ProjectCard.tsx
+++ b/features/projects/components/ProjectCard.tsx
@@ -3,10 +3,36 @@ import { formatNumber, cn } from '@/lib/utils';
import { useRouter } from 'nextjs-toploader/app';
import Image from 'next/image';
import { CountdownTimer } from '@/components/ui/timer';
-import { Crowdfunding } from '@/features/projects/types';
+
+export type ProjectCardData = {
+ id: string;
+ slug: string;
+ project: {
+ id?: string;
+ title: string;
+ vision?: string | null;
+ banner?: string | null;
+ logo?: string | null;
+ creator?: { name: string; image?: string | null };
+ category?: string | null;
+ status: string;
+ _count?: { votes?: number };
+ [key: string]: any;
+ };
+ fundingGoal?: number;
+ fundingRaised?: number;
+ fundingCurrency?: string;
+ fundingEndDate?: string | null;
+ milestones?: any[];
+ voteGoal?: number;
+ voteProgress?: number;
+ isSubmission?: boolean;
+ submissionStatus?: string | null;
+ [key: string]: any;
+};
type ProjectCardProps = {
- data: Crowdfunding;
+ data: ProjectCardData;
newTab?: boolean;
isFullWidth?: boolean;
className?: string;
@@ -23,13 +49,15 @@ function ProjectCard({
const {
slug,
project,
- fundingGoal,
- fundingRaised,
- fundingCurrency,
- fundingEndDate,
- milestones,
- voteGoal,
- voteProgress,
+ fundingGoal = 0,
+ fundingRaised = 0,
+ fundingCurrency = 'USDC',
+ fundingEndDate = null,
+ milestones = [],
+ voteGoal = 0,
+ voteProgress = 0,
+ isSubmission,
+ submissionStatus,
} = data;
const {
@@ -47,11 +75,22 @@ function ProjectCard({
banner || '/images/placeholders/project-banner-placeholder.png';
const handleClick = () => {
- router.push(`/projects/${slug}`);
+ const url = isSubmission
+ ? `/projects/${slug}?type=submission`
+ : `/projects/${slug}`;
+ router.push(url);
};
// Determine display status
const getDisplayStatus = () => {
+ if (isSubmission) {
+ if (submissionStatus === 'SHORTLISTED' || submissionStatus === 'ACCEPTED')
+ return 'Shortlisted';
+ if (submissionStatus === 'SUBMITTED') return 'Submitted';
+ if (submissionStatus === 'DISQUALIFIED') return 'Disqualified';
+ return 'Submission';
+ }
+
if (projectStatus === 'IDEA') return 'Validation';
if (projectStatus === 'ACTIVE') return 'Funding';
if (projectStatus === 'LIVE') return 'Funded';
diff --git a/features/projects/types/index.ts b/features/projects/types/index.ts
index bce8bcc40..7d0418dab 100644
--- a/features/projects/types/index.ts
+++ b/features/projects/types/index.ts
@@ -82,6 +82,13 @@ export interface PublicUserProfile {
logo: string;
createdAt: string;
}>;
+ hackathonSubmissionsAsParticipant?: Array<{
+ id: string;
+ projectId: string;
+ status: string;
+ hackathonId: string;
+ projectName: string;
+ }>;
badges: any[];
stats: {
projectsCreated: number;
diff --git a/hooks/hackathon/use-participants.ts b/hooks/hackathon/use-participants.ts
index f6fe782bc..45e43122b 100644
--- a/hooks/hackathon/use-participants.ts
+++ b/hooks/hackathon/use-participants.ts
@@ -12,17 +12,17 @@ export function useParticipants() {
const [apiParticipants, setApiParticipants] = useState([]);
const [isLoading, setIsLoading] = useState(false);
- const hackathonId = currentHackathon?.id || (params?.slug as string);
+ const hackathonId = currentHackathon?.id;
// Fetch teams to get accurate team info and roles
useEffect(() => {
if (hackathonId) {
setIsLoading(true);
- Promise.all([
- getTeamPosts(hackathonId, { limit: 50 }),
- getHackathonParticipants(hackathonId, { limit: 100 }),
- ])
- .then(([teamsResponse, participantsResponse]) => {
+
+ const fetchAllData = async () => {
+ try {
+ // Fetch teams
+ const teamsResponse = await getTeamPosts(hackathonId, { limit: 50 });
if (teamsResponse.success && teamsResponse.data) {
const teamsArray =
(teamsResponse.data as any).teams ||
@@ -30,16 +30,35 @@ export function useParticipants() {
setTeams(teamsArray);
}
- if (participantsResponse.success && participantsResponse.data) {
- setApiParticipants(participantsResponse.data.participants || []);
+ // Fetch participants with pagination
+ let allParticipants: any[] = [];
+ let page = 1;
+ let hasMore = true;
+
+ while (hasMore) {
+ const participantsResponse = await getHackathonParticipants(
+ hackathonId,
+ { limit: 100, page }
+ );
+ if (participantsResponse.success && participantsResponse.data) {
+ const newParticipants =
+ participantsResponse.data.participants || [];
+ allParticipants = [...allParticipants, ...newParticipants];
+ hasMore = participantsResponse.data.pagination?.hasNext || false;
+ page++;
+ } else {
+ hasMore = false;
+ }
}
- })
- .catch(err => {
+ setApiParticipants(allParticipants);
+ } catch (err) {
reportError(err, { context: 'participants-fetchData', hackathonId });
- })
- .finally(() => {
+ } finally {
setIsLoading(false);
- });
+ }
+ };
+
+ fetchAllData();
}
}, [hackathonId]);
diff --git a/lib/api/types.ts b/lib/api/types.ts
index 39010e576..d418161a3 100644
--- a/lib/api/types.ts
+++ b/lib/api/types.ts
@@ -122,6 +122,7 @@ export interface User {
rank?: number | null;
submittedAt: string;
hackathonId: string;
+ projectId: string;
hackathon?: Hackathon;
}>;
joinedHackathons?: Array<{
diff --git a/lib/providers/hackathonProvider.tsx b/lib/providers/hackathonProvider.tsx
index e6b26fbbe..e21a12c16 100644
--- a/lib/providers/hackathonProvider.tsx
+++ b/lib/providers/hackathonProvider.tsx
@@ -99,7 +99,7 @@ interface HackathonDataContextType {
getHackathonById: (id: string) => Hackathon | undefined;
getHackathonBySlug: (slug: string) => Promise;
- setCurrentHackathon: (slug: string) => void;
+ setCurrentHackathon: (slug: string) => Promise;
addDiscussion: (content: string) => Promise;
addReply: (parentCommentId: string, content: string) => Promise;
diff --git a/package-lock.json b/package-lock.json
index 32c85f04a..74be24bf5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -125,6 +125,7 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"three": "^0.180.0",
+ "turndown": "^7.2.2",
"tw-animate-css": "^1.3.6",
"uuid": "^13.0.0",
"vaul": "^1.1.2",
@@ -137,6 +138,7 @@
"@types/p-limit": "^2.1.0",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
+ "@types/turndown": "^5.0.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
@@ -1609,6 +1611,12 @@
"langium": "^4.0.0"
}
},
+ "node_modules/@mixmark-io/domino": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/@monogrid/gainmap-js": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
@@ -6352,6 +6360,13 @@
"license": "MIT",
"optional": true
},
+ "node_modules/@types/turndown": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.6.tgz",
+ "integrity": "sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -16839,6 +16854,15 @@
}
}
},
+ "node_modules/turndown": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.2.tgz",
+ "integrity": "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@mixmark-io/domino": "^2.2.0"
+ }
+ },
"node_modules/tw-animate-css": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
diff --git a/package.json b/package.json
index 8e73c10a0..a557459e3 100644
--- a/package.json
+++ b/package.json
@@ -141,6 +141,7 @@
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1",
"three": "^0.180.0",
+ "turndown": "^7.2.2",
"tw-animate-css": "^1.3.6",
"uuid": "^13.0.0",
"vaul": "^1.1.2",
@@ -153,6 +154,7 @@
"@types/p-limit": "^2.1.0",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
+ "@types/turndown": "^5.0.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",