diff --git a/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx b/app/(landing)/hackathons/[slug]/HackathonPageClient.tsx
index 0c4d1adf3..3f38dce02 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 000000000..f5ca054e2
--- /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 dcb8ccd55..f08d27b0a 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 470562408..9a4a9c2a0 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 3c48a5347..1604189b6 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 = ({