Skip to content

Commit 4e8d0bc

Browse files
authored
Feat/hackthon features (#346)
* feat: implemented hackathon features * fix: update * fix: added the staging api * fix: update API base URL to use environment variable * fix: correct API_BASE_URL assignment and comment out unused variables * feat: enhance infoSchema to support multiple categories and validation * feat: add participantType support and enhance deadline handling in HackathonCard * refactor: update hackathon category handling to support multiple categories across components and schemas
1 parent 99d436d commit 4e8d0bc

10 files changed

Lines changed: 53 additions & 60 deletions

File tree

app/(landing)/organizations/[id]/hackathons/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const calculateDraftCompletion = (draft: HackathonDraft): number => {
3232
draft.information?.title,
3333
draft.information?.banner,
3434
draft.information?.description,
35-
draft.information?.category,
35+
draft.information?.categories,
3636
draft.timeline?.startDate,
3737
draft.timeline?.submissionDeadline,
3838
draft.timeline?.judgingDate,
@@ -123,8 +123,9 @@ export default function HackathonsPage() {
123123

124124
if (categoryFilter !== 'all') {
125125
filtered = filtered.filter(item => {
126-
const category = item.data.information?.category?.toLowerCase() || '';
127-
return category === categoryFilter.toLowerCase();
126+
const category =
127+
item.data.information?.categories?.join(',')?.toLowerCase() || '';
128+
return category.includes(categoryFilter.toLowerCase());
128129
});
129130
}
130131

components/organization/hackathons/new/NewHackathonTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export default function NewHackathonTab({
113113
const { isPublishing, publish } = useHackathonPublish({
114114
organizationId: derivedOrgId || '',
115115
stepData,
116+
draftId,
116117
publishHackathonAction,
117118
});
118119

components/organization/hackathons/new/tabs/InfoTab.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ export default function InfoTab({
5252
name: initialData?.name || '',
5353
banner: initialData?.banner || '',
5454
description: initialData?.description || '',
55-
category: Array.isArray(initialData?.category)
56-
? initialData.category
57-
: initialData?.category
58-
? [initialData.category]
59-
: [],
55+
categories: Array.isArray(initialData?.categories)
56+
? initialData.categories
57+
: [],
6058
venueType: initialData?.venueType || 'physical',
6159
country: initialData?.country || '',
6260
state: initialData?.state || '',
@@ -145,7 +143,7 @@ export default function InfoTab({
145143
)}
146144
/>
147145

148-
<CategorySelection control={form.control} name='category' />
146+
<CategorySelection control={form.control} name='categories' />
149147

150148
<VenueSection
151149
control={form.control}

components/organization/hackathons/new/tabs/components/review/InformationSection.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ export default function InformationSection({
8383
<InfoItem
8484
label='Categories'
8585
value={
86-
Array.isArray(data.category)
87-
? data.category.join(', ')
88-
: data.category || ''
86+
Array.isArray(data.categories) ? data.categories.join(', ') : ''
8987
}
9088
/>
9189
<Separator className='bg-gray-900' />

components/organization/hackathons/new/tabs/schemas/infoSchema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const infoSchema = z
1919
.min(10, 'Description must be at least 10 characters')
2020
.max(5000, 'Description must be less than 5000 characters'),
2121

22-
category: z
22+
categories: z
2323
.array(
2424
z.enum([
2525
'DeFi',

components/organization/hackathons/settings/GeneralSettingsTab.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,9 @@ export default function GeneralSettingsTab({
7878
name: initialData?.name || '',
7979
banner: initialData?.banner || '',
8080
description: initialData?.description || '',
81-
category: Array.isArray(initialData?.category)
82-
? initialData.category
83-
: initialData?.category
84-
? [initialData.category]
85-
: [],
81+
categories: Array.isArray(initialData?.categories)
82+
? initialData.categories
83+
: [],
8684
venueType: initialData?.venueType || 'virtual',
8785
country: initialData?.country || '',
8886
state: initialData?.state || '',
@@ -205,7 +203,7 @@ export default function GeneralSettingsTab({
205203
)}
206204
/>
207205

208-
<CategorySelection control={form.control} name='category' />
206+
<CategorySelection control={form.control} name='categories' />
209207

210208
<VenueSection
211209
control={form.control}

hooks/hackathon/use-hackathon-transform.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,12 @@ export function useHackathonTransform() {
125125
extendedHackathon._organizationName ||
126126
'organization';
127127

128-
// Extract categories - support both single category and multiple categories
128+
// Extract categories
129129
const categories: string[] = [];
130-
if (hackathon.information?.category) {
131-
// If category is an array, use it; otherwise convert single category to array
132-
if (Array.isArray(hackathon.information.category)) {
133-
categories.push(...hackathon.information.category);
134-
} else {
135-
categories.push(hackathon.information.category);
130+
if (hackathon.information?.categories) {
131+
// If categories is an array, use it
132+
if (Array.isArray(hackathon.information.categories)) {
133+
categories.push(...hackathon.information.categories);
136134
}
137135
}
138136
// Add additional categories if they exist in the hackathon object

hooks/use-hackathon-publish.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface StepData {
4040
interface UseHackathonPublishProps {
4141
organizationId: string;
4242
stepData: StepData;
43+
draftId?: string | null;
4344
publishHackathonAction: (
4445
data: PublishHackathonRequest
4546
) => Promise<{ _id: string }>;
@@ -48,6 +49,7 @@ interface UseHackathonPublishProps {
4849
export const useHackathonPublish = ({
4950
organizationId,
5051
stepData,
52+
draftId,
5153
publishHackathonAction,
5254
}: UseHackathonPublishProps) => {
5355
const router = useRouter();
@@ -196,6 +198,9 @@ export const useHackathonPublish = ({
196198

197199
toast.info('Publishing hackathon...');
198200
const apiData = transformToApiFormat(stepData) as PublishHackathonRequest;
201+
if (draftId) {
202+
apiData.draftId = draftId;
203+
}
199204
apiData.contractId = contractId;
200205
apiData.escrowAddress = escrowAddress;
201206
apiData.transactionHash = transactionHash;

lib/api/hackathons.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ export interface HackathonInformation {
4242
title: string;
4343
banner: string;
4444
description: string;
45-
category?: HackathonCategory; // Legacy format (single category)
46-
categories?: HackathonCategory[]; // New format (array of categories)
47-
venue?: HackathonVenue;
45+
categories: HackathonCategory[]; // New format (array of categories)
4846
slug: string;
47+
venue?: HackathonVenue;
4948
}
5049

5150
// Timeline Tab Types
@@ -177,6 +176,7 @@ export type CreateDraftRequest = Partial<HackathonData>;
177176
export type UpdateDraftRequest = Partial<HackathonData>;
178177

179178
export interface PublishHackathonRequest extends HackathonData {
179+
draftId?: string;
180180
contractId?: string;
181181
escrowAddress?: string;
182182
transactionHash?: string;
@@ -709,7 +709,6 @@ interface FlatHackathonData {
709709
// Flat fields that map to nested structure
710710
banner?: string;
711711
description?: string;
712-
category?: string | HackathonCategory; // Legacy format
713712
categories?: HackathonCategory[]; // New format
714713
venue?: HackathonVenue;
715714
startDate?: string;
@@ -786,10 +785,7 @@ const transformHackathonResponse = (
786785
// Support both new format (categories) and legacy format (category)
787786
categories: Array.isArray(flat.categories)
788787
? (flat.categories as HackathonCategory[])
789-
: flat.category
790-
? [flat.category as HackathonCategory]
791-
: [HackathonCategory.OTHER],
792-
category: (flat.category as HackathonCategory) || HackathonCategory.OTHER,
788+
: [HackathonCategory.OTHER],
793789
venue: flat.venue,
794790
},
795791
timeline: {
@@ -1382,13 +1378,22 @@ export const transformPublicHackathonToHackathon = (
13821378
internalStatus = 'completed';
13831379
}
13841380

1385-
// Get first category or default to OTHER
1386-
const category = publicHackathon.categories?.[0] || HackathonCategory.OTHER;
1387-
const categoryEnum = Object.values(HackathonCategory).includes(
1388-
category as HackathonCategory
1389-
)
1390-
? (category as HackathonCategory)
1391-
: HackathonCategory.OTHER;
1381+
// Map all categories from API response
1382+
const categoriesArray: HackathonCategory[] = publicHackathon.categories
1383+
? publicHackathon.categories
1384+
.map(cat => {
1385+
// Check if the category string matches any HackathonCategory enum value
1386+
const matchedCategory = Object.values(HackathonCategory).find(
1387+
enumCat => enumCat === cat
1388+
);
1389+
return matchedCategory || null;
1390+
})
1391+
.filter((cat): cat is HackathonCategory => cat !== null)
1392+
: [];
1393+
1394+
// Ensure at least one category
1395+
const categories: HackathonCategory[] =
1396+
categoriesArray.length > 0 ? categoriesArray : [HackathonCategory.OTHER];
13921397

13931398
// Extract resources (telegram, discord, etc.) from resources array
13941399
const telegram = publicHackathon.resources?.find(
@@ -1412,7 +1417,7 @@ export const transformPublicHackathonToHackathon = (
14121417
title: publicHackathon.title,
14131418
banner: publicHackathon.imageUrl,
14141419
description: publicHackathon.description,
1415-
category: categoryEnum,
1420+
categories: categories,
14161421
slug: publicHackathon.slug,
14171422
venue,
14181423
},

lib/utils/hackathon-form-transforms.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,12 @@ export const transformToApiFormat = (stepData: {
2828
const judging = stepData.judging;
2929
const collaboration = stepData.collaboration;
3030

31-
// Convert form category array to API format
32-
const categoriesArray: HackathonCategory[] = Array.isArray(info?.category)
33-
? (info.category as HackathonCategory[]).filter(cat =>
31+
// Convert form categories array to API format
32+
const categoriesArray: HackathonCategory[] = Array.isArray(info?.categories)
33+
? (info.categories as HackathonCategory[]).filter(cat =>
3434
Object.values(HackathonCategory).includes(cat)
3535
)
36-
: info?.category && typeof info.category === 'string'
37-
? [info.category as HackathonCategory]
38-
: [];
36+
: [];
3937

4038
return {
4139
information: {
@@ -48,11 +46,6 @@ export const transformToApiFormat = (stepData: {
4846
categoriesArray.length > 0
4947
? categoriesArray
5048
: [HackathonCategory.OTHER],
51-
// Also include legacy category for backward compatibility (first category)
52-
category:
53-
categoriesArray.length > 0
54-
? categoriesArray[0]
55-
: HackathonCategory.OTHER,
5649
venue: {
5750
type: (info?.venueType as VenueType) || VenueType.VIRTUAL,
5851
country: info?.country,
@@ -144,19 +137,15 @@ export const transformFromApiFormat = (draft: HackathonDraft) => {
144137
const judging = draft.judging;
145138
const collaboration = draft.collaboration;
146139

147-
// Handle both new format (categories array) and legacy format (category string)
148-
const categoriesArray: string[] = info?.categories
149-
? info.categories
150-
: info?.category
151-
? [info.category]
152-
: [];
140+
// Handle categories array
141+
const categoriesArray: string[] = info?.categories ? info.categories : [];
153142

154143
return {
155144
information: {
156145
name: info?.title || '',
157146
banner: info?.banner || '',
158147
description: info?.description || '',
159-
category: categoriesArray,
148+
categories: categoriesArray,
160149
venueType: info?.venue?.type || 'physical',
161150
country: info?.venue?.country || '',
162151
state: info?.venue?.state || '',

0 commit comments

Comments
 (0)