From 6c0f9d26d6b850583b7dc00d5850492030fc4b98 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:51:39 +0100 Subject: [PATCH 01/15] UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form --- .../hackathons/submissions/SubmissionForm.tsx | 20 ++++++++++--------- .../hackathons/submissions/submissionTab.tsx | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index abb2e0e4..3c48a534 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -1081,15 +1081,17 @@ const SubmissionFormContent: React.FC = ({
- + {process.env.NODE_ENV === 'development' && ( + + )}
= ({ {!isLoadingMySubmission && !mySubmission && isAuthenticated && - isRegistered && ( + isRegistered && + status !== 'upcoming' && (

You haven't submitted a project yet. From ba798d79bb3b9906840848feb14df052da6d732b Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Sat, 7 Mar 2026 16:32:42 +0100 Subject: [PATCH 02/15] UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page --- .../hackathons/[slug]/HackathonPageClient.tsx | 38 ++- .../hackathons/[slug]/submit/page.tsx | 120 +++++++++ components/hackathons/hackathonBanner.tsx | 6 +- components/hackathons/hackathonStickyCard.tsx | 25 +- .../hackathons/submissions/SubmissionForm.tsx | 43 +++- .../hackathons/submissions/submissionCard.tsx | 62 ++--- .../hackathons/submissions/submissionTab.tsx | 53 ++-- .../settings/GeneralSettingsTab.tsx | 43 +++- components/stepper/Stepper.tsx | 40 ++- hooks/hackathon/use-participants.ts | 230 ++++++++++++------ lib/providers/hackathonProvider.tsx | 2 - 11 files changed, 478 insertions(+), 184 deletions(-) create mode 100644 app/(landing)/hackathons/[slug]/submit/page.tsx diff --git a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx index 0c4d1adf..3f38dce0 100644 --- a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx +++ b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx @@ -5,6 +5,8 @@ import { useRouter, useSearchParams, useParams } from 'next/navigation'; import { useHackathonData } from '@/lib/providers/hackathonProvider'; import { useRegisterHackathon } from '@/hooks/hackathon/use-register-hackathon'; import { useLeaveHackathon } from '@/hooks/hackathon/use-leave-hackathon'; +import { useSubmission } from '@/hooks/hackathon/use-submission'; +import { useAuthStatus } from '@/hooks/use-auth'; import { RegisterHackathonModal } from '@/components/hackathons/overview/RegisterHackathonModal'; import { HackathonBanner } from '@/components/hackathons/hackathonBanner'; import { HackathonNavTabs } from '@/components/hackathons/hackathonNavTabs'; @@ -45,6 +47,13 @@ export default function HackathonPageClient() { refreshCurrentHackathon, } = useHackathonData(); + const { isAuthenticated } = useAuthStatus(); + + const { submission: mySubmission } = useSubmission({ + hackathonSlugOrId: currentHackathon?.id || '', + autoFetch: !!currentHackathon && isAuthenticated, + }); + const timeline_Events = useTimelineEvents(currentHackathon, { includeEndDate: false, dateFormat: { month: 'short', day: 'numeric', year: 'numeric' }, @@ -232,7 +241,7 @@ export default function HackathonPageClient() { // Registration status const { isRegistered, - hasSubmitted, + hasSubmitted: participantHasSubmitted, setParticipant, register: registerForHackathon, } = useRegisterHackathon({ @@ -246,6 +255,8 @@ export default function HackathonPageClient() { organizationId: undefined, }); + const hasSubmitted = !!mySubmission || participantHasSubmitted; + // Leave hackathon functionality const { isLeaving, leave: leaveHackathon } = useLeaveHackathon({ hackathonSlugOrId: currentHackathon?.id || '', @@ -296,7 +307,7 @@ export default function HackathonPageClient() { }; const handleSubmitClick = () => { - router.push('?tab=submission'); + router.push(`/hackathons/${currentHackathon?.slug}/submit`); }; const handleViewSubmissionClick = () => { @@ -308,10 +319,25 @@ export default function HackathonPageClient() { }; // Set current hackathon on mount + const [isInitializing, setIsInitializing] = useState(true); + useEffect(() => { - if (hackathonId) { - setCurrentHackathon(hackathonId); - } + let isMounted = true; + + const initHackathon = async () => { + if (hackathonId) { + await setCurrentHackathon(hackathonId); + } + if (isMounted) { + setIsInitializing(false); + } + }; + + initHackathon(); + + return () => { + isMounted = false; + }; }, [hackathonId, setCurrentHackathon]); // Handle tab changes from URL @@ -349,7 +375,7 @@ export default function HackathonPageClient() { }; // Loading state - if (loading) { + if (loading || isInitializing) { return ; } diff --git a/app/(landing)/hackathons/[slug]/submit/page.tsx b/app/(landing)/hackathons/[slug]/submit/page.tsx new file mode 100644 index 00000000..f5ca054e --- /dev/null +++ b/app/(landing)/hackathons/[slug]/submit/page.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { use, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { useHackathonData } from '@/lib/providers/hackathonProvider'; +import { useAuthStatus } from '@/hooks/use-auth'; +import { useSubmission } from '@/hooks/hackathon/use-submission'; +import { SubmissionFormContent } from '@/components/hackathons/submissions/SubmissionForm'; +import LoadingScreen from '@/features/projects/components/CreateProjectModal/LoadingScreen'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft } from 'lucide-react'; +import { toast } from 'sonner'; + +export default function SubmitProjectPage({ + params, +}: { + params: Promise<{ slug: string }>; +}) { + const router = useRouter(); + const { isAuthenticated, isLoading } = useAuthStatus(); + + const resolvedParams = use(params); + const hackathonSlug = resolvedParams.slug; + + const { + currentHackathon, + loading: hackathonLoading, + setCurrentHackathon, + } = useHackathonData(); + + useEffect(() => { + if (hackathonSlug) { + setCurrentHackathon(hackathonSlug); + } + }, [hackathonSlug, setCurrentHackathon]); + + const hackathonId = currentHackathon?.id || ''; + const orgId = currentHackathon?.organizationId || undefined; + + const { + submission: mySubmission, + isFetching: isLoadingMySubmission, + fetchMySubmission, + } = useSubmission({ + hackathonSlugOrId: hackathonId || '', + autoFetch: isAuthenticated && !!hackathonId, + }); + + // Authentication check + useEffect(() => { + if (!isLoading && !isAuthenticated) { + toast.error('You must be logged in to submit a project'); + router.push( + `/auth?mode=signin&callbackUrl=/hackathons/${hackathonSlug}/submit` + ); + } + }, [isAuthenticated, isLoading, router, hackathonSlug]); + + const handleClose = () => { + router.push(`/hackathons/${hackathonSlug}`); + }; + + const handleSuccess = () => { + fetchMySubmission(); + toast.success( + mySubmission + ? 'Submission updated successfully!' + : 'Project submitted successfully!' + ); + router.push(`/hackathons/${hackathonSlug}?tab=submission`); + }; + + if ( + isLoading || + hackathonLoading || + isLoadingMySubmission || + !currentHackathon + ) { + return ; + } + + return ( +

+
+ + +
+ +
+
+
+ ); +} diff --git a/components/hackathons/hackathonBanner.tsx b/components/hackathons/hackathonBanner.tsx index dcb8ccd5..f08d27b0 100644 --- a/components/hackathons/hackathonBanner.tsx +++ b/components/hackathons/hackathonBanner.tsx @@ -315,14 +315,14 @@ export function HackathonBanner({ {status === 'ongoing' && isRegistered && hasSubmitted && - onViewSubmissionClick && ( + onSubmitClick && ( )} diff --git a/components/hackathons/hackathonStickyCard.tsx b/components/hackathons/hackathonStickyCard.tsx index 47056240..9a4a9c2a 100644 --- a/components/hackathons/hackathonStickyCard.tsx +++ b/components/hackathons/hackathonStickyCard.tsx @@ -238,20 +238,17 @@ export function HackathonStickyCard(props: HackathonStickyCardProps) { )} - {/* View Submission Button */} - {status === 'ongoing' && - isRegistered && - hasSubmitted && - onViewSubmissionClick && ( - - )} + {/* Edit / View Submission Button */} + {status === 'ongoing' && isRegistered && hasSubmitted && ( + + )} {/* Find Team Button */} {status === 'ongoing' && diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index 3c48a534..1604189b 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -125,6 +125,7 @@ interface SubmissionFormContentProps { initialData?: Partial; submissionId?: string; onSuccess?: () => void; + onClose?: () => void; } const INITIAL_STEPS: Step[] = [ @@ -198,8 +199,19 @@ const SubmissionFormContent: React.FC = ({ initialData, submissionId, onSuccess, + onClose, }) => { - const { collapse, isExpanded: open } = useExpandableScreen(); + // Use context carefully since it might not be available when used standalone + let collapse = () => {}; + let open = true; + try { + const expandableCtx = useExpandableScreen(); + collapse = expandableCtx.collapse; + open = expandableCtx.isExpanded; + } catch (e) { + // Standalone mode, not in ExpandableScreen + } + const { currentHackathon } = useHackathonData(); const { user } = useAuthStatus(); @@ -773,7 +785,11 @@ const SubmissionFormContent: React.FC = ({ } else { await create(submissionData); } - collapse(); + if (onClose) { + onClose(); + } else { + collapse(); + } onSuccess?.(); } catch { // Error handled in hook @@ -1503,22 +1519,31 @@ const SubmissionFormContent: React.FC = ({
-
+
-
+
{renderStepContent()}
{currentStep < steps.length - 1 ? ( - - - - - Edit Submission - - e.stopPropagation()}> + + + + + - - Delete Submission - - - + onEditClick?.()} + className='cursor-pointer text-gray-300 focus:bg-gray-800 focus:text-white' + > + + Edit Submission + + onDeleteClick?.()} + className='cursor-pointer text-red-500 focus:bg-red-900/20 focus:text-red-400' + > + + Delete Submission + + + +
)}
diff --git a/components/hackathons/submissions/submissionTab.tsx b/components/hackathons/submissions/submissionTab.tsx index 61f17bec..fff6a497 100644 --- a/components/hackathons/submissions/submissionTab.tsx +++ b/components/hackathons/submissions/submissionTab.tsx @@ -54,6 +54,7 @@ interface SubmissionTabContentProps extends SubmissionTabProps { fetchMySubmission: () => Promise; removeSubmission: (id: string) => Promise; hackathonId: string; + hackathonSlug: string; } const SubmissionTabContent: React.FC = ({ @@ -64,10 +65,10 @@ const SubmissionTabContent: React.FC = ({ fetchMySubmission, removeSubmission, hackathonId, + hackathonSlug, }) => { const { isAuthenticated } = useAuthStatus(); const router = useRouter(); - const { expand } = useExpandableScreen(); const [viewMode, setViewMode] = useState('grid'); @@ -82,7 +83,8 @@ const SubmissionTabContent: React.FC = ({ setSelectedSort, setSelectedCategory, } = useSubmissions(); - const { currentHackathon } = useHackathonData(); + const { currentHackathon, loading: isHackathonDataLoading } = + useHackathonData(); const { status } = useHackathonStatus( currentHackathon?.startDate, currentHackathon?.submissionDeadline @@ -129,6 +131,7 @@ const SubmissionTabContent: React.FC = ({ await removeSubmission(submissionToDelete); setSubmissionToDelete(null); toast.success('Submission deleted successfully'); + window.location.reload(); } catch (error) { reportError(error, { context: 'submission-delete', @@ -263,6 +266,14 @@ const SubmissionTabContent: React.FC = ({
+ {/* Loading State */} + {(isLoadingMySubmission || isHackathonDataLoading) && ( +
+ + Loading submissions... +
+ )} + {/* Submissions Grid with Create Button if no submission */} {!isLoadingMySubmission && !mySubmission && @@ -274,7 +285,7 @@ const SubmissionTabContent: React.FC = ({ You haven't submitted a project yet.

+
+ )} + {!isAuthenticated && ( -
+
setIsOpen(false)} - className='inline-flex h-9 w-full items-center justify-center gap-2 rounded-[10px] bg-[#a7f950] px-4 py-2 text-sm font-medium whitespace-nowrap text-black shadow-sm shadow-[#a7f950]/20 transition-all hover:bg-[#a7f950]/90' + className='inline-flex min-h-[44px] w-full items-center justify-center rounded-[10px] bg-[#a7f950] px-4 py-3 text-sm font-medium text-black shadow-sm shadow-[#a7f950]/20 transition-colors hover:bg-[#a7f950]/90' > Get Started setIsOpen(false)} - className='inline-flex h-9 w-full items-center justify-center gap-2 rounded-[10px] border border-white/30 px-4 py-2 text-sm font-medium whitespace-nowrap text-white transition-all hover:border-white/40 hover:bg-white/10' + className='inline-flex min-h-[44px] w-full items-center justify-center rounded-[10px] border border-white/30 px-4 py-3 text-sm font-medium text-white transition-colors hover:border-white/40 hover:bg-white/10' > Sign In From 84b1d35bb5baff35f1c586b66c31079ca493b23b Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sat, 7 Mar 2026 19:46:14 +0100 Subject: [PATCH 04/15] feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --- .../[hackathonId]/announcement/page.tsx | 19 +- .../announcements/AnnouncementsTab.tsx | 24 +- .../shadcn-io/announcement-editor/index.tsx | 466 ++---------------- 3 files changed, 79 insertions(+), 430 deletions(-) diff --git a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx index 60116acb..20a2d817 100644 --- a/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx +++ b/app/(landing)/organizations/[id]/hackathons/[hackathonId]/announcement/page.tsx @@ -29,6 +29,23 @@ import { import { Switch } from '@/components/ui/switch'; import { reportError } from '@/lib/error-reporting'; +/** Strip Markdown to plain text for list preview (headings, bold, links, etc.). */ +function stripMarkdown(md: string): string { + if (!md || typeof md !== 'string') return ''; + return md + .replace(/!\[[^\]]*\]\([^)]*\)/g, '') + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/#{1,6}\s*/g, '') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/\*([^*]+)\*/g, '$1') + .replace(/__([^_]+)__/g, '$1') + .replace(/_([^_]+)_/g, '$1') + .replace(/`([^`]+)`/g, '$1') + .replace(/<[^>]*>/g, '') + .replace(/\n+/g, ' ') + .trim(); +} + export default function AnnouncementPage() { const params = useParams(); const organizationId = params.id as string; @@ -298,7 +315,7 @@ export default function AnnouncementPage() { )}

- {item.content.replace(/<[^>]*>/g, '')} + {stripMarkdown(item.content)}

diff --git a/components/hackathons/announcements/AnnouncementsTab.tsx b/components/hackathons/announcements/AnnouncementsTab.tsx index 621b2304..e10c9b69 100644 --- a/components/hackathons/announcements/AnnouncementsTab.tsx +++ b/components/hackathons/announcements/AnnouncementsTab.tsx @@ -5,6 +5,26 @@ import { Megaphone, Pin, ArrowUpDown, ExternalLink } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { HackathonAnnouncement } from '@/lib/api/hackathons/index'; import Link from 'next/link'; +import { useMarkdown } from '@/hooks/use-markdown'; + +/** Renders announcement body as Markdown (supports both Markdown and legacy HTML). */ +function AnnouncementPreview({ content }: { content: string }) { + const raw = content?.trim() || ''; + const isLikelyHtml = raw.startsWith('<'); + const markdown = isLikelyHtml ? raw.replace(/<[^>]*>/g, ' ') : raw; + const { styledContent, loading } = useMarkdown(markdown, { + loadingDelay: 0, + }); + + if (!raw) return No content; + if (loading) return ; + + return ( +
+ {styledContent} +
+ ); +} interface AnnouncementsTabProps { announcements: HackathonAnnouncement[]; @@ -90,9 +110,7 @@ export function AnnouncementsTab({
-

- {announcement.content.replace(/<[^>]*>/g, '')} -

+
diff --git a/components/ui/shadcn-io/announcement-editor/index.tsx b/components/ui/shadcn-io/announcement-editor/index.tsx index d29c90eb..60d8efb9 100644 --- a/components/ui/shadcn-io/announcement-editor/index.tsx +++ b/components/ui/shadcn-io/announcement-editor/index.tsx @@ -1,40 +1,21 @@ 'use client'; import * as React from 'react'; -import { EditorContent, useEditor } from '@tiptap/react'; -import StarterKit from '@tiptap/starter-kit'; -import { Button } from '@/components/ui/button'; -import { Separator } from '@/components/ui/separator'; -import { Toggle } from '@/components/ui/toggle'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - Bold, - Italic, - Strikethrough, - Code, - Quote, - Link as LinkIcon, - Image as ImageIcon, - Undo, - Redo, - Code2, -} from 'lucide-react'; +import dynamic from 'next/dynamic'; +import { Loader2 } from 'lucide-react'; import { cn } from '@/lib/utils'; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; + +const MDEditor = dynamic( + () => import('@uiw/react-md-editor').then(mod => mod.default), + { + ssr: false, + loading: () => ( +
+ +
+ ), + } +); interface AnnouncementEditorProps { content?: string; @@ -51,129 +32,6 @@ function AnnouncementEditor({ editable = true, className, }: AnnouncementEditorProps) { - const [linkUrl, setLinkUrl] = React.useState(''); - const [linkText, setLinkText] = React.useState(''); - const [imageUrl, setImageUrl] = React.useState(''); - const [embedUrl, setEmbedUrl] = React.useState(''); - const [isLinkDialogOpen, setIsLinkDialogOpen] = React.useState(false); - const [isImageDialogOpen, setIsImageDialogOpen] = React.useState(false); - const [isEmbedDialogOpen, setIsEmbedDialogOpen] = React.useState(false); - - const editor = useEditor({ - extensions: [ - StarterKit.configure({ - bulletList: { - keepMarks: true, - keepAttributes: false, - }, - orderedList: { - keepMarks: true, - keepAttributes: false, - }, - }), - ], - content, - editable, - immediatelyRender: false, - onUpdate: ({ editor }) => { - onChange?.(editor.getHTML()); - }, - editorProps: { - attributes: { - class: cn( - 'prose prose-sm sm:prose-base lg:prose-lg xl:prose-2xl mx-auto focus:outline-none', - 'min-h-[400px] border-0 p-6 text-white' - ), - }, - }, - }); - - React.useEffect(() => { - if (editor && content !== editor.getHTML()) { - editor.commands.setContent(content); - } - }, [content, editor]); - - React.useEffect(() => { - if (editor) { - editor.setOptions({ - editorProps: { - ...editor.options.editorProps, - handleDOMEvents: { - ...editor.options.editorProps?.handleDOMEvents, - drop: (view, event) => { - const files = event.dataTransfer?.files; - if (files && files.length > 0) { - const file = files[0]; - if (file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = e => { - const src = e.target?.result as string; - editor - .chain() - .focus() - .insertContent( - `Image` - ) - .run(); - }; - reader.readAsDataURL(file); - return true; - } - } - return false; - }, - }, - }, - }); - } - }, [editor]); - - if (!editor) { - return null; - } - - const handleInsertLink = () => { - if (linkUrl && linkText) { - editor - .chain() - .focus() - .insertContent(`${linkText}`) - .run(); - setLinkUrl(''); - setLinkText(''); - setIsLinkDialogOpen(false); - } - }; - - const handleInsertImage = () => { - if (imageUrl) { - editor - .chain() - .focus() - .insertContent( - `Image` - ) - .run(); - setImageUrl(''); - setIsImageDialogOpen(false); - } - }; - - const handleInsertEmbed = () => { - if (embedUrl) { - editor - .chain() - .focus() - .insertContent( - `` - ) - .run(); - setEmbedUrl(''); - setIsEmbedDialogOpen(false); - } - }; - return (
-
- - - - - - - - - - editor.chain().focus().toggleBold().run()} - disabled={!editor.can().chain().focus().toggleBold().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleItalic().run()} - disabled={!editor.can().chain().focus().toggleItalic().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleStrike().run()} - disabled={!editor.can().chain().focus().toggleStrike().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - editor.chain().focus().toggleCode().run()} - disabled={!editor.can().chain().focus().toggleCode().run()} - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - - editor.chain().focus().toggleBlockquote().run() - } - className='h-8 w-8 p-0 data-[state=on]:bg-gray-800 data-[state=on]:text-white' - > - - - - - - - - - - Insert Link - -
-
- - setLinkText(e.target.value)} - placeholder='Link text' - className='bg-background border-gray-800 text-white' - /> -
-
- - setLinkUrl(e.target.value)} - placeholder='https://example.com' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
- - - - - - - - Insert Image - -
-
- - setImageUrl(e.target.value)} - placeholder='https://example.com/image.jpg' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
- - - - - - - - Insert Embed - -
-
- - setEmbedUrl(e.target.value)} - placeholder='https://example.com/embed' - className='bg-background border-gray-800 text-white' - /> -
-
- - -
-
-
-
-
- -
- - {(!editor.getHTML() || editor.getHTML() === '

') && ( -
- {placeholder} -
- )} -
+ onChange?.(value ?? '')} + height={400} + data-color-mode='dark' + preview='edit' + hideToolbar={!editable} + visibleDragbar={editable} + textareaProps={{ + placeholder, + readOnly: !editable, + style: { + fontSize: 14, + lineHeight: 1.6, + color: '#ffffff', + backgroundColor: '#18181b', + fontFamily: 'inherit', + border: 'none', + }, + }} + style={{ + backgroundColor: '#18181b', + color: '#ffffff', + border: 'none', + }} + />
); } From 4ae17617584f912a257217b33d106cf768a65de4 Mon Sep 17 00:00:00 2001 From: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:04:07 +0530 Subject: [PATCH 05/15] =?UTF-8?q?feat(newsletter):=20implement=20newslette?= =?UTF-8?q?r=20API=20integration=20with=20subscribe=E2=80=A6=20(#452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages --- .../newsletter/confirm/error/page.tsx | 32 ++++++ app/(landing)/newsletter/confirmed/page.tsx | 14 +++ .../newsletter/unsubscribe/error/page.tsx | 31 ++++++ .../newsletter/unsubscribed/page.tsx | 14 +++ app/api/newsletter/confirm/[token]/route.ts | 21 ++++ app/api/newsletter/preferences/route.ts | 28 ++++++ app/api/newsletter/subscribe/route.ts | 25 +---- .../newsletter/unsubscribe/[token]/route.ts | 20 ++++ app/api/newsletter/unsubscribe/route.ts | 15 +++ components/overview/Newsletter.tsx | 54 +++++++++- lib/api/waitlist.ts | 98 ++++++++++++++++--- 11 files changed, 315 insertions(+), 37 deletions(-) create mode 100644 app/(landing)/newsletter/confirm/error/page.tsx create mode 100644 app/(landing)/newsletter/confirmed/page.tsx create mode 100644 app/(landing)/newsletter/unsubscribe/error/page.tsx create mode 100644 app/(landing)/newsletter/unsubscribed/page.tsx create mode 100644 app/api/newsletter/confirm/[token]/route.ts create mode 100644 app/api/newsletter/preferences/route.ts create mode 100644 app/api/newsletter/unsubscribe/[token]/route.ts create mode 100644 app/api/newsletter/unsubscribe/route.ts diff --git a/app/(landing)/newsletter/confirm/error/page.tsx b/app/(landing)/newsletter/confirm/error/page.tsx new file mode 100644 index 00000000..84de7a0e --- /dev/null +++ b/app/(landing)/newsletter/confirm/error/page.tsx @@ -0,0 +1,32 @@ +'use client'; +import { useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import { Suspense } from 'react'; + +const msgs: Record = { + expired: 'This confirmation link has expired.', + invalid: 'This confirmation link is invalid or already used.', +}; + +function Content() { + const p = useSearchParams(); + return ( +
+

Confirmation failed

+

+ {msgs[p.get('reason') ?? ''] ?? 'An unexpected error occurred.'} +

+ + Back to home + +
+ ); +} + +export default function Page() { + return ( + + + + ); +} diff --git a/app/(landing)/newsletter/confirmed/page.tsx b/app/(landing)/newsletter/confirmed/page.tsx new file mode 100644 index 00000000..67718529 --- /dev/null +++ b/app/(landing)/newsletter/confirmed/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link'; +export default function NewsletterConfirmedPage() { + return ( +
+

You're subscribed! 🎉

+

+ Your subscription has been confirmed. Welcome aboard! +

+ + Back to home + +
+ ); +} diff --git a/app/(landing)/newsletter/unsubscribe/error/page.tsx b/app/(landing)/newsletter/unsubscribe/error/page.tsx new file mode 100644 index 00000000..b957980a --- /dev/null +++ b/app/(landing)/newsletter/unsubscribe/error/page.tsx @@ -0,0 +1,31 @@ +'use client'; +import { useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import { Suspense } from 'react'; + +const msgs: Record = { + invalid: 'This unsubscribe link is invalid or has already been used.', +}; + +function Content() { + const p = useSearchParams(); + return ( +
+

Unsubscribe failed

+

+ {msgs[p.get('reason') ?? ''] ?? 'An unexpected error occurred.'} +

+ + Back to home + +
+ ); +} + +export default function Page() { + return ( + + + + ); +} diff --git a/app/(landing)/newsletter/unsubscribed/page.tsx b/app/(landing)/newsletter/unsubscribed/page.tsx new file mode 100644 index 00000000..0d758a3c --- /dev/null +++ b/app/(landing)/newsletter/unsubscribed/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link'; +export default function NewsletterUnsubscribedPage() { + return ( +
+

You've been unsubscribed

+

+ You won't receive any more emails from us. +

+ + Back to home + +
+ ); +} diff --git a/app/api/newsletter/confirm/[token]/route.ts b/app/api/newsletter/confirm/[token]/route.ts new file mode 100644 index 00000000..774ba058 --- /dev/null +++ b/app/api/newsletter/confirm/[token]/route.ts @@ -0,0 +1,21 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const backendUrl = process.env.NEXT_PUBLIC_API_URL; +const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? ''; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ token: string }> } +) { + const { token } = await params; + const res = await fetch(`${backendUrl}/api/newsletter/confirm/${token}`, { + redirect: 'manual', + }); + if (res.status === 302) { + return NextResponse.redirect(`${appUrl}/newsletter/confirmed`); + } + const reason = res.status === 400 ? 'expired' : 'invalid'; + return NextResponse.redirect( + `${appUrl}/newsletter/confirm/error?reason=${reason}` + ); +} diff --git a/app/api/newsletter/preferences/route.ts b/app/api/newsletter/preferences/route.ts new file mode 100644 index 00000000..36acc11b --- /dev/null +++ b/app/api/newsletter/preferences/route.ts @@ -0,0 +1,28 @@ +import { NextRequest, NextResponse } from 'next/server'; + +function normalizeBackendUrl(raw: string | undefined): string | undefined { + if (!raw) return undefined; + return raw.replace(/\/$/, '').replace(/\/api$/i, ''); +} + +const backendUrl = normalizeBackendUrl(process.env.NEXT_PUBLIC_API_URL); + +export async function PATCH(req: NextRequest) { + const body = await req.json(); + + if (!backendUrl) { + return NextResponse.json( + { message: 'Server configuration error: NEXT_PUBLIC_API_URL is not set' }, + { status: 500 } + ); + } + + const res = await fetch(`${backendUrl}/api/newsletter/preferences`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + return NextResponse.json(await res.json().catch(() => ({})), { + status: res.status, + }); +} diff --git a/app/api/newsletter/subscribe/route.ts b/app/api/newsletter/subscribe/route.ts index 9b7b0fca..31f0f067 100644 --- a/app/api/newsletter/subscribe/route.ts +++ b/app/api/newsletter/subscribe/route.ts @@ -4,17 +4,14 @@ export async function POST(request: NextRequest) { try { const body = await request.json(); - // Normalize API URL: remove trailing slash and /api if present - // The env var should be base URL without /api (e.g., https://api.boundlessfi.xyz) let backendUrl = process.env.NEXT_PUBLIC_API_URL || 'https://staging-api.boundlessfi.xyz'; backendUrl = backendUrl.replace(/\/$/, '').replace(/\/api$/i, ''); - const response = await fetch(`${backendUrl}/api/waitlist/subscribe`, { + const response = await fetch(`${backendUrl}/api/newsletter/subscribe`, { method: 'POST', headers: { 'Content-Type': 'application/json', - ...(request.headers.get('user-agent') && { 'User-Agent': request.headers.get('user-agent')!, }), @@ -22,25 +19,11 @@ export async function POST(request: NextRequest) { body: JSON.stringify(body), }); - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - return NextResponse.json( - { - message: errorData.message || 'Failed to subscribe to waitlist', - status: response.status, - }, - { status: response.status } - ); - } - - const data = await response.json(); - return NextResponse.json(data, { status: 200 }); + const data = await response.json().catch(() => ({})); + return NextResponse.json(data, { status: response.status }); } catch { return NextResponse.json( - { - message: 'Internal server error', - status: 500, - }, + { message: 'Internal server error', status: 500 }, { status: 500 } ); } diff --git a/app/api/newsletter/unsubscribe/[token]/route.ts b/app/api/newsletter/unsubscribe/[token]/route.ts new file mode 100644 index 00000000..dfd12ccd --- /dev/null +++ b/app/api/newsletter/unsubscribe/[token]/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const backendUrl = process.env.NEXT_PUBLIC_API_URL; +const appUrl = process.env.NEXT_PUBLIC_APP_URL ?? ''; + +export async function GET( + _req: NextRequest, + { params }: { params: Promise<{ token: string }> } +) { + const { token } = await params; + const res = await fetch(`${backendUrl}/api/newsletter/unsubscribe/${token}`, { + redirect: 'manual', + }); + if (res.status === 302) { + return NextResponse.redirect(`${appUrl}/newsletter/unsubscribed`); + } + return NextResponse.redirect( + `${appUrl}/newsletter/unsubscribe/error?reason=invalid` + ); +} diff --git a/app/api/newsletter/unsubscribe/route.ts b/app/api/newsletter/unsubscribe/route.ts new file mode 100644 index 00000000..64e31d8d --- /dev/null +++ b/app/api/newsletter/unsubscribe/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const backendUrl = process.env.NEXT_PUBLIC_API_URL; + +export async function POST(req: NextRequest) { + const body = await req.json(); + const res = await fetch(`${backendUrl}/api/newsletter/unsubscribe`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + return NextResponse.json(await res.json().catch(() => ({})), { + status: res.status, + }); +} diff --git a/components/overview/Newsletter.tsx b/components/overview/Newsletter.tsx index eac5a25d..f2360d1b 100644 --- a/components/overview/Newsletter.tsx +++ b/components/overview/Newsletter.tsx @@ -25,7 +25,11 @@ import { BoundlessButton } from '../buttons'; import { Input } from '../ui/input'; import gsap from 'gsap'; import { useGSAP } from '@gsap/react'; -import { newsletterSubscribe } from '@/lib/api/waitlist'; +import { + newsletterSubscribe, + type NewsletterApiError, + type NewsletterTag, +} from '@/lib/api/waitlist'; import { Button } from '../ui/button'; const formSchema = z.object({ @@ -42,6 +46,7 @@ const Newsletter = ({ }) => { const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); + const [selectedTags, setSelectedTags] = useState([]); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -82,15 +87,23 @@ const Newsletter = ({ const onSubmit = async (values: z.infer) => { setError(null); setIsSubmitting(true); - try { await newsletterSubscribe({ email: values.email, name: values.name, + source: 'website', + tags: selectedTags, }); - } catch { - setError('Failed to submit form. Please try again.'); - setIsSubmitting(false); + onOpenChange(false); + window.location.href = '/newsletter/confirmed'; + } catch (err) { + const e = err as NewsletterApiError; + if (e.code === 'ALREADY_SUBSCRIBED') + setError('This email is already subscribed.'); + else if (e.code === 'RATE_LIMITED') + setError('Too many attempts. Please try again in a minute.'); + else if (e.code === 'INVALID_TAGS') setError('Invalid topic selection.'); + else setError('Failed to submit form. Please try again.'); } finally { setIsSubmitting(false); } @@ -186,6 +199,37 @@ const Newsletter = ({ )} /> + +
+ {( + [ + 'bounties', + 'hackathons', + 'grants', + 'updates', + ] as NewsletterTag[] + ).map(tag => ( + + ))} +
+ = { + 400: 'INVALID_TAGS', + 404: 'NOT_FOUND', + 409: 'ALREADY_SUBSCRIBED', + 429: 'RATE_LIMITED', +}; + +function throwApiError(status: number, body: { message?: string }): never { + throw { + status, + code: codeMap[status] ?? 'UNKNOWN', + message: body.message ?? 'An unexpected error occurred.', + } as NewsletterApiError; +} + export const addToWaitlist = async (data: AddToWaitlistRequest) => { const res = await fetch('/api/waitlist/subscribe', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); @@ -32,16 +76,48 @@ export const addToWaitlist = async (data: AddToWaitlistRequest) => { export const newsletterSubscribe = async (data: NewsletterSubscribeRequest) => { const res = await fetch('/api/newsletter/subscribe', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); - if (!res.ok) { - const errorData = await res.json().catch(() => ({})); - throw new Error(errorData.message || 'Failed to subscribe to newsletter'); + const body = await res.json().catch(() => ({})); + if (!res.ok) throwApiError(res.status, body); + return body as { message: string; id: string }; +}; + +export const newsletterUnsubscribe = async ( + data: NewsletterUnsubscribeRequest +) => { + const res = await fetch('/api/newsletter/unsubscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + + const body = await res.json().catch(() => ({})); + if (!res.ok) throwApiError(res.status, body); + return body as { message: string }; +}; + +export const newsletterUpdatePreferences = async ( + data: NewsletterPreferencesRequest +) => { + const invalid = data.tags.filter(t => !NEWSLETTER_TAGS.includes(t)); + if (invalid.length > 0) { + throw { + status: 400, + code: 'INVALID_TAGS', + message: `Invalid tags: ${invalid.join(', ')}. Allowed: ${NEWSLETTER_TAGS.join(', ')}.`, + } as NewsletterApiError; } - return res.json(); + const res = await fetch('/api/newsletter/preferences', { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + + const body = await res.json().catch(() => ({})); + if (!res.ok) throwApiError(res.status, body); + return body as { message: string }; }; From 17d37c72cbc408ac2c8b2c1f7758d17438ab9ea8 Mon Sep 17 00:00:00 2001 From: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:33:38 +0100 Subject: [PATCH 06/15] Docs: add comprehensive bug test report for organization features (#456) * Test: add comprehensive bug test report for organization features * docs: update bug test report for organization features and address critical issues * fix: update branch name in bug test report and upgrade dompurify to version 3.3.2 --- docs/bug-test-report.md | 123 ++++++++++++++++++++++++++++++++++++++++ package-lock.json | 9 ++- 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 docs/bug-test-report.md diff --git a/docs/bug-test-report.md b/docs/bug-test-report.md new file mode 100644 index 00000000..e09a659e --- /dev/null +++ b/docs/bug-test-report.md @@ -0,0 +1,123 @@ +# Bug Test Report: Organization & Onboarding Flow + +**Project:** BoundlessFi +**Date:** March 7, 2026 +**Environment:** Frontend (Local) / Backend (Staging API) +**Status:** Issue #448 Verification Complete + +--- + +## 1. Executive Summary + +Overall, core organization features (creation, search, and navigation) are stable. However, critical blockers exist within the **Invitation Lifecycle** and **Hackathon Publishing** flows. Additionally, UX refinements are needed for mobile navigation and input validation. + +--- + +## 2. Issue #448 Verification Status [COMPLETED] + +All items specified in Issue #448 have been verified for production readiness: + +| Feature | Status | Notes | +| :----------------------- | :----- | :--------------------------------------------------- | +| **Public Profile Route** | Pass | `/org/[slug]` functions correctly end-to-end. | +| **OG Metadata** | Pass | Title, description, and images validated. | +| **Settings Tabs** | Pass | Profile, Links, Members, and Transfer verified. | +| **Sidebar Actions** | Pass | Host Hackathon and Grants (disabled state) verified. | +| **Search/Sort** | Pass | Search and sort keys behave as expected. | +| **Deep-linking** | Pass | Direct URL loading for organizations is functional. | +| **Header Search** | Pass | `⌘K` navigation is bug-free. | +| **Profile Validation** | Pass | Slug/Profile validation logic is functional. | +| **Mobile Responsive** | Pass | Hamburger menu is functional on settings pages. | + +--- + +## 3. High-Priority Bugs + +### BUG-001: Invite Link Routing Mismatch (404) + +- **Issue:** Generated invite links point to `/signup` instead of the active `/auth` route. +- **Example Link:** `https://staging.boundlessfi.xyz/signup?invitationId=...` +- **Impact:** Users cannot join organizations via email. +- **Recommended Fix:** Update `getInvitationUrl` in the backend/auth-config to target `/auth`. + +### BUG-002: Invite Acceptance Logic Failure + +- **Issue:** Manually correcting the URL to `/auth` allows sign-up, but the user is not associated with the organization. +- **Actual Behavior:** User is redirected to home; status remains "Pending"; member list is not updated. +- **Expected Behavior:** User should automatically join the organization and redirect to the org dashboard. + +### BUG-003: Hackathon Publish Endpoint (404) + +- **Action:** Click **Publish** in the Preview tab for a draft hackathon. +- **Observed Error:** `PUT .../api/organizations/{orgId}/hackathons/draft/{hackathonId}/publish` returns **404 Not Found**. +- **Likely Root Cause:** Wallet balance is insufficient for publish-related on-chain or fee-dependent operations. +- **UX Gap:** The UI does not tell users that the publish failure is caused by **insufficient wallet funds**. +- **Impact:** Users cannot move hackathons from "Draft" to "Live" and do not know how to resolve it. +- **Suggestion:** Show a clear inline/toast error such as: `Insufficient wallet funds to publish. Please fund your wallet and try again.` + +**Suggested User Guidance (Fund Wallet Steps)** + +1. Open wallet settings from the profile/wallet menu. +2. Copy your connected wallet address. +3. Add funds to that wallet on the required network (via exchange transfer, bridge, or faucet for test/staging). +4. Wait for transaction confirmation and refresh the app. +5. Return to the hackathon draft and click **Publish** again. + +### BUG-004: Delete/Archive Organization Non-Functional + +- **Issue:** Actions to delete or archive an organization fail. + +**What I tested** + +- Opened `/organizations`. +- Selected an organization. +- Clicked the **Archive** button and completed the confirmation prompt. +- Clicked the **Delete** button and completed the confirmation prompt. + +**Observed Result** + +- Both actions failed. +- The organization was not archived and not deleted. + +**Expected Result** + +- Archive should move the organization to archived state. +- Delete should remove the organization. + +**Scope** + +- Test type: Manual UI test. +- Environment: Frontend local + staging backend. +- Role: Owner. +- Organizations tested: 1 organization in this pass. + +## 4. UI/UX Improvements + +### UX-001: Mobile Navigation Tabs (Host Hackathon) + +- **Observation:** Sub-navigation tabs (e.g., Participation, Edit) are difficult to access/truncated on mobile screens. +- **Suggestion:** Implement **horizontal scrollable tabs** for sub-nav menus to improve mobile accessibility. + +### UX-002: Slug Availability Feedback + +- **Issue:** The UI displays "Slug available" even for taken slugs, only failing upon final form submission. +- **Suggestion:** Real-time validation should return "Slug already taken" before the user attempts to submit. + +### UX-003: Password Strength Feedback + +- **Issue:** Form submission is blocked for weak passwords without explaining why. +- **Suggestion:** Add a helper text: _"Password must be at least 8 characters and include a number."_ + +--- + +## 5. Technical Edge Cases + +### Route Guarding: Invalid Organization IDs + +- **Issue:** Manually entering an invalid ID (e.g., `/organizations/random123/settings`) still renders the settings UI shell. +- **Recommendation:** Implement a check to return a 404 page if the Organization ID does not exist in the database. + +--- + +**Reported by:** QA Team +**Branch:** `Test/Test-the-organization-flow---manual-QA-checklist` diff --git a/package-lock.json b/package-lock.json index 32c85f04..8a7aca79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8970,10 +8970,13 @@ } }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } From e16dfeeb22df1f901dc8ed84cd0954bde85933fb Mon Sep 17 00:00:00 2001 From: Collins Ikechukwu Date: Sun, 8 Mar 2026 19:18:38 +0100 Subject: [PATCH 07/15] fix: update the milestone submission flow for campaign --- .../milestones/[milestoneIndex]/page.tsx | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/me/crowdfunding/[slug]/milestones/[milestoneIndex]/page.tsx b/app/me/crowdfunding/[slug]/milestones/[milestoneIndex]/page.tsx index 2d346395..7aa3c0bf 100644 --- a/app/me/crowdfunding/[slug]/milestones/[milestoneIndex]/page.tsx +++ b/app/me/crowdfunding/[slug]/milestones/[milestoneIndex]/page.tsx @@ -162,31 +162,31 @@ export default function MilestoneDetailPage({ params }: PageProps) { // Step 3: Perform blockchain transaction with Trustless Work SDK toast('Please confirm the transaction in your wallet'); - const { unsignedTransaction } = await changeMilestoneStatus( - { - contractId: campaign.escrowAddress, - milestoneIndex: (milestone.orderIndex ?? milestoneIndex).toString(), - newStatus: data.status === 'completed' ? 'completed' : 'in_progress', - newEvidence: data.submissionNotes, // Use submission notes as evidence description - serviceProvider: walletAddress || '', - }, - 'multi-release' - ); - if (!unsignedTransaction) { - throw new Error( - 'Unsigned transaction is missing from useChangeMilestoneStatusresponse.' - ); - } - - const signedXdr = await signTransaction({ - unsignedTransaction, - address: walletAddress || '', - }); - - const trxsent = await sendTransaction(signedXdr); - if (trxsent.status === 'SUCCESS') { - toast.success('Transaction confirmed on blockchain'); - } + // const { unsignedTransaction } = await changeMilestoneStatus( + // { + // contractId: campaign.escrowAddress, + // milestoneIndex: (milestone.orderIndex ?? milestoneIndex).toString(), + // newStatus: data.status === 'completed' ? 'completed' : 'in_progress', + // newEvidence: data.submissionNotes, // Use submission notes as evidence description + // serviceProvider: walletAddress || '', + // }, + // 'multi-release' + // ); + // if (!unsignedTransaction) { + // throw new Error( + // 'Unsigned transaction is missing from useChangeMilestoneStatusresponse.' + // ); + // } + + // const signedXdr = await signTransaction({ + // unsignedTransaction, + // address: walletAddress || '', + // }); + + // const trxsent = await sendTransaction(signedXdr); + // if (trxsent.status === 'SUCCESS') { + // toast.success('Transaction confirmed on blockchain'); + // } toast('Updating milestone...'); From b9d770d488d8c4a3ca6ca2ce2f2dcee10bf112d0 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:22:46 +0100 Subject: [PATCH 08/15] UI fixes (#458) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * fix: fix auto refresh ib submission page * fix: hackathon submission fixes * fix: fix coderabbit corrections * fix: fix coderabbit corrections * chore: write boundless on x challenge blog * fix: remove blog --- .../hackathons/[slug]/submit/page.tsx | 35 ++++++++--- app/me/projects/page.tsx | 42 +++++++------ .../hackathons/submissions/SubmissionForm.tsx | 22 +++---- .../hackathons/submissions/submissionCard.tsx | 15 ++++- .../hackathons/submissions/submissionTab.tsx | 2 + .../settings/GeneralSettingsTab.tsx | 22 ++++++- components/profile/ProjectsTab.tsx | 61 +++++++++++-------- components/profile/ProjectsTabPublic.tsx | 48 +++++++++------ components/ui/expandable-screen.tsx | 4 ++ features/projects/components/ProjectCard.tsx | 59 +++++++++++++++--- features/projects/types/index.ts | 7 +++ hooks/hackathon/use-participants.ts | 45 ++++++++++---- lib/api/types.ts | 1 + lib/providers/hackathonProvider.tsx | 2 +- package-lock.json | 24 ++++++++ package.json | 2 + 16 files changed, 279 insertions(+), 112 deletions(-) diff --git a/app/(landing)/hackathons/[slug]/submit/page.tsx b/app/(landing)/hackathons/[slug]/submit/page.tsx index f5ca054e..61e39497 100644 --- a/app/(landing)/hackathons/[slug]/submit/page.tsx +++ b/app/(landing)/hackathons/[slug]/submit/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { use, useEffect } from 'react'; +import { use, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useHackathonData } from '@/lib/providers/hackathonProvider'; import { useAuthStatus } from '@/hooks/use-auth'; @@ -70,15 +70,34 @@ export default function SubmitProjectPage({ router.push(`/hackathons/${hackathonSlug}?tab=submission`); }; - if ( - isLoading || - hackathonLoading || - isLoadingMySubmission || - !currentHackathon - ) { + const [hasInitialLoaded, setHasInitialLoaded] = useState(false); + + useEffect(() => { + if (!isLoading && !hackathonLoading && !isLoadingMySubmission) { + setHasInitialLoaded(true); + } + }, [isLoading, hackathonLoading, isLoadingMySubmission]); + + if (!hasInitialLoaded) { return ; } + if (!currentHackathon) { + return ( +
+

Hackathon Not Found

+ +
+ ); + } + return (
@@ -107,6 +126,8 @@ export default function SubmitProjectPage({ introduction: mySubmission.introduction, links: mySubmission.links, participationType: (mySubmission as any).participationType, + teamName: (mySubmission as any).teamName, + teamMembers: (mySubmission as any).teamMembers, } : undefined } diff --git a/app/me/projects/page.tsx b/app/me/projects/page.tsx index 4c61e8f1..fd6c819b 100644 --- a/app/me/projects/page.tsx +++ b/app/me/projects/page.tsx @@ -102,14 +102,23 @@ export default function MyProjectsPage() { ) : (
- {sortedProjects.map(project => ( - { + // Find if this project is a hackathon submission + const submission = + meData.user.hackathonSubmissionsAsParticipant?.find( + s => s.projectId === project.id + ); + + // If it's a submission, use the submission ID for the slug to ensure correct redirection from ProjectCard + const displayId = submission?.id || project.id; + + return ( + - ))} + isSubmission: !!submission, + submissionStatus: submission?.status, + }} + /> + ); + })}
)}
diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index 1604189b..12d5273f 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -34,6 +34,7 @@ import { ExpandableScreenContent, ExpandableScreenTrigger, useExpandableScreen, + useOptionalExpandableScreen, } from '@/components/ui/expandable-screen'; import Stepper from '@/components/stepper/Stepper'; import { uploadService } from '@/lib/api/upload'; @@ -201,16 +202,9 @@ const SubmissionFormContent: React.FC = ({ onSuccess, onClose, }) => { - // Use context carefully since it might not be available when used standalone - let collapse = () => {}; - let open = true; - try { - const expandableCtx = useExpandableScreen(); - collapse = expandableCtx.collapse; - open = expandableCtx.isExpanded; - } catch (e) { - // Standalone mode, not in ExpandableScreen - } + const expandableCtx = useOptionalExpandableScreen(); + const collapse = expandableCtx?.collapse ?? (() => {}); + const open = expandableCtx?.isExpanded ?? true; const { currentHackathon } = useHackathonData(); const { user } = useAuthStatus(); @@ -416,7 +410,7 @@ const SubmissionFormContent: React.FC = ({ description: 'An intelligent task management application that uses machine learning to prioritize tasks...', logo: '', - videoUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + videoUrl: 'https://youtu.be/rOSZhhblE_8?si=Hf_YvPTMmyWTUOKQ', introduction: 'This project leverages advanced AI algorithms...', links: [ { type: 'github', url: 'https://github.com/example/ai-task-manager' }, @@ -785,12 +779,14 @@ const SubmissionFormContent: React.FC = ({ } else { await create(submissionData); } - if (onClose) { + + if (onSuccess) { + onSuccess(); + } else if (onClose) { onClose(); } else { collapse(); } - onSuccess?.(); } catch { // Error handled in hook } diff --git a/components/hackathons/submissions/submissionCard.tsx b/components/hackathons/submissions/submissionCard.tsx index 1f0d6e59..f5b51162 100644 --- a/components/hackathons/submissions/submissionCard.tsx +++ b/components/hackathons/submissions/submissionCard.tsx @@ -127,6 +127,16 @@ const SubmissionCard = ({ return formatDistanceToNow(new Date(dateString), { addSuffix: true }); }; + const handleEditSelect = (e: Event) => { + e.stopPropagation(); + onEditClick?.(); + }; + + const handleDeleteSelect = (e: Event) => { + e.stopPropagation(); + onDeleteClick?.(); + }; + return (
@@ -181,14 +192,14 @@ const SubmissionCard = ({ className='border-gray-800 bg-black text-white' > onEditClick?.()} + onSelect={handleEditSelect} className='cursor-pointer text-gray-300 focus:bg-gray-800 focus:text-white' > Edit Submission onDeleteClick?.()} + onSelect={handleDeleteSelect} className='cursor-pointer text-red-500 focus:bg-red-900/20 focus:text-red-400' > diff --git a/components/hackathons/submissions/submissionTab.tsx b/components/hackathons/submissions/submissionTab.tsx index fff6a497..29193c83 100644 --- a/components/hackathons/submissions/submissionTab.tsx +++ b/components/hackathons/submissions/submissionTab.tsx @@ -83,6 +83,7 @@ const SubmissionTabContent: React.FC = ({ setSelectedSort, setSelectedCategory, } = useSubmissions(); + const { currentHackathon, loading: isHackathonDataLoading } = useHackathonData(); const { status } = useHackathonStatus( @@ -276,6 +277,7 @@ const SubmissionTabContent: React.FC = ({ {/* Submissions Grid with Create Button if no submission */} {!isLoadingMySubmission && + !isHackathonDataLoading && !mySubmission && isAuthenticated && isRegistered && diff --git a/components/organization/hackathons/settings/GeneralSettingsTab.tsx b/components/organization/hackathons/settings/GeneralSettingsTab.tsx index e0b2ce74..1c1e84e6 100644 --- a/components/organization/hackathons/settings/GeneralSettingsTab.tsx +++ b/components/organization/hackathons/settings/GeneralSettingsTab.tsx @@ -55,6 +55,8 @@ interface GeneralSettingsTabProps { isPublished?: boolean; } +import TurndownService from 'turndown'; + export default function GeneralSettingsTab({ organizationId, hackathonId, @@ -78,6 +80,24 @@ export default function GeneralSettingsTab({ address: string; } | null>(null); + // Normalize HTML to Markdown for existing descriptions + const normalizedDescription = React.useMemo(() => { + let desc = initialData?.description || ''; + if (desc && /<[a-z][\\s\\S]*>/i.test(desc)) { + try { + const turndownService = new TurndownService({ + headingStyle: 'atx', + codeBlockStyle: 'fenced', + bulletListMarker: '-', + }); + desc = turndownService.turndown(desc); + } catch (err) { + console.error('Failed to convert HTML to Markdown', err); + } + } + return desc; + }, [initialData?.description]); + const form = useForm({ resolver: zodResolver(infoSchema), defaultValues: { @@ -85,7 +105,7 @@ export default function GeneralSettingsTab({ tagline: initialData?.tagline || '', slug: initialData?.slug || '', banner: initialData?.banner || '', - description: initialData?.description || '', + description: normalizedDescription, categories: Array.isArray(initialData?.categories) ? initialData.categories : [], diff --git a/components/profile/ProjectsTab.tsx b/components/profile/ProjectsTab.tsx index ff2808d5..4758ce18 100644 --- a/components/profile/ProjectsTab.tsx +++ b/components/profile/ProjectsTab.tsx @@ -100,38 +100,47 @@ export default function ProjectsTab({ user }: ProjectsTabProps) { onScrollCapture={handleScroll} >
- {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 478c1faf..ccbf7194 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 7eaf54e9..4894a27b 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/features/projects/components/ProjectCard.tsx b/features/projects/components/ProjectCard.tsx index 8dd194c8..155c4ec3 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 bce8bcc4..7d0418da 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 f6fe782b..45e43122 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 39010e57..d418161a 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 e6b26fbb..e21a12c1 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 8a7aca79..b7d49bcf 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", @@ -16842,6 +16857,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 8e73c10a..a557459e 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", From fd0fdf216ecaca8ae472ec87ab0942bffe3474e8 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:27:03 +0100 Subject: [PATCH 09/15] Blog fix (#464) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * fix: fix auto refresh ib submission page * fix: hackathon submission fixes * fix: fix coderabbit corrections * fix: fix coderabbit corrections * chore: write boundless on x challenge blog * fix: remove blog * chore: add blog content --- ...ou-know-about-boundless-on-x-challenge.mdx | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 content/blog/what-do-you-know-about-boundless-on-x-challenge.mdx 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 00000000..baf6dc81 --- /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)** From ab5e64aa8681416f1f4369bcae0f03e698ba4d30 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:44:47 +0100 Subject: [PATCH 10/15] Blog fix (#465) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * fix: fix auto refresh ib submission page * fix: hackathon submission fixes * fix: fix coderabbit corrections * fix: fix coderabbit corrections * chore: write boundless on x challenge blog * fix: remove blog * chore: add blog content * fix: fix broken blog image and notification redirect --- components/notifications/NotificationDropdown.tsx | 2 +- .../blog/what-do-you-know-about-boundless-on-x-challenge.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/notifications/NotificationDropdown.tsx b/components/notifications/NotificationDropdown.tsx index 63f36bc3..c92a780e 100644 --- a/components/notifications/NotificationDropdown.tsx +++ b/components/notifications/NotificationDropdown.tsx @@ -211,7 +211,7 @@ export const NotificationDropdown = ({ )} 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 index baf6dc81..166a9c1c 100644 --- 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 @@ -1,7 +1,7 @@ --- 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' +coverImage: 'https://res.cloudinary.com/dmsphf4d3/image/upload/v1773048379/boundless-on-x.webp' publishedAt: '2026-03-09' author: name: 'Boundless Team' From a4d68612f99541d936e2816d2326b544f032f3ca Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:58:56 +0100 Subject: [PATCH 11/15] merge fork main to boundlessfi main (#468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve timeline input , ui improvement and fixes for participation tab * merg to prod (#450) * fix: improve timeline input , ui improvement and fixes for participation tab (#442) * feat: integrate Sentry for error tracking and reporting - Added Sentry configuration for both server and edge environments to capture errors and performance metrics. - Updated error handling across various components to report errors to Sentry, enhancing monitoring capabilities. - Introduced a new MessagesProvider for managing in-app messaging and notifications. - Refactored the .env.example file to include Sentry DSN and related configurations for production environments. - Removed the deprecated connect-wallet component to streamline the codebase. * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * UI fixes (#445) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: handle potential null values in notification data - Updated notification handling to safely access properties using optional chaining. - Ensured that the notification data is checked for null values before accessing its properties in various components, improving robustness and preventing runtime errors. * feat: Enforce Submission Requirements (requireGithub, requireDemoVideo, requireOtherLinks) (#443) * feat: enforce submission requirements (requireGithub, requireDemoVideo, requireOtherLinks) - Add dynamic schema validation based on currentHackathon flags - Enforce requireDemoVideo at schema level with Zod refine - Add Step 2 validation for requireGithub, requireDemoVideo, requireOtherLinks - Add final validation pass in onSubmit before API call - Add dynamic asterisk (*) to required field labels - Add legend and helper text for required fields - Wrap links section in FormField for proper error display * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * feat: prevent removal of required links and style required asterisks in red Made-with: Cursor --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: felipevega2x * feat(submissions): enforce team size limits (teamMin, teamMax) (#446) * feat(submissions): enforce team size limits (teamMin, teamMax) - Add validation in handleNext (Step 0) to block progression when team size is below teamMin, showing specific message with members needed - Guard handleAddInvitee function to prevent adding members when team reaches teamMax capacity - Add capacity indicator Badge showing "X / Y members" with visual states: orange when below minimum, yellow when at capacity - Disable "Add Member" button and inputs when team is full - Display helper text when team is below minimum requirement Closes #405 * fix(submissions): remove hardcoded team size fallbacks - Use undefined instead of hardcoded defaults (1, 10) - Add hasTeamLimits guard to only enforce when hackathon defines limits - Conditionally render Badge and helper text when limits are defined * refactor: enhance error handling and submission response structure - Introduced a utility function to standardize API error messages across submission operations. - Updated the create and update submission functions to handle optional chaining for response checks. - Improved the response structure to include messages for successful submissions. - Adjusted the CreateSubmissionRequest interface to remove the hackathonId field and made organizationId optional in the update submission request. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> * UI fixes (#451) (#453) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * ui-fixes (#455) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * merge to prod (#457) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * merg to prod (#459) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> * merg to prod (#461) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages * Docs: add comprehensive bug test report for organization features (#456) * Test: add comprehensive bug test report for organization features * docs: update bug test report for organization features and address critical issues * fix: update branch name in bug test report and upgrade dompurify to version 3.3.2 * fix: update the milestone submission flow for campaign --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> --------- Co-authored-by: Collins Ikechukwu Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> --- app/(landing)/hackathons/[slug]/submit/page.tsx | 1 - components/hackathons/submissions/SubmissionForm.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/app/(landing)/hackathons/[slug]/submit/page.tsx b/app/(landing)/hackathons/[slug]/submit/page.tsx index 61e39497..f0721551 100644 --- a/app/(landing)/hackathons/[slug]/submit/page.tsx +++ b/app/(landing)/hackathons/[slug]/submit/page.tsx @@ -97,7 +97,6 @@ export default function SubmitProjectPage({
); } - return (
diff --git a/components/hackathons/submissions/SubmissionForm.tsx b/components/hackathons/submissions/SubmissionForm.tsx index 12d5273f..cabc7abe 100644 --- a/components/hackathons/submissions/SubmissionForm.tsx +++ b/components/hackathons/submissions/SubmissionForm.tsx @@ -779,7 +779,6 @@ const SubmissionFormContent: React.FC = ({ } else { await create(submissionData); } - if (onSuccess) { onSuccess(); } else if (onClose) { From 72fc1fd0b4be2416bc49e8c7d1b387059d980807 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:59:52 +0100 Subject: [PATCH 12/15] merge to main (#469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve timeline input , ui improvement and fixes for participation tab * merg to prod (#450) * fix: improve timeline input , ui improvement and fixes for participation tab (#442) * feat: integrate Sentry for error tracking and reporting - Added Sentry configuration for both server and edge environments to capture errors and performance metrics. - Updated error handling across various components to report errors to Sentry, enhancing monitoring capabilities. - Introduced a new MessagesProvider for managing in-app messaging and notifications. - Refactored the .env.example file to include Sentry DSN and related configurations for production environments. - Removed the deprecated connect-wallet component to streamline the codebase. * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * UI fixes (#445) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: handle potential null values in notification data - Updated notification handling to safely access properties using optional chaining. - Ensured that the notification data is checked for null values before accessing its properties in various components, improving robustness and preventing runtime errors. * feat: Enforce Submission Requirements (requireGithub, requireDemoVideo, requireOtherLinks) (#443) * feat: enforce submission requirements (requireGithub, requireDemoVideo, requireOtherLinks) - Add dynamic schema validation based on currentHackathon flags - Enforce requireDemoVideo at schema level with Zod refine - Add Step 2 validation for requireGithub, requireDemoVideo, requireOtherLinks - Add final validation pass in onSubmit before API call - Add dynamic asterisk (*) to required field labels - Add legend and helper text for required fields - Wrap links section in FormField for proper error display * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * feat: prevent removal of required links and style required asterisks in red Made-with: Cursor --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: felipevega2x * feat(submissions): enforce team size limits (teamMin, teamMax) (#446) * feat(submissions): enforce team size limits (teamMin, teamMax) - Add validation in handleNext (Step 0) to block progression when team size is below teamMin, showing specific message with members needed - Guard handleAddInvitee function to prevent adding members when team reaches teamMax capacity - Add capacity indicator Badge showing "X / Y members" with visual states: orange when below minimum, yellow when at capacity - Disable "Add Member" button and inputs when team is full - Display helper text when team is below minimum requirement Closes #405 * fix(submissions): remove hardcoded team size fallbacks - Use undefined instead of hardcoded defaults (1, 10) - Add hasTeamLimits guard to only enforce when hackathon defines limits - Conditionally render Badge and helper text when limits are defined * refactor: enhance error handling and submission response structure - Introduced a utility function to standardize API error messages across submission operations. - Updated the create and update submission functions to handle optional chaining for response checks. - Improved the response structure to include messages for successful submissions. - Adjusted the CreateSubmissionRequest interface to remove the hackathonId field and made organizationId optional in the update submission request. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> * UI fixes (#451) (#453) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * ui-fixes (#455) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * merge to prod (#457) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * merg to prod (#459) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> * merg to prod (#461) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages * Docs: add comprehensive bug test report for organization features (#456) * Test: add comprehensive bug test report for organization features * docs: update bug test report for organization features and address critical issues * fix: update branch name in bug test report and upgrade dompurify to version 3.3.2 * fix: update the milestone submission flow for campaign --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> --------- Co-authored-by: Collins Ikechukwu Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> From f26787fda13b2c95e6248f569bada20b09feb3f3 Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:05:00 +0100 Subject: [PATCH 13/15] merge (#470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: improve timeline input , ui improvement and fixes for participation tab * merg to prod (#450) * fix: improve timeline input , ui improvement and fixes for participation tab (#442) * feat: integrate Sentry for error tracking and reporting - Added Sentry configuration for both server and edge environments to capture errors and performance metrics. - Updated error handling across various components to report errors to Sentry, enhancing monitoring capabilities. - Introduced a new MessagesProvider for managing in-app messaging and notifications. - Refactored the .env.example file to include Sentry DSN and related configurations for production environments. - Removed the deprecated connect-wallet component to streamline the codebase. * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * UI fixes (#445) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: handle potential null values in notification data - Updated notification handling to safely access properties using optional chaining. - Ensured that the notification data is checked for null values before accessing its properties in various components, improving robustness and preventing runtime errors. * feat: Enforce Submission Requirements (requireGithub, requireDemoVideo, requireOtherLinks) (#443) * feat: enforce submission requirements (requireGithub, requireDemoVideo, requireOtherLinks) - Add dynamic schema validation based on currentHackathon flags - Enforce requireDemoVideo at schema level with Zod refine - Add Step 2 validation for requireGithub, requireDemoVideo, requireOtherLinks - Add final validation pass in onSubmit before API call - Add dynamic asterisk (*) to required field labels - Add legend and helper text for required fields - Wrap links section in FormField for proper error display * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * feat: prevent removal of required links and style required asterisks in red Made-with: Cursor --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: felipevega2x * feat(submissions): enforce team size limits (teamMin, teamMax) (#446) * feat(submissions): enforce team size limits (teamMin, teamMax) - Add validation in handleNext (Step 0) to block progression when team size is below teamMin, showing specific message with members needed - Guard handleAddInvitee function to prevent adding members when team reaches teamMax capacity - Add capacity indicator Badge showing "X / Y members" with visual states: orange when below minimum, yellow when at capacity - Disable "Add Member" button and inputs when team is full - Display helper text when team is below minimum requirement Closes #405 * fix(submissions): remove hardcoded team size fallbacks - Use undefined instead of hardcoded defaults (1, 10) - Add hasTeamLimits guard to only enforce when hackathon defines limits - Conditionally render Badge and helper text when limits are defined * refactor: enhance error handling and submission response structure - Introduced a utility function to standardize API error messages across submission operations. - Updated the create and update submission functions to handle optional chaining for response checks. - Improved the response structure to include messages for successful submissions. - Adjusted the CreateSubmissionRequest interface to remove the hackathonId field and made organizationId optional in the update submission request. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> * UI fixes (#451) (#453) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * ui-fixes (#455) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * merge to prod (#457) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * merg to prod (#459) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> * merg to prod (#461) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages * Docs: add comprehensive bug test report for organization features (#456) * Test: add comprehensive bug test report for organization features * docs: update bug test report for organization features and address critical issues * fix: update branch name in bug test report and upgrade dompurify to version 3.3.2 * fix: update the milestone submission flow for campaign --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> --------- Co-authored-by: Collins Ikechukwu Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> From bb28c3b9289c053014788daba5bf45d47f85838a Mon Sep 17 00:00:00 2001 From: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:37:44 +0100 Subject: [PATCH 14/15] merge fork to main (#471) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * merg to prod (#450) * fix: improve timeline input , ui improvement and fixes for participation tab (#442) * feat: integrate Sentry for error tracking and reporting - Added Sentry configuration for both server and edge environments to capture errors and performance metrics. - Updated error handling across various components to report errors to Sentry, enhancing monitoring capabilities. - Introduced a new MessagesProvider for managing in-app messaging and notifications. - Refactored the .env.example file to include Sentry DSN and related configurations for production environments. - Removed the deprecated connect-wallet component to streamline the codebase. * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * UI fixes (#445) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: handle potential null values in notification data - Updated notification handling to safely access properties using optional chaining. - Ensured that the notification data is checked for null values before accessing its properties in various components, improving robustness and preventing runtime errors. * feat: Enforce Submission Requirements (requireGithub, requireDemoVideo, requireOtherLinks) (#443) * feat: enforce submission requirements (requireGithub, requireDemoVideo, requireOtherLinks) - Add dynamic schema validation based on currentHackathon flags - Enforce requireDemoVideo at schema level with Zod refine - Add Step 2 validation for requireGithub, requireDemoVideo, requireOtherLinks - Add final validation pass in onSubmit before API call - Add dynamic asterisk (*) to required field labels - Add legend and helper text for required fields - Wrap links section in FormField for proper error display * UI fixes (#444) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * feat: prevent removal of required links and style required asterisks in red Made-with: Cursor --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: felipevega2x * feat(submissions): enforce team size limits (teamMin, teamMax) (#446) * feat(submissions): enforce team size limits (teamMin, teamMax) - Add validation in handleNext (Step 0) to block progression when team size is below teamMin, showing specific message with members needed - Guard handleAddInvitee function to prevent adding members when team reaches teamMax capacity - Add capacity indicator Badge showing "X / Y members" with visual states: orange when below minimum, yellow when at capacity - Disable "Add Member" button and inputs when team is full - Display helper text when team is below minimum requirement Closes #405 * fix(submissions): remove hardcoded team size fallbacks - Use undefined instead of hardcoded defaults (1, 10) - Add hasTeamLimits guard to only enforce when hackathon defines limits - Conditionally render Badge and helper text when limits are defined * refactor: enhance error handling and submission response structure - Introduced a utility function to standardize API error messages across submission operations. - Updated the create and update submission functions to handle optional chaining for response checks. - Improved the response structure to include messages for successful submissions. - Adjusted the CreateSubmissionRequest interface to remove the hackathonId field and made organizationId optional in the update submission request. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> * UI fixes (#451) (#453) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * ui-fixes (#455) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * merge to prod (#457) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> * merg to prod (#459) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> * merg to prod (#461) * UI fixes (#451) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * UI fixes (#454) * fix: improve timeline input , ui improvement and fixes for participation tab * fix: implement 2fa for email and password login * fix: fix conflict * fix: fix submission form * fix: fix hackathon submission and participant page * fix: fix hackathon submission and participant page * feat(navbar): enhance navbar styles and layout - Added CSS variables for active navbar link states, improving visual feedback. - Refactored navbar layout for better alignment and responsiveness, including adjustments for mobile and desktop views. - Updated component structure to ensure consistent styling and behavior across different states. * feat(announcements): enhance announcement rendering and markdown support - Implemented a `stripMarkdown` function to convert Markdown content to plain text for better preview handling in the announcement page. - Created an `AnnouncementPreview` component to render announcements with Markdown support, improving content display in the announcements tab. - Updated the announcement editor to utilize a dynamic Markdown editor, enhancing the editing experience for users. * feat(newsletter): implement newsletter API integration with subscribe… (#452) * feat(newsletter): implement newsletter API integration with subscribe, confirm, unsubscribe, and preferences * Draft feat(newsletter): Imp some extra things * feat(newsletter): add API routes, client helpers, and UI pages * Docs: add comprehensive bug test report for organization features (#456) * Test: add comprehensive bug test report for organization features * docs: update bug test report for organization features and address critical issues * fix: update branch name in bug test report and upgrade dompurify to version 3.3.2 * fix: update the milestone submission flow for campaign --------- Co-authored-by: Nnaji Benjamin <60315147+Benjtalkshow@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> --------- Co-authored-by: Collins Ikechukwu Co-authored-by: Matias Aguilar Co-authored-by: felipevega2x Co-authored-by: Josué Araya Marín <104031367+Josue19-08@users.noreply.github.com> Co-authored-by: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Co-authored-by: Ekene Ngwudike <94962750+Ekene001@users.noreply.github.com> From 5a3eee3524a7a0d676e769d46f2b4fa48894ef76 Mon Sep 17 00:00:00 2001 From: David Emulo <161654052+Davidemulo@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:02:25 +0100 Subject: [PATCH 15/15] Create hackathon-detail-design.md (#466) --- .../[slug]/hackathon-detail-design.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 app/(landing)/hackathons/[slug]/hackathon-detail-design.md diff --git a/app/(landing)/hackathons/[slug]/hackathon-detail-design.md b/app/(landing)/hackathons/[slug]/hackathon-detail-design.md new file mode 100644 index 00000000..cdb81ff1 --- /dev/null +++ b/app/(landing)/hackathons/[slug]/hackathon-detail-design.md @@ -0,0 +1,30 @@ +Hackathon Detail Page Redesign +Issue: #414 + +Figma Design +https://www.figma.com/design/EMNGAQl1SGObXcsoa24krt/Boundless_Project-Details?node-id=0-1&t=A1ywRcn60Xyw0X6h-1 + +This design proposes a cleaner and more professional UI/UX for the hackathon detail page. + +Included in the Figma file: +- Desktop layout +- Mobile layout +- Banner / hero placement proposal +- Redesigned hero section +- Sticky sidebar card +- Tab navigation +- All tab layouts (overview, participants, resources, announcements, submissions, discussions, find team, winners) +- Loading state +- Hackathon not found state + +Banner Placement Proposal +The hackathon banner is placed as a full-width hero image at the top of the page, allowing it to visually represent the hackathon and improve page identity. + +The sidebar becomes a compact summary card with key information and actions. + +Design Goals +- Simpler UI and improved visual hierarchy +- Clear primary actions (Join, Submit, View Submission) +- Consistent spacing and typography +- Better mobile usability +- Professional and product-quality look