feat(hackathon): improve team member count display and submission verification

- Fix browse teams page to use member_count from API response
- Add member_count field to TTeamItem type
- Add minimum team members requirement (2) for project submission
- Add type-to-confirm verification before final submission

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Maulana Sodiqin
2025-11-28 18:15:34 +07:00
co-authored by Claude
parent fa30849d03
commit e1f13b3872
3 changed files with 52 additions and 7 deletions
@@ -14,11 +14,14 @@ import {
} from '@imphnen-frontend-service/service'; } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
const MIN_TEAM_MEMBERS = 2; // Minimum members required to submit (including leader)
const SubmitProjectPage: FC = (): ReactElement => { const SubmitProjectPage: FC = (): ReactElement => {
const { teamId } = useParams<{ teamId: string }>(); const { teamId } = useParams<{ teamId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const { session } = useAuthStore(); const { session } = useAuthStore();
const [showConfirmModal, setShowConfirmModal] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false);
const [confirmText, setConfirmText] = useState('');
const [screenshots, setScreenshots] = useState<string[]>([]); const [screenshots, setScreenshots] = useState<string[]>([]);
const { data: teamData } = useTeamById(teamId || ''); const { data: teamData } = useTeamById(teamId || '');
@@ -31,6 +34,8 @@ const SubmitProjectPage: FC = (): ReactElement => {
const currentUserId = session?.user?.id; const currentUserId = session?.user?.id;
const isLeader = currentUserId === team?.leader_id; const isLeader = currentUserId === team?.leader_id;
const hasSubmission = !!submissionData?.data; const hasSubmission = !!submissionData?.data;
const memberCount = team?.members?.length || 0;
const hasEnoughMembers = memberCount >= MIN_TEAM_MEMBERS;
const form = useForm<TProjectSubmissionForm>({ const form = useForm<TProjectSubmissionForm>({
resolver: zodResolver(projectSubmissionSchema), resolver: zodResolver(projectSubmissionSchema),
@@ -122,6 +127,25 @@ const SubmitProjectPage: FC = (): ReactElement => {
</div> </div>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Minimum Members Warning */}
{!hasEnoughMembers && (
<div className="bg-amber-50 dark:bg-amber-900/20 border-2 border-amber-500 rounded-lg p-6 mb-6">
<div className="flex items-start space-x-3">
<span className="text-3xl">👥</span>
<div>
<h3 className="font-bold text-amber-900 dark:text-amber-100 text-lg">
Team Members Required
</h3>
<p className="text-amber-800 dark:text-amber-200 mt-2 text-sm font-sans">
Your team needs at least <strong>{MIN_TEAM_MEMBERS} members</strong> to submit a project.
Currently you have <strong>{memberCount} member{memberCount !== 1 ? 's' : ''}</strong>.
Please invite more members to your team before submitting.
</p>
</div>
</div>
</div>
)}
{/* Warning Banner */} {/* Warning Banner */}
<div className="bg-red-50 dark:bg-red-900/20 border-2 border-red-500 rounded-lg p-6 mb-6"> <div className="bg-red-50 dark:bg-red-900/20 border-2 border-red-500 rounded-lg p-6 mb-6">
<div className="flex items-start space-x-3"> <div className="flex items-start space-x-3">
@@ -274,11 +298,16 @@ const SubmitProjectPage: FC = (): ReactElement => {
<Button <Button
type="submit" type="submit"
className="flex-1" className="flex-1"
disabled={!form.formState.isValid || isUploading} disabled={!form.formState.isValid || isUploading || !hasEnoughMembers}
> >
Review & Submit Review & Submit
</Button> </Button>
</div> </div>
{!hasEnoughMembers && (
<p className="text-center text-sm text-amber-600 dark:text-amber-400 mt-2">
You need at least {MIN_TEAM_MEMBERS} team members to submit
</p>
)}
</form> </form>
</div> </div>
</div> </div>
@@ -298,7 +327,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
</div> </div>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 mb-6"> <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 mb-6">
<p className="text-sm text-gray-700 dark:text-gray-300 mb-3"> <p className="text-sm text-gray-700 dark:text-gray-300 mb-3">
By clicking "Submit Project", you confirm that: By submitting, you confirm that:
</p> </p>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-2"> <ul className="text-sm text-gray-600 dark:text-gray-400 space-y-2">
<li> All information is correct and complete</li> <li> All information is correct and complete</li>
@@ -307,19 +336,35 @@ const SubmitProjectPage: FC = (): ReactElement => {
<li> Your team agrees with this submission</li> <li> Your team agrees with this submission</li>
</ul> </ul>
</div> </div>
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Type <span className="font-bold text-red-600 dark:text-red-400">SUBMIT</span> to confirm:
</label>
<input
type="text"
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
placeholder="Type SUBMIT here"
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500"
autoComplete="off"
/>
</div>
<div className="flex space-x-3"> <div className="flex space-x-3">
<Button <Button
type="button" type="button"
variant="secondary" variant="secondary"
className="flex-1" className="flex-1"
onClick={() => setShowConfirmModal(false)} onClick={() => {
setShowConfirmModal(false);
setConfirmText('');
}}
> >
Go Back Go Back
</Button> </Button>
<Button <Button
onClick={onSubmit} onClick={onSubmit}
className="flex-1 bg-red-600 hover:bg-red-700" className="flex-1 bg-red-600 hover:bg-red-700"
disabled={isSubmitting} disabled={isSubmitting || confirmText !== 'SUBMIT'}
> >
{isSubmitting ? 'Submitting...' : 'Submit Project'} {isSubmitting ? 'Submitting...' : 'Submit Project'}
</Button> </Button>
+2 -2
View File
@@ -209,7 +209,7 @@ const BrowseTeamsPage: FC = (): ReactElement => {
</p> </p>
<p className="whitespace-nowrap shrink-0 flex items-center gap-x-1"> <p className="whitespace-nowrap shrink-0 flex items-center gap-x-1">
<Icon icon="mdi:account-group" />{' '} <Icon icon="mdi:account-group" />{' '}
{team.members?.length || 0} members {team.member_count || 0} members
</p> </p>
</div> </div>
</div> </div>
@@ -229,7 +229,7 @@ const BrowseTeamsPage: FC = (): ReactElement => {
) : ( ) : (
<> <>
{myTeams.length === 0 && {myTeams.length === 0 &&
(team.members?.length || 0) < 5 && ( (team.member_count || 0) < 5 && (
<Button <Button
className="w-full" className="w-full"
onClick={() => handleJoinRequest(team.id)} onClick={() => handleJoinRequest(team.id)}
+1 -1
View File
@@ -36,6 +36,7 @@ export type TTeamItem = {
city: string; city: string;
visibility: ETeamVisibility; visibility: ETeamVisibility;
leader_id: string; leader_id: string;
member_count: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
}; };
@@ -43,7 +44,6 @@ export type TTeamItem = {
export type TTeamDetailItem = TTeamItem & { export type TTeamDetailItem = TTeamItem & {
leader: TUserItem; leader: TUserItem;
members: TTeamMemberItem[]; members: TTeamMemberItem[];
member_count: number;
has_submission: boolean; has_submission: boolean;
}; };