From 45c75b3283d3af2b9663a0075bfd7c5dc9c0f5cc Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Nov 2025 14:11:12 +0700 Subject: [PATCH 1/7] fix(dashboard): unwrap nested team data from API response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/hackathon/src/app/dashboard/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/hackathon/src/app/dashboard/page.tsx b/apps/hackathon/src/app/dashboard/page.tsx index 27a597e..e14b7ba 100644 --- a/apps/hackathon/src/app/dashboard/page.tsx +++ b/apps/hackathon/src/app/dashboard/page.tsx @@ -131,7 +131,8 @@ const DashboardPage: FC = (): ReactElement => { {myTeams.length > 0 ? ( (() => { - const team = myTeams[0] as any; + const item = myTeams[0] as any; + const team = item.team || item; return (

From 23b6677323a8bb47e5be8811b515e85114d4cff5 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Nov 2025 14:13:11 +0700 Subject: [PATCH 2/7] fix: unwrap nested team data in hooks instead of components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - useMyTeams and useTeamsByUserId now unwrap {team: {...}} structure - Simplified dashboard and user profile components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/hackathon/src/app/dashboard/page.tsx | 3 +- .../hackathon/src/app/users/[userId]/page.tsx | 101 +++++++++--------- libs/service/src/hooks/teams/index.ts | 14 ++- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/apps/hackathon/src/app/dashboard/page.tsx b/apps/hackathon/src/app/dashboard/page.tsx index e14b7ba..27a597e 100644 --- a/apps/hackathon/src/app/dashboard/page.tsx +++ b/apps/hackathon/src/app/dashboard/page.tsx @@ -131,8 +131,7 @@ const DashboardPage: FC = (): ReactElement => { {myTeams.length > 0 ? ( (() => { - const item = myTeams[0] as any; - const team = item.team || item; + const team = myTeams[0] as any; return (

diff --git a/apps/hackathon/src/app/users/[userId]/page.tsx b/apps/hackathon/src/app/users/[userId]/page.tsx index 76f7ff1..ac13860 100644 --- a/apps/hackathon/src/app/users/[userId]/page.tsx +++ b/apps/hackathon/src/app/users/[userId]/page.tsx @@ -131,61 +131,58 @@ const UserProfilePage: FC = (): ReactElement => { Team

- {userTeams.map((item: any) => { - const team = item.team || item; - return ( - - {team.name} -
-
- {team.logo ? ( - {team.name} - ) : ( -
- -
- )} -
-

- {team.name} -

-
- {team.has_submission && ( - - - Submitted - - )} - {team.city && ( - - - {team.city} - - )} -
+ {userTeams.map((team: any) => ( + + {team.name} +
+
+ {team.logo ? ( + {team.name} + ) : ( +
+ +
+ )} +
+

+ {team.name} +

+
+ {team.has_submission && ( + + + Submitted + + )} + {team.city && ( + + + {team.city} + + )}
- {team.description && ( -

- {team.description} -

- )}
- - ); - })} + {team.description && ( +

+ {team.description} +

+ )} +
+ + ))}
) : ( diff --git a/libs/service/src/hooks/teams/index.ts b/libs/service/src/hooks/teams/index.ts index b0cf0f3..57626a5 100644 --- a/libs/service/src/hooks/teams/index.ts +++ b/libs/service/src/hooks/teams/index.ts @@ -434,8 +434,11 @@ export const useMyTeams = () => { return useQuery({ queryKey: teamKeys.myTeams(), queryFn: async () => { - const response = await hackathonApi.get>('/teams/my'); - return { data: response.data.data || [] }; + const response = await hackathonApi.get>('/teams/my'); + const rawData = response.data.data || []; + // Unwrap nested team data if present (API returns [{team: {...}}] or [{id, name, ...}]) + const teams = rawData.map((item: any) => item.team || item); + return { data: teams }; }, enabled: !!session?.user?.id, }); @@ -555,8 +558,11 @@ export const useTeamsByUserId = (userId: string) => { return useQuery({ queryKey: ['teams-by-user', userId], queryFn: async () => { - const response = await hackathonApi.get>(`/users/${userId}/teams`); - return { data: response.data.data || [] }; + const response = await hackathonApi.get>(`/users/${userId}/teams`); + const rawData = response.data.data || []; + // Unwrap nested team data if present (API returns [{team: {...}}] or [{id, name, ...}]) + const teams = rawData.map((item: any) => item.team || item); + return { data: teams }; }, enabled: !!userId, }); From 6bbc87479a463a2d4bbb986a665de3d6b88fd826 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Nov 2025 22:15:49 +0700 Subject: [PATCH 3/7] feat(auth): disable registration after 23:29 WIB Nov 30, 2025 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show "Registration Closed" message after deadline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/hackathon/src/app/auth/signup/page.tsx | 51 ++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/apps/hackathon/src/app/auth/signup/page.tsx b/apps/hackathon/src/app/auth/signup/page.tsx index f879dc1..1140d0b 100644 --- a/apps/hackathon/src/app/auth/signup/page.tsx +++ b/apps/hackathon/src/app/auth/signup/page.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useGitHubAuth, useSignup } from '@imphnen-frontend-service/service'; import { GithubOutlined } from '@ant-design/icons'; -import { useNavigate, Link, Links } from 'react-router'; +import { useNavigate, Link } from 'react-router'; import { toast } from 'sonner'; import { Icon } from '@iconify/react'; import { ThemeToggle } from '../../../components/theme-toggle'; @@ -32,8 +32,14 @@ const signupSchema = z type SignupFormData = z.infer; +// Registration deadline: 2025-11-30 23:29:00 WIB (UTC+7) +const REGISTRATION_DEADLINE = new Date('2025-11-30T16:29:00Z'); + export default function SignupPage() { const navigate = useNavigate(); + + // Check if registration is closed + const isRegistrationClosed = new Date() >= REGISTRATION_DEADLINE; const { signInWithGitHub } = useGitHubAuth(); const signupMutation = useSignup(); const [isGithubLoading, setIsGithubLoading] = useState(false); @@ -70,6 +76,49 @@ export default function SignupPage() { } }; + // Show closed registration screen + if (isRegistrationClosed) { + return ( +
+
+
+
+ +
+

+ Registration Closed +

+

+ The registration period for this hackathon has ended. +

+
+ +
+

+ Thank you for your interest! Registration closed on November 30, 2025 at 23:29 WIB. +

+ + + + + + +
+
+
+ ); + } + // Show success screen after registration if (registrationSuccess) { return ( From 0c646ceb85738fee323c6e387c51f6d0db9776a7 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Nov 2025 22:43:09 +0700 Subject: [PATCH 4/7] feat: pyush --- apps/hackathon/src/app/dashboard/page.tsx | 46 ++++++++++------ .../hackathon/src/app/teams/[teamId]/page.tsx | 18 ++++--- apps/hackathon/src/app/teams/browse/page.tsx | 9 +++- apps/hackathon/src/app/teams/create/page.tsx | 52 +++++++++++++++++++ 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/apps/hackathon/src/app/dashboard/page.tsx b/apps/hackathon/src/app/dashboard/page.tsx index 27a597e..ffe5f2d 100644 --- a/apps/hackathon/src/app/dashboard/page.tsx +++ b/apps/hackathon/src/app/dashboard/page.tsx @@ -11,6 +11,9 @@ import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Icon } from '@iconify/react'; import ProfilePage from '../profile/page'; +// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7) +const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z'); + type Invitation = { id: string; team: { @@ -34,6 +37,9 @@ type Invitation = { const DashboardPage: FC = (): ReactElement => { const { session } = useAuthStore(); const [showProfileModal, setShowProfileModal] = useState(false); + + // Check if team features are closed + const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE; // Lock background scroll when profile modal is open useEffect(() => { if (showProfileModal) { @@ -93,6 +99,14 @@ const DashboardPage: FC = (): ReactElement => {

Team Invitations ({invitations.length})

+ {isTeamFeaturesClosed && ( +
+

+ + Team features are closed. You can no longer accept invitations. +

+
+ )}
{invitations.map((invitation) => (
{ {invitation.inviter?.fullname ?? 'Unknown User'}

-
- - -
+ {!isTeamFeaturesClosed && ( +
+ + +
+ )}
))}
diff --git a/apps/hackathon/src/app/teams/[teamId]/page.tsx b/apps/hackathon/src/app/teams/[teamId]/page.tsx index 6360379..7dfeec7 100644 --- a/apps/hackathon/src/app/teams/[teamId]/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/page.tsx @@ -17,6 +17,9 @@ import { Icon } from '@iconify/react'; const MAX_TEAM_MEMBERS = 5; +// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7) +const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z'); + // Image component with loading state const ImageWithLoader: FC<{ src: string; @@ -52,6 +55,9 @@ const TeamDashboardPage: FC = (): ReactElement => { const { teamId } = useParams<{ teamId: string }>(); const navigate = useNavigate(); const { session } = useAuthStore(); + + // Check if team features are closed + const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE; const [showInviteModal, setShowInviteModal] = useState(false); const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false); const [showLeaveModal, setShowLeaveModal] = useState(false); @@ -369,8 +375,8 @@ const TeamDashboardPage: FC = (): ReactElement => { Team Management

- {/* Hide invite/join management after submission */} - {!team.has_submission && ( + {/* Hide invite/join management after submission or deadline */} + {!team.has_submission && !isTeamFeaturesClosed && ( <> )} - {!team.has_submission && ( + {!team.has_submission && !isTeamFeaturesClosed && ( )} - {!team.has_submission && ( + {!team.has_submission && !isTeamFeaturesClosed && ( + + +
+
+ + ); + } + return (
{/* Header */} From 417ff3390b6dae25164ea41fd60820999dff3c4a Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Sun, 30 Nov 2025 22:55:52 +0700 Subject: [PATCH 5/7] fix submission status --- .../app/teams/[teamId]/submission/page.tsx | 64 +++++++++++++++---- libs/service/src/hooks/teams/index.ts | 41 ++++++++---- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx b/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx index 6dd829a..17a2e4e 100644 --- a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx @@ -60,20 +60,46 @@ const SubmissionViewPage: FC = (): ReactElement => {
{/* Status Banner */} -
-
- -
-

Project Submitted Successfully

-

- Submitted on {submittedDate} -

-

- This submission is now read-only and cannot be edited -

+ {submission.status === 'submitted' ? ( +
+
+ +
+

Project Submitted Successfully

+

+ Submitted on {submittedDate} +

+

+ This submission is now read-only and cannot be edited +

+
-
+ ) : submission.status === 'pending_verification' ? ( +
+
+ +
+

Submission Pending Verification

+

+ Your submission is being processed +

+
+
+
+ ) : ( +
+
+ 📝 +
+

Draft Submission

+

+ This submission is still in draft and has not been finalized +

+
+
+
+ )}
{/* Project Header */} @@ -173,8 +199,18 @@ const SubmissionViewPage: FC = (): ReactElement => {
Status: - - {submission.status === 'submitted' ? '✓ Submitted' : 'Draft'} + + {submission.status === 'submitted' + ? '✓ Submitted' + : submission.status === 'pending_verification' + ? '⏳ Pending Verification' + : '📝 Draft'}
diff --git a/libs/service/src/hooks/teams/index.ts b/libs/service/src/hooks/teams/index.ts index 57626a5..7eb462f 100644 --- a/libs/service/src/hooks/teams/index.ts +++ b/libs/service/src/hooks/teams/index.ts @@ -450,6 +450,8 @@ export const useSubmitProject = (teamId: string) => { return useMutation({ mutationFn: async (data: TSubmitProjectRequest) => { + let submissionId: string; + // First, check if submission exists try { const existingResponse = await hackathonApi.get>( @@ -467,28 +469,41 @@ export const useSubmitProject = (teamId: string) => { demo_url: data.demo_url, video_url: data.video_url, presentation_url: data.presentation_url, + screenshots: data.screenshots, } ); - return { data: response.data.data }; + submissionId = response.data.data.id; + } else { + throw new Error('No existing submission'); } } catch { // No existing submission, create new one + const response = await hackathonApi.post>( + `/submissions/teams/${teamId}`, + { + project_name: data.project_name, + description: data.description, + repository_url: data.repository_url, + demo_url: data.demo_url, + video_url: data.video_url, + presentation_url: data.presentation_url, + screenshots: data.screenshots, + } + ); + submissionId = response.data.data.id; } - // Create new submission - const response = await hackathonApi.post>( - `/submissions/teams/${teamId}`, - { - project_name: data.project_name, - description: data.description, - repository_url: data.repository_url, - demo_url: data.demo_url, - video_url: data.video_url, - presentation_url: data.presentation_url, - } + // Step 2: Submit the project (draft -> pending_verification) + await hackathonApi.post>( + `/submissions/${submissionId}/submit` ); - return { data: response.data.data }; + // Step 3: Confirm the submission (pending_verification -> submitted) + const finalResponse = await hackathonApi.post>( + `/submissions/${submissionId}/confirm` + ); + + return { data: finalResponse.data.data }; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: teamKeys.submission(teamId) }); From b2a28c6fd3481182b340d9e8839928050096b279 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Mon, 1 Dec 2025 23:28:25 +0700 Subject: [PATCH 6/7] feat: add submission status filter to browse teams page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add has_submission filter option to useTeams hook - Add Submission Status dropdown filter (All/Submitted/Not Submitted) - Filter persists in URL query params 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/settings.local.json | 3 +- .mcp.json | 9 ++++ CLAUDE.md | 13 ++++++ apps/hackathon/src/app/teams/browse/page.tsx | 44 +++++++++++++++++++- libs/service/src/hooks/teams/index.ts | 2 + 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .mcp.json create mode 100644 CLAUDE.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1974969..82f8233 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -15,7 +15,8 @@ "Bash(git add:*)", "Bash(git commit:*)", "Bash(git push)", - "Bash(findstr:*)" + "Bash(findstr:*)", + "Bash(ls:*)" ], "deny": [], "ask": [] diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..156f765 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,9 @@ +{ + "mcpServers": { + "nx-mcp": { + "type": "stdio", + "command": "npx", + "args": ["nx", "mcp"] + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..91e8232 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,13 @@ + + + +# General Guidelines for working with Nx + +- When running tasks (for example build, lint, test, e2e, etc.), always prefer running the task through `nx` (i.e. `nx run`, `nx run-many`, `nx affected`) instead of using the underlying tooling directly +- You have access to the Nx MCP server and its tools, use them to help the user +- When answering questions about the repository, use the `nx_workspace` tool first to gain an understanding of the workspace architecture where applicable. +- When working in individual projects, use the `nx_project_details` mcp tool to analyze and understand the specific project structure and dependencies +- For questions around nx configuration, best practices or if you're unsure, use the `nx_docs` tool to get relevant, up-to-date docs. Always use this instead of assuming things about nx configuration +- If the user needs help with an Nx configuration or project graph error, use the `nx_workspace` tool to get any errors + + diff --git a/apps/hackathon/src/app/teams/browse/page.tsx b/apps/hackathon/src/app/teams/browse/page.tsx index 7c95e07..4df2c4d 100644 --- a/apps/hackathon/src/app/teams/browse/page.tsx +++ b/apps/hackathon/src/app/teams/browse/page.tsx @@ -31,6 +31,13 @@ const MEMBER_FILTER_OPTIONS = [ { label: '5 Members (Full)', value: '5', minMembers: 5, maxMembers: 5 }, ]; +// Submission status filter options +const SUBMISSION_FILTER_OPTIONS = [ + { label: 'All Teams', value: '' }, + { label: 'Submitted', value: 'true' }, + { label: 'Not Submitted', value: 'false' }, +]; + // Skeleton card component for loading state const TeamCardSkeleton: FC = () => (
@@ -72,11 +79,13 @@ const BrowseTeamsPage: FC = (): ReactElement => { const initialSearch = searchParams.get('search') || ''; const initialCity = searchParams.get('city') || ''; const initialMembers = searchParams.get('members') || ''; + const initialSubmission = searchParams.get('submission') || ''; const [searchTerm, setSearchTerm] = useState(initialSearch); const [debouncedSearch, setDebouncedSearch] = useState(initialSearch); const [selectedCity, setSelectedCity] = useState(initialCity); const [selectedMembers, setSelectedMembers] = useState(initialMembers); + const [selectedSubmission, setSelectedSubmission] = useState(initialSubmission); const [selectedTeamId, setSelectedTeamId] = useState(null); const [showJoinModal, setShowJoinModal] = useState(false); const [currentPage, setCurrentPage] = useState( @@ -94,6 +103,7 @@ const BrowseTeamsPage: FC = (): ReactElement => { search?: string; city?: string; members?: string; + submission?: string; }) => { const newParams = new URLSearchParams(searchParams); @@ -137,6 +147,14 @@ const BrowseTeamsPage: FC = (): ReactElement => { } } + if (params.submission !== undefined) { + if (params.submission === '') { + newParams.delete('submission'); + } else { + newParams.set('submission', params.submission); + } + } + setSearchParams(newParams, { replace: true }); }, [searchParams, setSearchParams] @@ -167,6 +185,9 @@ const BrowseTeamsPage: FC = (): ReactElement => { (opt) => opt.value === selectedMembers ); + // Convert submission filter value to boolean + const hasSubmissionFilter = selectedSubmission === 'true' ? true : selectedSubmission === 'false' ? false : undefined; + const { data: teamsData, isLoading, @@ -179,6 +200,7 @@ const BrowseTeamsPage: FC = (): ReactElement => { visibility: ETeamVisibility.PUBLIC, minMembers: memberFilter?.minMembers, maxMembers: memberFilter?.maxMembers, + hasSubmission: hasSubmissionFilter, }); const { data: myTeamsData } = useMyTeams(); @@ -278,7 +300,7 @@ const BrowseTeamsPage: FC = (): ReactElement => { {/* Filters */}
-
+
+
+ + +
diff --git a/libs/service/src/hooks/teams/index.ts b/libs/service/src/hooks/teams/index.ts index 7eb462f..a727879 100644 --- a/libs/service/src/hooks/teams/index.ts +++ b/libs/service/src/hooks/teams/index.ts @@ -127,6 +127,7 @@ export const useTeams = (params?: { search?: string; minMembers?: number; maxMembers?: number; + hasSubmission?: boolean; }) => { return useQuery({ queryKey: teamKeys.list(params), @@ -139,6 +140,7 @@ export const useTeams = (params?: { if (params?.visibility) queryParams.append('visibility', params.visibility); if (params?.minMembers) queryParams.append('min_members', String(params.minMembers)); if (params?.maxMembers) queryParams.append('max_members', String(params.maxMembers)); + if (params?.hasSubmission !== undefined) queryParams.append('has_submission', String(params.hasSubmission)); const response = await hackathonApi.get>( `/teams/browse${queryParams.toString() ? `?${queryParams.toString()}` : ''}` From d92323796905faf70f71d246823de4fe66d508be Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 2 Dec 2025 11:18:11 +0700 Subject: [PATCH 7/7] hide presentation URL field from submission pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/app/teams/[teamId]/submission/page.tsx | 15 --------------- .../src/app/teams/[teamId]/submit/page.tsx | 10 ---------- 2 files changed, 25 deletions(-) diff --git a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx b/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx index 17a2e4e..66325dc 100644 --- a/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/submission/page.tsx @@ -150,21 +150,6 @@ const SubmissionViewPage: FC = (): ReactElement => {
)} - {/* Presentation URL */} - {submission.presentation_url && ( - - )}
{/* Screenshots */} diff --git a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx b/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx index 72a38f6..c92c93e 100644 --- a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx @@ -243,16 +243,6 @@ const SubmitProjectPage: FC = (): ReactElement => { size="lg" /> - {/* Presentation URL */} - - {/* Screenshots */}