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/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 ( 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 */} 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..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()}` : ''}` @@ -434,8 +436,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, }); @@ -447,6 +452,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>( @@ -464,28 +471,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) }); @@ -555,8 +575,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, });