From 692a7a7258e1095a2801d44780019d665b778513 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Fri, 28 Nov 2025 19:54:48 +0700 Subject: [PATCH] feat: add size warning --- .../src/app/onboarding/user/page.tsx | 16 +++- apps/hackathon/src/app/profile/page.tsx | 10 ++- .../src/app/teams/[teamId]/edit/page.tsx | 70 +++++++++------- .../src/app/teams/[teamId]/submit/page.tsx | 13 ++- apps/hackathon/src/app/teams/create/page.tsx | 79 +++++++++++++------ 5 files changed, 129 insertions(+), 59 deletions(-) diff --git a/apps/hackathon/src/app/onboarding/user/page.tsx b/apps/hackathon/src/app/onboarding/user/page.tsx index a21b308..9d4c641 100644 --- a/apps/hackathon/src/app/onboarding/user/page.tsx +++ b/apps/hackathon/src/app/onboarding/user/page.tsx @@ -60,6 +60,20 @@ const UserOnboardingPage: FC = (): ReactElement => { const handleAvatarChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + // Validate file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + toast.error('The file is too large. Maximum size is 2MB.'); + e.target.value = ''; + return; + } + + // Validate file type + if (!file.type.startsWith('image/')) { + toast.error('The file must be an image'); + e.target.value = ''; + return; + } + setAvatarFile(file); const reader = new FileReader(); reader.onloadend = () => { @@ -150,7 +164,7 @@ const UserOnboardingPage: FC = (): ReactElement => { />

- Optional, but highly recommended + Optional, but highly recommended. Max 2MB

diff --git a/apps/hackathon/src/app/profile/page.tsx b/apps/hackathon/src/app/profile/page.tsx index 04fc242..f8f107f 100644 --- a/apps/hackathon/src/app/profile/page.tsx +++ b/apps/hackathon/src/app/profile/page.tsx @@ -84,15 +84,17 @@ const ProfilePage: FC = ({ const handleAvatarChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { - // Validate file size (max 5MB) - if (file.size > 5 * 1024 * 1024) { - toast.error('The file is too large. Maximum 5MB'); + // Validate file size (max 2MB) + if (file.size > 2 * 1024 * 1024) { + toast.error('The file is too large. Maximum size is 2MB.'); + e.target.value = ''; return; } // Validate file type if (!file.type.startsWith('image/')) { toast.error('The file must be an image'); + e.target.value = ''; return; } @@ -235,7 +237,7 @@ const ProfilePage: FC = ({

Click the camera icon to change your photo
- Format: JPG, PNG. Max 5MB + Format: JPG, PNG. Max 2MB

diff --git a/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx b/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx index a6170f0..6b7441b 100644 --- a/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/edit/page.tsx @@ -16,6 +16,9 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { CitySelect } from '../../../../components/city-select'; import { Icon } from '@iconify/react'; +import { toast } from 'sonner'; + +const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB const EditTeamPage: FC = (): ReactElement => { const { teamId } = useParams<{ teamId: string }>(); @@ -85,6 +88,11 @@ const EditTeamPage: FC = (): ReactElement => { const handleLogoChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + if (file.size > MAX_FILE_SIZE) { + toast.error('Logo image is too large. Maximum size is 2MB.'); + e.target.value = ''; + return; + } setLogoFile(file); const reader = new FileReader(); reader.onloadend = () => { @@ -97,6 +105,11 @@ const EditTeamPage: FC = (): ReactElement => { const handleBannerChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + if (file.size > MAX_FILE_SIZE) { + toast.error('Banner image is too large. Maximum size is 2MB.'); + e.target.value = ''; + return; + } setBannerFile(file); const reader = new FileReader(); reader.onloadend = () => { @@ -183,7 +196,7 @@ const EditTeamPage: FC = (): ReactElement => { Click to upload banner

- 1200x400 recommended + 1200x400 recommended. Max 2MB

{ )} -
- - {logoPreview && ( - - )} +
+
+ + {logoPreview && ( + + )} +
+

Max 2MB

diff --git a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx b/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx index a72227a..72a38f6 100644 --- a/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx +++ b/apps/hackathon/src/app/teams/[teamId]/submit/page.tsx @@ -13,8 +13,10 @@ import { useAuthStore, } from '@imphnen-frontend-service/service'; import { zodResolver } from '@hookform/resolvers/zod'; +import { toast } from 'sonner'; const MIN_TEAM_MEMBERS = 2; // Minimum members required to submit (including leader) +const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB const SubmitProjectPage: FC = (): ReactElement => { const { teamId } = useParams<{ teamId: string }>(); @@ -89,6 +91,14 @@ const SubmitProjectPage: FC = (): ReactElement => { const files = e.target.files; if (!files) return; + // Validate file sizes + const oversizedFiles = Array.from(files).filter(file => file.size > MAX_FILE_SIZE); + if (oversizedFiles.length > 0) { + toast.error(`${oversizedFiles.length} file(s) are too large. Maximum size is 2MB per file.`); + e.target.value = ''; + return; + } + try { const uploadPromises = Array.from(files).map((file) => uploadFile(file)); const results = await Promise.all(uploadPromises); @@ -96,6 +106,7 @@ const SubmitProjectPage: FC = (): ReactElement => { setScreenshots([...screenshots, ...urls]); } catch (error) { console.error('Failed to upload screenshots:', error); + toast.error('Failed to upload screenshots. Please try again.'); } }; @@ -271,7 +282,7 @@ const SubmitProjectPage: FC = (): ReactElement => { Click to upload screenshots

- PNG, JPG up to 5MB each + PNG, JPG. Max 2MB each

{ const navigate = useNavigate(); const [logoFile, setLogoFile] = useState(null); @@ -31,6 +34,11 @@ const CreateTeamPage: FC = (): ReactElement => { const handleLogoChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + if (file.size > MAX_FILE_SIZE) { + toast.error('Logo image is too large. Maximum size is 2MB.'); + e.target.value = ''; + return; + } setLogoFile(file); const reader = new FileReader(); reader.onloadend = () => { @@ -43,6 +51,11 @@ const CreateTeamPage: FC = (): ReactElement => { const handleBannerChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + if (file.size > MAX_FILE_SIZE) { + toast.error('Banner image is too large. Maximum size is 2MB.'); + e.target.value = ''; + return; + } setBannerFile(file); const reader = new FileReader(); reader.onloadend = () => { @@ -73,9 +86,20 @@ const CreateTeamPage: FC = (): ReactElement => { banner: bannerUrl, }); + toast.success('Team created successfully!'); navigate(`/teams/${result.data.id}`); - } catch (error) { + } catch (error: any) { console.error('Failed to create team:', error); + + // Handle specific error messages + const message = error?.message || ''; + if (message.includes('413') || message.includes('length limit') || message.includes('too large')) { + toast.error('Image file is too large. Please use smaller images (max 2MB each).'); + } else if (message.includes('already a member')) { + toast.error(message); + } else { + toast.error(message || 'Failed to create team. Please try again.'); + } } }); @@ -119,7 +143,7 @@ const CreateTeamPage: FC = (): ReactElement => {