= {};
@@ -205,25 +205,23 @@ export default function PermissionsTable() {
setIsLoading(true);
try {
- // Merge admin and member permissions
const mergedPermissions: OrganizationPermissions = { ...permissions! };
defaultPermissionConfigs.forEach(config => {
const adminValue = editablePermissions.admin[config.key];
const memberValue = editablePermissions.member[config.key];
- if (adminValue !== undefined) {
+ if (adminValue !== undefined)
mergedPermissions[config.key] = adminValue;
- }
- if (memberValue !== undefined && config.key in mergedPermissions) {
+ if (memberValue !== undefined)
mergedPermissions[config.key] = memberValue;
- }
});
await updateOrganizationPermissions(activeOrgId, mergedPermissions);
setIsEditing(false);
setIsCustom(true);
setPermissions(mergedPermissions);
+
toast.success('Permissions updated successfully');
} catch {
toast.error('Failed to update permissions');
@@ -238,9 +236,10 @@ export default function PermissionsTable() {
setIsLoading(true);
try {
await resetOrganizationPermissions(activeOrgId);
- await loadPermissions(); // Reload to get default permissions
+ await loadPermissions();
setIsEditing(false);
setIsCustom(false);
+
toast.success('Permissions reset to defaults');
} catch {
toast.error('Failed to reset permissions');
@@ -257,11 +256,9 @@ export default function PermissionsTable() {
const editableValue = editablePermissions[role][config.key];
if (editableValue !== undefined) return editableValue;
}
-
if (permissions && permissions[config.key] !== undefined) {
return permissions[config.key];
}
-
return typeof config[role] === 'object' ? config[role].value : config[role];
};
@@ -302,6 +299,7 @@ export default function PermissionsTable() {
)}
+
{canEdit && (
{isEditing
@@ -335,6 +333,7 @@ export default function PermissionsTable() {
Reset to Defaults
+
{isLoading ? 'Saving...' : 'Save Changes'}
+
{
setIsEditing(false);
- loadPermissions(); // Reload to discard changes
+ loadPermissions();
}}
disabled={isLoading}
className='border-gray-600 text-white hover:bg-gray-800'
@@ -379,6 +379,7 @@ export default function PermissionsTable() {
+
{defaultPermissionConfigs.map((config, index) => (
- {/* Owner - Always true and not editable */}
+ {/* Owner */}
- {/* Admin - Separate control */}
+ {/* Admin */}
{isEditing && canEdit ? (
handleAdminPermissionChange(config.key, e.target.checked)
}
- className='h-4 w-4 rounded border-gray-600 bg-gray-800 text-green-500 focus:ring-green-500 focus:ring-offset-gray-900'
+ className='h-4 w-4 rounded border-gray-600 bg-gray-800'
/>
) : (
@@ -428,7 +429,7 @@ export default function PermissionsTable() {
)}
- {/* Member - Separate control */}
+ {/* Member */}
{isEditing && canEdit ? (
handleMemberPermissionChange(config.key, e.target.checked)
}
- className='h-4 w-4 rounded border-gray-600 bg-gray-800 text-green-500 focus:ring-green-500 focus:ring-offset-gray-900'
+ className='h-4 w-4 rounded border-gray-600 bg-gray-800'
/>
) : (
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
index 590f38332..1c1e226c3 100644
--- a/components/ui/tooltip.tsx
+++ b/components/ui/tooltip.tsx
@@ -52,7 +52,7 @@ function TooltipContent({
{...props}
>
{children}
-
+ {/*
*/}
);
diff --git a/hooks/hackathon/use-discussions.ts b/hooks/hackathon/use-discussions.ts
new file mode 100644
index 000000000..ec516618e
--- /dev/null
+++ b/hooks/hackathon/use-discussions.ts
@@ -0,0 +1,50 @@
+import { useState, useMemo } from 'react';
+import { useHackathonData } from '@/lib/providers/hackathonProvider';
+
+export function useDiscussions() {
+ const {
+ discussions,
+ addDiscussion,
+ addReply,
+ updateDiscussion,
+ deleteDiscussion,
+ reportDiscussion,
+ loading,
+ error,
+ } = useHackathonData();
+ const [sortBy, setSortBy] = useState<
+ 'createdAt' | 'updatedAt' | 'totalReactions'
+ >('createdAt');
+ const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
+
+ const sortedDiscussions = useMemo(() => {
+ return [...discussions].sort((a, b) => {
+ let comparison = 0;
+ if (sortBy === 'createdAt') {
+ comparison =
+ new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
+ } else if (sortBy === 'updatedAt') {
+ comparison =
+ new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
+ } else {
+ comparison = a.totalReactions - b.totalReactions;
+ }
+ return sortOrder === 'desc' ? -comparison : comparison;
+ });
+ }, [discussions, sortBy, sortOrder]);
+
+ return {
+ discussions: sortedDiscussions,
+ sortBy,
+ sortOrder,
+ loading,
+ error,
+ setSortBy,
+ setSortOrder,
+ addDiscussion,
+ addReply,
+ updateDiscussion,
+ deleteDiscussion,
+ reportDiscussion,
+ };
+}
diff --git a/hooks/hackathon/use-hackathon-transform.ts b/hooks/hackathon/use-hackathon-transform.ts
index 7d380cc96..9365a181d 100644
--- a/hooks/hackathon/use-hackathon-transform.ts
+++ b/hooks/hackathon/use-hackathon-transform.ts
@@ -111,12 +111,12 @@ export function useHackathonTransform() {
}
// Generate slug from title (simple version)
- const title =
- hackathon.information?.title || hackathon.title || 'untitled';
- const slug = title
- .toLowerCase()
- .replace(/[^a-z0-9]+/g, '-')
- .replace(/(^-|-$)/g, '');
+ // const title =
+ // hackathon.information?.title || hackathon.title || 'untitled';
+ // const slug = title
+ // .toLowerCase()
+ // .replace(/[^a-z0-9]+/g, '-')
+ // .replace(/(^-|-$)/g, '');
// Get organization name - check if it's in the hackathon object or use provided
const extendedHackathon = hackathon as ExtendedHackathon;
@@ -153,7 +153,7 @@ export function useHackathonTransform() {
return {
hackathonId: hackathon._id,
organizationName: orgName,
- hackathonSlug: slug,
+ hackathonSlug: hackathon.information.slug,
organizerName: orgName,
organizerLogo: '/avatar.png', // This should come from organization data
hackathonImage:
diff --git a/hooks/hackathon/use-participants.ts b/hooks/hackathon/use-participants.ts
new file mode 100644
index 000000000..87e8750cb
--- /dev/null
+++ b/hooks/hackathon/use-participants.ts
@@ -0,0 +1,88 @@
+import { useState, useMemo } from 'react';
+import { useHackathonData } from '@/lib/providers/hackathonProvider';
+
+export function useParticipants() {
+ const { participants } = useHackathonData();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [sortBy, setSortBy] = useState('newest');
+ const [submissionFilter, setSubmissionFilter] = useState('all');
+ const [skillFilter, setSkillFilter] = useState('all');
+
+ const filteredAndSortedParticipants = useMemo(() => {
+ let filtered = [...participants];
+
+ // Search
+ if (searchTerm) {
+ filtered = filtered.filter(
+ p =>
+ p.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ p.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ p.role?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ p.categories?.some(cat =>
+ cat.toLowerCase().includes(searchTerm.toLowerCase())
+ )
+ );
+ }
+
+ // Submission filter
+ if (submissionFilter === 'submitted') {
+ filtered = filtered.filter(p => p.hasSubmitted);
+ }
+ if (submissionFilter === 'not_submitted') {
+ filtered = filtered.filter(p => !p.hasSubmitted);
+ }
+
+ // Skill filter
+ if (skillFilter !== 'all') {
+ filtered = filtered.filter(
+ p =>
+ p.role?.toLowerCase().includes(skillFilter) ||
+ p.categories?.some(cat => cat.toLowerCase().includes(skillFilter))
+ );
+ }
+
+ // Sorting
+ filtered.sort((a, b) => {
+ switch (sortBy) {
+ case 'newest':
+ return (
+ new Date(b.joinedDate || '').getTime() -
+ new Date(a.joinedDate || '').getTime()
+ );
+ case 'oldest':
+ return (
+ new Date(a.joinedDate || '').getTime() -
+ new Date(b.joinedDate || '').getTime()
+ );
+ case 'followers_high':
+ return (b.followers || 0) - (a.followers || 0);
+ case 'followers_low':
+ return (a.followers || 0) - (b.followers || 0);
+ case 'projects_high':
+ return (b.projects || 0) - (a.projects || 0);
+ case 'projects_low':
+ return (a.projects || 0) - (b.projects || 0);
+ default:
+ return 0;
+ }
+ });
+
+ return filtered;
+ }, [participants, searchTerm, sortBy, submissionFilter, skillFilter]);
+
+ const submittedCount = participants.filter(p => p.hasSubmitted).length;
+
+ return {
+ participants: filteredAndSortedParticipants,
+ totalParticipants: participants.length,
+ submittedCount,
+ searchTerm,
+ sortBy,
+ submissionFilter,
+ skillFilter,
+ setSearchTerm,
+ setSortBy,
+ setSubmissionFilter,
+ setSkillFilter,
+ };
+}
diff --git a/hooks/hackathon/use-submissions.ts b/hooks/hackathon/use-submissions.ts
new file mode 100644
index 000000000..e7fb26649
--- /dev/null
+++ b/hooks/hackathon/use-submissions.ts
@@ -0,0 +1,118 @@
+import { useState, useMemo } from 'react';
+import { useHackathonData } from '@/lib/providers/hackathonProvider';
+
+export function useSubmissions() {
+ const { submissions } = useHackathonData();
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedSort, setSelectedSort] = useState('newest');
+ const [selectedCategory, setSelectedCategory] = useState('All Categories');
+
+ // Memoize sort options to avoid changing reference each render
+ const sortOptions = useMemo(
+ () => [
+ { label: 'Newest First', value: 'newest' },
+ { label: 'Oldest First', value: 'oldest' },
+ { label: 'Most Upvoted', value: 'upvotes_high' },
+ { label: 'Least Upvoted', value: 'upvotes_low' },
+ { label: 'Highest Score', value: 'score_high' },
+ { label: 'Most Commented', value: 'comments_high' },
+ ],
+ []
+ );
+
+ const categoryOptions = useMemo(() => {
+ const categoriesSet = new Set
();
+ submissions.forEach(sub => {
+ if (sub.category) categoriesSet.add(sub.category);
+ if (sub.categories) sub.categories.forEach(cat => categoriesSet.add(cat));
+ });
+
+ return [
+ { label: 'All Categories', value: 'all' },
+ ...Array.from(categoriesSet).map(cat => ({
+ label: cat,
+ value: cat.toLowerCase(),
+ })),
+ ];
+ }, [submissions]);
+
+ const filteredAndSortedSubmissions = useMemo(() => {
+ let filtered = submissions;
+
+ // Filter by search term
+ if (searchTerm) {
+ filtered = filtered.filter(
+ sub =>
+ sub.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ sub.submitterName.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ }
+
+ // Filter by category
+ if (selectedCategory !== 'All Categories') {
+ filtered = filtered.filter(sub => {
+ const allCategories = sub.category
+ ? [sub.category, ...(sub.categories || [])]
+ : sub.categories || [];
+ return allCategories.some(
+ cat => cat.toLowerCase() === selectedCategory.toLowerCase()
+ );
+ });
+ }
+
+ // Sort
+ const sortValue =
+ sortOptions.find(opt => opt.label === selectedSort)?.value || 'newest';
+
+ filtered = [...filtered].sort((a, b) => {
+ switch (sortValue) {
+ case 'newest':
+ if (a.submittedDate && b.submittedDate) {
+ return (
+ new Date(b.submittedDate).getTime() -
+ new Date(a.submittedDate).getTime()
+ );
+ }
+ return 0;
+ case 'oldest':
+ if (a.submittedDate && b.submittedDate) {
+ return (
+ new Date(a.submittedDate).getTime() -
+ new Date(b.submittedDate).getTime()
+ );
+ }
+ return 0;
+ case 'upvotes_high':
+ return (
+ (b.votes?.current || b.upvotes || 0) -
+ (a.votes?.current || a.upvotes || 0)
+ );
+ case 'upvotes_low':
+ return (
+ (a.votes?.current || a.upvotes || 0) -
+ (b.votes?.current || b.upvotes || 0)
+ );
+ case 'score_high':
+ return (b.score || 0) - (a.score || 0);
+ case 'comments_high':
+ return (b.comments || 0) - (a.comments || 0);
+ default:
+ return 0;
+ }
+ });
+
+ return filtered;
+ }, [submissions, searchTerm, selectedSort, selectedCategory, sortOptions]);
+
+ return {
+ submissions: filteredAndSortedSubmissions,
+ searchTerm,
+ selectedSort,
+ selectedCategory,
+ sortOptions,
+ categoryOptions,
+ setSearchTerm,
+ setSelectedSort,
+ setSelectedCategory,
+ };
+}
diff --git a/lib/api/hackathon.ts b/lib/api/hackathon.ts
new file mode 100644
index 000000000..08aae0ac1
--- /dev/null
+++ b/lib/api/hackathon.ts
@@ -0,0 +1,117 @@
+import { api } from './api';
+import {
+ Hackathon,
+ Participant,
+ SubmissionCardProps,
+ Discussion,
+} from '@/types/hackathon';
+
+export interface HackathonListResponse {
+ success: boolean;
+ data: {
+ hackathons: Hackathon[];
+ hasMore: boolean;
+ total: number;
+ currentPage: number;
+ totalPages: number;
+ };
+ message: string;
+}
+
+export interface HackathonResponse {
+ success: boolean;
+ data: Hackathon;
+ message: string;
+}
+
+export interface ParticipantsResponse {
+ success: boolean;
+ data: {
+ participants: Participant[];
+ hasMore: boolean;
+ total: number;
+ currentPage: number;
+ totalPages: number;
+ };
+ message: string;
+}
+
+export interface SubmissionsResponse {
+ success: boolean;
+ data: {
+ submissions: SubmissionCardProps[];
+ hasMore: boolean;
+ total: number;
+ currentPage: number;
+ totalPages: number;
+ };
+ message: string;
+}
+
+export interface DiscussionsResponse {
+ success: boolean;
+ data: Discussion[];
+ message: string;
+}
+
+// API functions remain the same...
+export const getHackathons = async (): Promise => {
+ const response = await api.get('/hackathons');
+ return response.data;
+};
+
+// Get single hackathon by slug
+export const getHackathon = async (
+ slug: string
+): Promise => {
+ const response = await api.get(`/hackathons/${slug}`);
+ return response.data;
+};
+
+// Get featured hackathons
+export const getFeaturedHackathons =
+ async (): Promise => {
+ const response = await api.get(
+ '/hackathons?featured=true'
+ );
+ return response.data;
+ };
+
+// Get participants for a hackathon
+export const getHackathonParticipants = async (
+ slug: string,
+ params?: { page?: number; limit?: number; status?: string }
+): Promise => {
+ const queryParams = new URLSearchParams();
+ if (params?.page) queryParams.append('page', params.page.toString());
+ if (params?.limit) queryParams.append('limit', params.limit.toString());
+ if (params?.status) queryParams.append('status', params.status);
+
+ const response = await api.get(
+ `/hackathons/${slug}/participants?${queryParams.toString()}`
+ );
+ return response.data;
+};
+
+// Get submissions for a hackathon
+export const getHackathonSubmissions = async (
+ slug: string,
+ params?: { page?: number; limit?: number; status?: string; sort?: string }
+): Promise => {
+ const queryParams = new URLSearchParams();
+ if (params?.page) queryParams.append('page', params.page.toString());
+ if (params?.limit) queryParams.append('limit', params.limit.toString());
+ if (params?.status) queryParams.append('status', params.status);
+ if (params?.sort) queryParams.append('sort', params.sort);
+
+ const response = await api.get(
+ `/hackathons/${slug}/submissions?${queryParams.toString()}`
+ );
+ return response.data;
+};
+
+// Get discussions for a hackathon (you'll need to implement this endpoint)
+// export const getHackathonDiscussions = async (hackathonId: string): Promise => {
+// const response = await api.get(`/hackathons/${hackathonId}/discussions`);
+// return response.data;
+// };
diff --git a/lib/api/hackathons.ts b/lib/api/hackathons.ts
index a0471861c..778880906 100644
--- a/lib/api/hackathons.ts
+++ b/lib/api/hackathons.ts
@@ -45,6 +45,7 @@ export interface HackathonInformation {
category?: HackathonCategory; // Legacy format (single category)
categories?: HackathonCategory[]; // New format (array of categories)
venue?: HackathonVenue;
+ slug: string;
}
// Timeline Tab Types
@@ -737,6 +738,7 @@ interface FlatHackathonData {
rewards?: HackathonRewards;
judging?: HackathonJudging;
collaboration?: HackathonCollaboration;
+ slug: string;
}
/**
@@ -780,6 +782,7 @@ const transformHackathonResponse = (
title: flat.title || '',
banner: flat.banner || '',
description: flat.description || '',
+ slug: flat.slug || '',
// Support both new format (categories) and legacy format (category)
categories: Array.isArray(flat.categories)
? (flat.categories as HackathonCategory[])
@@ -1410,6 +1413,7 @@ export const transformPublicHackathonToHackathon = (
banner: publicHackathon.imageUrl,
description: publicHackathon.description,
category: categoryEnum,
+ slug: publicHackathon.slug,
venue,
},
timeline: {
diff --git a/lib/mocks/hackathons-mock.ts b/lib/mocks/hackathons-mock.ts
deleted file mode 100644
index 458344611..000000000
--- a/lib/mocks/hackathons-mock.ts
+++ /dev/null
@@ -1,691 +0,0 @@
-import type { Hackathon } from '@/lib/api/hackathons';
-import {
- HackathonCategory,
- ParticipantType,
- VenueType,
-} from '@/lib/api/hackathons';
-
-export const mockHackathons: (Hackathon & {
- _organizationName: string;
- featured?: boolean;
- categories?: string[];
-})[] = [
- {
- _id: '1',
- organizationId: 'org1',
- _organizationName: 'scaffoldstellar',
- status: 'published',
- featured: true,
- createdAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Stellar DeFi Innovation Challenge',
- information: {
- title: 'Stellar DeFi Innovation Challenge',
- banner: '/banner.png',
- description:
- 'Build the next generation of DeFi applications on Stellar. Create innovative solutions for lending, borrowing, and yield farming.',
- category: HackathonCategory.DEFI,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- categories: [
- HackathonCategory.DEFI,
- HackathonCategory.INFRASTRUCTURE,
- HackathonCategory.CROSS_CHAIN,
- ],
- timeline: {
- startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 20 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 25 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 30 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
- teamMin: 1,
- teamMax: 5,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 500000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 250000,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 1000000,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Innovation',
- weight: 30,
- description: 'Novelty and creativity of the solution',
- },
- {
- title: 'Technical Excellence',
- weight: 40,
- description: 'Code quality and architecture',
- },
- {
- title: 'Impact',
- weight: 30,
- description: 'Potential impact on the ecosystem',
- },
- ],
- },
- collaboration: {
- contactEmail: 'contact@scaffoldstellar.com',
- telegram: 'https://t.me/scaffoldstellar',
- discord: 'https://discord.gg/scaffoldstellar',
- },
- },
- {
- _id: '2',
- organizationId: 'org2',
- _organizationName: 'stellarfoundation',
- status: 'ongoing',
- featured: true,
- createdAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'NFT Marketplace Builder',
- information: {
- title: 'NFT Marketplace Builder',
- banner: '/blog1.png',
- description:
- 'Create a fully-featured NFT marketplace on Stellar with minting, trading, and auction capabilities.',
- category: HackathonCategory.NFTS,
- venue: {
- type: VenueType.PHYSICAL,
- country: 'United States',
- city: 'San Francisco',
- venueName: 'Stellar HQ',
- },
- },
- categories: [HackathonCategory.NFTS, HackathonCategory.WEB3_GAMING],
- timeline: {
- startDate: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 10 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 15 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 20 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'America/Los_Angeles',
- },
- participation: {
- participantType: ParticipantType.TEAM,
- teamMin: 2,
- teamMax: 4,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 75000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 35000,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 15000,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'User Experience',
- weight: 35,
- description: 'Ease of use and design quality',
- },
- {
- title: 'Functionality',
- weight: 40,
- description: 'Feature completeness',
- },
- {
- title: 'Scalability',
- weight: 25,
- description: 'Ability to handle growth',
- },
- ],
- },
- collaboration: {
- contactEmail: 'hackathon@stellar.org',
- },
- },
- {
- _id: '3',
- organizationId: 'org3',
- _organizationName: 'web3builders',
- status: 'published',
- featured: true,
- createdAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Cross-Chain Bridge Protocol',
- information: {
- title: 'Cross-Chain Bridge Protocol',
- banner: '/blog2.jpg',
- description:
- 'Develop a secure and efficient bridge protocol connecting Stellar with other blockchain networks.',
- category: HackathonCategory.CROSS_CHAIN,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- categories: [
- HackathonCategory.CROSS_CHAIN,
- HackathonCategory.INFRASTRUCTURE,
- HackathonCategory.PRIVACY,
- HackathonCategory.DEFI,
- ],
- timeline: {
- startDate: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 30 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 35 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 40 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
- teamMin: 1,
- teamMax: 6,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 100000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 50000,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 25000,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Security',
- weight: 50,
- description: 'Security and audit readiness',
- },
- {
- title: 'Performance',
- weight: 30,
- description: 'Speed and efficiency',
- },
- {
- title: 'Innovation',
- weight: 20,
- description: 'Novel approach to bridging',
- },
- ],
- },
- collaboration: {
- contactEmail: 'bridge@web3builders.io',
- discord: 'https://discord.gg/web3builders',
- },
- },
- {
- _id: '4',
- organizationId: 'org1',
- _organizationName: 'scaffoldstellar',
- status: 'published',
- featured: false,
- createdAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Layer 2 Scaling Solutions',
- information: {
- title: 'Layer 2 Scaling Solutions',
- banner: '/blog3.jpg',
- description:
- 'Build Layer 2 solutions to improve Stellar network throughput and reduce transaction costs.',
- category: HackathonCategory.LAYER_2,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- timeline: {
- startDate: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 15 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 20 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 25 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM,
- teamMin: 2,
- teamMax: 5,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 40000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 20000,
- currency: 'USDC',
- description: 'Second Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Technical Merit',
- weight: 50,
- description: 'Technical quality and innovation',
- },
- {
- title: 'Scalability',
- weight: 30,
- description: 'Ability to scale effectively',
- },
- {
- title: 'Documentation',
- weight: 20,
- description: 'Quality of documentation',
- },
- ],
- },
- collaboration: {
- contactEmail: 'layer2@scaffoldstellar.com',
- },
- },
- {
- _id: '5',
- organizationId: 'org4',
- _organizationName: 'gaminglab',
- status: 'ongoing',
- featured: false,
- createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Web3 Gaming Platform',
- information: {
- title: 'Web3 Gaming Platform',
- banner: '/blog4.jpg',
- description:
- 'Create a Web3 gaming platform with NFT integration, play-to-earn mechanics, and Stellar-based transactions.',
- category: HackathonCategory.WEB3_GAMING,
- venue: {
- type: VenueType.PHYSICAL,
- country: 'United Kingdom',
- city: 'London',
- venueName: 'Gaming Lab HQ',
- },
- },
- categories: [
- HackathonCategory.WEB3_GAMING,
- HackathonCategory.NFTS,
- HackathonCategory.SOCIAL_TOKENS,
- ],
- timeline: {
- startDate: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 25 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 30 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 35 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'Europe/London',
- },
- participation: {
- participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
- teamMin: 1,
- teamMax: 4,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 60000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 30000,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 15000,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Gameplay',
- weight: 40,
- description: 'Fun and engaging gameplay',
- },
- {
- title: 'Web3 Integration',
- weight: 35,
- description: 'Quality of blockchain integration',
- },
- {
- title: 'Graphics',
- weight: 25,
- description: 'Visual quality and design',
- },
- ],
- },
- collaboration: {
- contactEmail: 'gaming@gaminglab.io',
- telegram: 'https://t.me/gaminglab',
- },
- },
- {
- _id: '6',
- organizationId: 'org5',
- _organizationName: 'daocollective',
- status: 'published',
- featured: false,
- createdAt: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'DAO Governance Platform',
- information: {
- title: 'DAO Governance Platform',
- banner: '/blog5.jpg',
- description:
- 'Build a comprehensive DAO governance platform with voting, proposals, and treasury management on Stellar.',
- category: HackathonCategory.DAOS,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- timeline: {
- startDate: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 5 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 10 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 15 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM,
- teamMin: 3,
- teamMax: 6,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 45000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 22500,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 10000,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Governance Features',
- weight: 40,
- description: 'Completeness of governance features',
- },
- {
- title: 'Security',
- weight: 35,
- description: 'Security and audit readiness',
- },
- {
- title: 'User Experience',
- weight: 25,
- description: 'Ease of use and interface quality',
- },
- ],
- },
- collaboration: {
- contactEmail: 'dao@daocollective.org',
- discord: 'https://discord.gg/daocollective',
- },
- },
- {
- _id: '7',
- organizationId: 'org6',
- _organizationName: 'infrastructurehub',
- status: 'completed',
- featured: false,
- createdAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
- publishedAt: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Stellar Infrastructure Tools',
- information: {
- title: 'Stellar Infrastructure Tools',
- banner: '/blog6.jpg',
- description:
- 'Develop essential infrastructure tools and SDKs to make building on Stellar easier for developers.',
- category: HackathonCategory.INFRASTRUCTURE,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- timeline: {
- startDate: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() - 10 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() - 2 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
- teamMin: 1,
- teamMax: 4,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 35000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 17500,
- currency: 'USDC',
- description: 'Second Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Developer Experience',
- weight: 45,
- description: 'Ease of use for developers',
- },
- {
- title: 'Documentation',
- weight: 30,
- description: 'Quality and completeness of docs',
- },
- {
- title: 'Code Quality',
- weight: 25,
- description: 'Code quality and best practices',
- },
- ],
- },
- collaboration: {
- contactEmail: 'tools@infrastructurehub.io',
- },
- },
- {
- _id: '8',
- organizationId: 'org7',
- _organizationName: 'privacyfirst',
- status: 'published',
- featured: false,
- createdAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
- updatedAt: new Date().toISOString(),
- publishedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
- title: 'Privacy-Preserving Solutions',
- information: {
- title: 'Privacy-Preserving Solutions',
- banner: '/landing/explore/project-placeholder-1.png',
- description:
- 'Build privacy-preserving solutions using zero-knowledge proofs and other cryptographic techniques on Stellar.',
- category: HackathonCategory.PRIVACY,
- venue: {
- type: VenueType.VIRTUAL,
- },
- },
- timeline: {
- startDate: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
- submissionDeadline: new Date(
- Date.now() + 40 * 24 * 60 * 60 * 1000
- ).toISOString(),
- judgingDate: new Date(
- Date.now() + 45 * 24 * 60 * 60 * 1000
- ).toISOString(),
- winnerAnnouncementDate: new Date(
- Date.now() + 50 * 24 * 60 * 60 * 1000
- ).toISOString(),
- timezone: 'UTC',
- },
- participation: {
- participantType: ParticipantType.TEAM_OR_INDIVIDUAL,
- teamMin: 1,
- teamMax: 5,
- },
- rewards: {
- prizeTiers: [
- {
- position: '1st',
- amount: 55000,
- currency: 'USDC',
- description: 'Grand Prize',
- },
- {
- position: '2nd',
- amount: 27500,
- currency: 'USDC',
- description: 'Second Place',
- },
- {
- position: '3rd',
- amount: 12500,
- currency: 'USDC',
- description: 'Third Place',
- },
- ],
- },
- judging: {
- criteria: [
- {
- title: 'Privacy Technology',
- weight: 50,
- description: 'Quality of privacy-preserving tech',
- },
- {
- title: 'Security',
- weight: 30,
- description: 'Security and audit readiness',
- },
- {
- title: 'Practicality',
- weight: 20,
- description: 'Real-world applicability',
- },
- ],
- },
- collaboration: {
- contactEmail: 'privacy@privacyfirst.org',
- },
- },
-];
diff --git a/lib/providers/hackathonProvider.tsx b/lib/providers/hackathonProvider.tsx
new file mode 100644
index 000000000..901ad0b48
--- /dev/null
+++ b/lib/providers/hackathonProvider.tsx
@@ -0,0 +1,502 @@
+'use client';
+
+import React, {
+ createContext,
+ useContext,
+ ReactNode,
+ useEffect,
+ useState,
+ useCallback,
+ useRef,
+} from 'react';
+import {
+ Discussion,
+ Participant,
+ SubmissionCardProps,
+ Hackathon,
+} from '@/types/hackathon';
+import {
+ getHackathons,
+ getHackathon,
+ getHackathonParticipants,
+ getHackathonSubmissions,
+} from '@/lib/api/hackathon';
+
+// -------------------
+// Types
+// -------------------
+
+interface TimelineEvent {
+ event: string;
+ date: string;
+}
+
+interface Prize {
+ title: string;
+ rank: string;
+ prize: string;
+ icon: string;
+ details: string[];
+}
+
+interface ResourceItem {
+ id: number;
+ title: string;
+ type: 'link';
+ size: string;
+ url: string;
+ uploadDate: string;
+ description: string;
+}
+
+interface HackathonDataContextType {
+ hackathons: Hackathon[];
+ featuredHackathons: Hackathon[];
+ ongoingHackathons: Hackathon[];
+ upcomingHackathons: Hackathon[];
+ pastHackathons: Hackathon[];
+
+ currentHackathon: Hackathon | null;
+ discussions: Discussion[];
+ participants: Participant[];
+ submissions: SubmissionCardProps[];
+ content: string;
+ timelineEvents: TimelineEvent[];
+ prizes: Prize[];
+ resources: ResourceItem[];
+
+ loading: boolean;
+ error: string | null;
+
+ getHackathonById: (id: string) => Hackathon | undefined;
+ getHackathonBySlug: (slug: string) => Promise;
+
+ setCurrentHackathon: (slug: string) => void;
+
+ addDiscussion: (content: string) => Promise;
+ addReply: (parentCommentId: string, content: string) => Promise;
+ updateDiscussion: (commentId: string, content: string) => Promise;
+ deleteDiscussion: (commentId: string) => Promise;
+ reportDiscussion: (
+ commentId: string,
+ reason: string,
+ description?: string
+ ) => Promise;
+
+ setLoading: (loading: boolean) => void;
+ setError: (error: string | null) => void;
+
+ refreshHackathons: () => Promise;
+ refreshCurrentHackathon: () => Promise;
+}
+
+const HackathonDataContext = createContext<
+ HackathonDataContextType | undefined
+>(undefined);
+
+interface HackathonDataProviderProps {
+ children: ReactNode;
+ hackathonSlug?: string;
+}
+
+// -----------------------------
+// Provider
+// -----------------------------
+
+export function HackathonDataProvider({
+ children,
+ hackathonSlug,
+}: HackathonDataProviderProps) {
+ const [hackathons, setHackathons] = useState([]);
+ const [currentHackathon, setCurrentHackathonState] =
+ useState(null);
+ const [currentHackathonSlug, setCurrentHackathonSlug] = useState<
+ string | null
+ >(hackathonSlug || null);
+ const [participants, setParticipants] = useState([]);
+ const [submissions, setSubmissions] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setErrorState] = useState(null);
+
+ const fetchingRef = useRef(false);
+
+ // --------------------------------
+ // Error helper
+ // --------------------------------
+ const setError = useCallback((err: string | null) => {
+ setErrorState(err);
+ }, []);
+
+ // --------------------------------
+ // Fetch all hackathons
+ // --------------------------------
+ const fetchHackathons = useCallback(async () => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const response = await getHackathons();
+
+ if (response.success && response.data) {
+ let hackathonsArray: Hackathon[];
+
+ if (Array.isArray(response.data)) {
+ hackathonsArray = response.data;
+ } else if (
+ response.data.hackathons &&
+ Array.isArray(response.data.hackathons)
+ ) {
+ hackathonsArray = response.data.hackathons;
+ } else {
+ hackathonsArray = [];
+ }
+
+ setHackathons(hackathonsArray);
+ } else {
+ throw new Error(response.message || 'Failed to fetch hackathons');
+ }
+ } catch (error: unknown) {
+ const msg =
+ error instanceof Error ? error.message : 'Failed to fetch hackathons';
+ setError(msg);
+ setHackathons([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [setError]);
+
+ // --------------------------------
+ // Fetch hackathon by slug
+ // --------------------------------
+ const fetchHackathonBySlug = useCallback(
+ async (slug: string) => {
+ if (fetchingRef.current) return null;
+
+ try {
+ fetchingRef.current = true;
+ setLoading(true);
+ setError(null);
+
+ const response = await getHackathon(slug);
+ if (response.success && response.data) {
+ setCurrentHackathonState(response.data);
+ return response.data;
+ } else {
+ throw new Error(response.message || 'Hackathon not found');
+ }
+ } catch (error: unknown) {
+ const msg =
+ error instanceof Error ? error.message : 'Failed to fetch hackathon';
+ setError(msg);
+ return null;
+ } finally {
+ fetchingRef.current = false;
+ setLoading(false);
+ }
+ },
+ [setError]
+ );
+
+ // --------------------------------
+ // Fetch participants
+ // --------------------------------
+ const fetchParticipants = useCallback(async (slug: string) => {
+ try {
+ const response = await getHackathonParticipants(slug, { limit: 50 });
+ if (response.success && response.data) {
+ setParticipants(response.data.participants);
+ }
+ } catch {
+ /* ignore */
+ }
+ }, []);
+
+ // --------------------------------
+ // Fetch submissions
+ // --------------------------------
+ const fetchSubmissions = useCallback(async (slug: string) => {
+ try {
+ const response = await getHackathonSubmissions(slug, { limit: 50 });
+ if (response.success && response.data) {
+ setSubmissions(response.data.submissions);
+ }
+ } catch {
+ /* ignore */
+ }
+ }, []);
+
+ // --------------------------------
+ // Computed lists
+ // --------------------------------
+ const featuredHackathons = React.useMemo(
+ () => hackathons.filter(h => h.featured),
+ [hackathons]
+ );
+
+ const ongoingHackathons = React.useMemo(
+ () => hackathons.filter(h => h.status === 'ongoing'),
+ [hackathons]
+ );
+
+ const upcomingHackathons = React.useMemo(
+ () => hackathons.filter(h => h.status === 'upcoming'),
+ [hackathons]
+ );
+
+ const pastHackathons = React.useMemo(
+ () => hackathons.filter(h => h.status === 'ended'),
+ [hackathons]
+ );
+
+ const getHackathonById = (id: string) => hackathons.find(h => h.id === id);
+
+ const getHackathonBySlug = async (slug: string) =>
+ await fetchHackathonBySlug(slug);
+
+ // --------------------------------
+ // Set current hackathon
+ // --------------------------------
+ const setCurrentHackathon = useCallback(
+ async (slug: string) => {
+ if (currentHackathonSlug === slug && fetchingRef.current) return;
+
+ setCurrentHackathonSlug(slug);
+ const data = await fetchHackathonBySlug(slug);
+
+ if (data) {
+ await Promise.all([fetchParticipants(slug), fetchSubmissions(slug)]);
+ }
+ },
+ [
+ currentHackathonSlug,
+ fetchHackathonBySlug,
+ fetchParticipants,
+ fetchSubmissions,
+ ]
+ );
+
+ const refreshHackathons = async () => {
+ await fetchHackathons();
+ };
+
+ const refreshCurrentHackathon = async () => {
+ if (currentHackathonSlug) {
+ await setCurrentHackathon(currentHackathonSlug);
+ }
+ };
+
+ // --------------------------------
+ // Mock Data
+ // --------------------------------
+
+ const mockDiscussions: Discussion[] = [
+ {
+ _id: '1',
+ userId: {
+ _id: 'user1',
+ profile: {
+ firstName: 'Sarah',
+ lastName: 'Chen',
+ username: 'sarahc',
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah',
+ },
+ },
+ projectId: '',
+ content:
+ 'Excited to participate in this hackathon! Are there any team formation channels?',
+ status: 'active',
+ createdAt: '2025-01-10T10:30:00Z',
+ updatedAt: '2025-01-10T10:30:00Z',
+ totalReactions: 12,
+ reactionCounts: { LIKE: 12, DISLIKE: 0, HELPFUL: 0 },
+ editHistory: [],
+ replyCount: 1,
+ isSpam: false,
+ reports: [],
+ replies: [
+ {
+ _id: '1-1',
+ userId: {
+ _id: 'user2',
+ profile: {
+ firstName: 'Alex',
+ lastName: 'Rodriguez',
+ username: 'alexr',
+ avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alex',
+ },
+ },
+ projectId: '',
+ content:
+ "Yes! Check out the Discord server, there's a #team-formation channel.",
+ status: 'active',
+ createdAt: '2025-01-10T11:00:00Z',
+ updatedAt: '2025-01-10T11:00:00Z',
+ totalReactions: 5,
+ reactionCounts: { LIKE: 5, DISLIKE: 0, HELPFUL: 0 },
+ editHistory: [],
+ replyCount: 0,
+ isSpam: false,
+ reports: [],
+ replies: [],
+ parentCommentId: '1',
+ },
+ ],
+ },
+ ];
+
+ const mockContent = `# ${
+ currentHackathon?.title || 'Hackathon'
+ }\n\n## 🌐 Hackathon Theme\n${
+ currentHackathon?.subtitle || 'Build innovative solutions'
+ }\n\n## Challenge Description\n${
+ currentHackathon?.description ||
+ 'Create an innovative project that solves real-world problems.'
+ }`;
+
+ const mockTimelineEvents: TimelineEvent[] = currentHackathon
+ ? [
+ {
+ event: 'Hackathon Launch',
+ date: new Date(currentHackathon.startDate).toLocaleDateString(),
+ },
+ {
+ event: 'Submission Deadline',
+ date: new Date(currentHackathon.deadline).toLocaleDateString(),
+ },
+ {
+ event: 'Winners Announced',
+ date: new Date(currentHackathon.endDate).toLocaleDateString(),
+ },
+ ]
+ : [];
+
+ const mockPrizes: Prize[] = currentHackathon
+ ? [
+ {
+ title: 'Grand Prize',
+ rank: '1 winner',
+ prize: `${currentHackathon.totalPrizePool} in prizes`,
+ icon: '⭐',
+ details: [
+ `Prize: ${currentHackathon.totalPrizePool}`,
+ 'Premium Swag Box',
+ ],
+ },
+ ]
+ : [];
+
+ const mockResources: ResourceItem[] = currentHackathon?.resources
+ ? currentHackathon.resources.map((resource, index) => ({
+ id: index + 1,
+ title: `Resource ${index + 1}`,
+ type: 'link',
+ size: 'N/A',
+ url: resource,
+ uploadDate: new Date().toISOString(),
+ description: 'Additional resource for participants',
+ }))
+ : [];
+
+ // --------------------------------
+ // Discussion placeholder functions
+ // --------------------------------
+
+ const addDiscussion = async (content: string) => {
+ void content;
+ };
+
+ const addReply = async (parentCommentId: string, content: string) => {
+ void parentCommentId;
+ void content;
+ };
+
+ const updateDiscussion = async (commentId: string, content: string) => {
+ void commentId;
+ void content;
+ };
+
+ const deleteDiscussion = async (commentId: string) => {
+ void commentId;
+ };
+
+ const reportDiscussion = async (
+ commentId: string,
+ reason: string,
+ description?: string
+ ) => {
+ void commentId;
+ void reason;
+ void description;
+ };
+
+ // --------------------------------
+ // Initial load
+ // --------------------------------
+
+ useEffect(() => {
+ fetchHackathons();
+ }, [fetchHackathons]);
+
+ useEffect(() => {
+ if (hackathonSlug && !currentHackathon) {
+ setCurrentHackathon(hackathonSlug);
+ }
+ }, [hackathonSlug, currentHackathon, setCurrentHackathon]);
+
+ // --------------------------------
+ // Context Value
+ // --------------------------------
+
+ const value: HackathonDataContextType = {
+ hackathons,
+ featuredHackathons,
+ ongoingHackathons,
+ upcomingHackathons,
+ pastHackathons,
+
+ currentHackathon,
+ discussions: mockDiscussions,
+ participants,
+ submissions,
+ content: mockContent,
+ timelineEvents: mockTimelineEvents,
+ prizes: mockPrizes,
+ resources: mockResources,
+
+ loading,
+ error,
+
+ getHackathonById,
+ getHackathonBySlug,
+ setCurrentHackathon,
+ addDiscussion,
+ addReply,
+ updateDiscussion,
+ deleteDiscussion,
+ reportDiscussion,
+ setLoading,
+ setError,
+ refreshHackathons,
+ refreshCurrentHackathon,
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+// -----------------------------
+// Hook
+// -----------------------------
+
+export const useHackathonData = () => {
+ const context = useContext(HackathonDataContext);
+ if (!context) {
+ throw new Error(
+ 'useHackathonData must be used within a HackathonDataProvider'
+ );
+ }
+ return context;
+};
diff --git a/lib/utils/hackathon-form-transforms.ts b/lib/utils/hackathon-form-transforms.ts
index c4e9096bc..6b7089eb6 100644
--- a/lib/utils/hackathon-form-transforms.ts
+++ b/lib/utils/hackathon-form-transforms.ts
@@ -42,6 +42,7 @@ export const transformToApiFormat = (stepData: {
title: info?.name || '',
banner: info?.banner || '',
description: info?.description || '',
+ slug: info?.slug || '',
// Send categories array (new format, recommended)
categories:
categoriesArray.length > 0
diff --git a/types/hackathon.ts b/types/hackathon.ts
new file mode 100644
index 000000000..f739ed43f
--- /dev/null
+++ b/types/hackathon.ts
@@ -0,0 +1,101 @@
+export interface Participant {
+ id: string | number;
+ name: string;
+ username: string;
+ avatar: string;
+ verified?: boolean;
+ joinedDate?: string;
+ role?: string;
+ description?: string;
+ categories?: string[];
+ projects?: number;
+ followers?: number;
+ following?: number;
+ hasSubmitted?: boolean;
+}
+
+export interface CommentUser {
+ _id: string;
+ profile: {
+ firstName: string;
+ lastName: string;
+ username: string;
+ avatar?: string;
+ };
+}
+
+export interface CommentReactionCounts {
+ LIKE: number;
+ DISLIKE: number;
+ HELPFUL: number;
+}
+
+export interface CommentEditHistory {
+ content: string;
+ editedAt: string;
+}
+
+export interface CommentReport {
+ userId: string;
+ reason: 'spam' | 'inappropriate' | 'harassment' | 'misinformation' | 'other';
+ description?: string;
+ createdAt: string;
+}
+
+export interface Discussion {
+ _id: string;
+ userId: CommentUser;
+ projectId: string;
+ content: string;
+ parentCommentId?: string;
+ status: 'active' | 'deleted' | 'flagged' | 'hidden';
+ editHistory: CommentEditHistory[];
+ reactionCounts: CommentReactionCounts;
+ totalReactions: number;
+ replyCount: number;
+ replies: Discussion[];
+ createdAt: string;
+ updatedAt: string;
+ isSpam: boolean;
+ reports: CommentReport[];
+}
+
+export interface SubmissionCardProps {
+ title: string;
+ description: string;
+ submitterName: string;
+ submitterAvatar?: string;
+ category?: string;
+ categories?: string[];
+ status?: 'Pending' | 'Approved' | 'Rejected';
+ upvotes?: number;
+ votes?: { current: number; total: number };
+ comments?: number;
+ submittedDate?: string;
+ daysLeft?: number;
+ score?: number;
+ image?: string;
+ onViewClick?: () => void;
+ onUpvoteClick?: () => void;
+ onCommentClick?: () => void;
+ hasUserUpvoted?: boolean;
+}
+
+export interface Hackathon {
+ id: string;
+ title: string;
+ subtitle: string;
+ description: string;
+ slug?: string;
+ imageUrl: string;
+ status: 'upcoming' | 'ongoing' | 'ended';
+ participants: number;
+ totalPrizePool: string;
+ deadline: string;
+ categories: string[];
+ startDate: string;
+ endDate: string;
+ organizer: string;
+ featured?: boolean;
+ resources?: string[];
+}