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:
co-authored by
Claude
parent
fa30849d03
commit
e1f13b3872
@@ -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>
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user