diff --git a/apps/mobile/src/features/club/screens/ClubInfoEditScreen/index.tsx b/apps/mobile/src/features/club/screens/ClubInfoEditScreen/index.tsx
index c791382c..808b487a 100644
--- a/apps/mobile/src/features/club/screens/ClubInfoEditScreen/index.tsx
+++ b/apps/mobile/src/features/club/screens/ClubInfoEditScreen/index.tsx
@@ -260,14 +260,15 @@ SuccessModal.displayName = "SuccessModal";
type ClubCategorySectionProps = {
category: ClubInfoEditFormData["category"];
+ isMissing: boolean;
onChange: (category: ClubInfoEditFormData["category"]) => void;
};
const ClubCategorySection = memo(
- ({ category, onChange }: ClubCategorySectionProps) => {
+ ({ category, isMissing, onChange }: ClubCategorySectionProps) => {
return (
- 카테고리
+ *카테고리
{CLUB_CATEGORIES.map((categoryOption) => {
const isSelected = category === categoryOption;
@@ -292,6 +293,9 @@ const ClubCategorySection = memo(
);
})}
+ {isMissing && (
+ 카테고리는 필수 입력 정보예요
+ )}
);
},
@@ -301,14 +305,15 @@ ClubCategorySection.displayName = "ClubCategorySection";
type ClubRecruitTypeSectionProps = {
recruitType: string;
+ isMissing: boolean;
onChange: (recruitType: string) => void;
};
const ClubRecruitTypeSection = memo(
- ({ recruitType, onChange }: ClubRecruitTypeSectionProps) => {
+ ({ recruitType, isMissing, onChange }: ClubRecruitTypeSectionProps) => {
return (
- 모집 형태
+ *모집 형태
{CLUB_RECRUIT_TYPES.map((recruitTypeOption) => {
const isSelected = recruitType === recruitTypeOption;
@@ -333,6 +338,11 @@ const ClubRecruitTypeSection = memo(
);
})}
+ {isMissing && (
+
+ 모집 형태는 필수 입력 정보예요
+
+ )}
);
},
@@ -497,6 +507,11 @@ type ClubBasicInfoSectionProps = {
department: string;
shortIntro: string;
showDepartmentDropdown: boolean;
+ isImageMissing: boolean;
+ isNameMissing: boolean;
+ isBasicInfoMissing: boolean;
+ isDepartmentMissing: boolean;
+ isShortIntroMissing: boolean;
onSelectImage: () => void;
onClubNameChange: (value: string) => void;
onToggleDepartmentDropdown: () => void;
@@ -510,6 +525,11 @@ const ClubBasicInfoSection = memo(
department,
shortIntro,
showDepartmentDropdown,
+ isImageMissing,
+ isNameMissing,
+ isBasicInfoMissing,
+ isDepartmentMissing,
+ isShortIntroMissing,
onSelectImage,
onClubNameChange,
onToggleDepartmentDropdown,
@@ -517,11 +537,17 @@ const ClubBasicInfoSection = memo(
}: ClubBasicInfoSectionProps) => (
<>
- 동아리 대표 이미지
+ *동아리 대표 이미지
4X4 정방형 이미지를 권장드려요
-
+
{clubImage ? (
) : (
@@ -531,25 +557,40 @@ const ClubBasicInfoSection = memo(
이미지 변경
+ {isImageMissing && (
+
+ 대표 이미지는 필수 입력 정보예요
+
+ )}
- 동아리명
+ *동아리명
+ {isNameMissing && (
+ 동아리명은 필수 입력 정보예요
+ )}
- 한줄소개
+ *한줄소개
@@ -566,11 +607,23 @@ const ClubBasicInfoSection = memo(
+ {isBasicInfoMissing && (
+
+ 동아리 한줄소개는 필수 입력 정보예요
+
+ )}
>
),
@@ -580,23 +633,28 @@ ClubBasicInfoSection.displayName = "ClubBasicInfoSection";
type ClubDescriptionSectionProps = {
description: string;
+ isMissing: boolean;
onChange: (value: string) => void;
};
const ClubDescriptionSection = memo(
- ({ description, onChange }: ClubDescriptionSectionProps) => (
+ ({ description, isMissing, onChange }: ClubDescriptionSectionProps) => (
- 동아리 추가 설명
- *동아리 추가 설명
+
+ {isMissing && (
+
+ 동아리 추가 설명은 필수 입력 정보예요
+
+ )}
),
);
@@ -912,15 +970,24 @@ const ClubInfoEditScreen = () => {
JSON.stringify(initialComparable?.snsUrls) ||
areValidSnsUrls(formData.clubSNSUrls);
+ const isImageMissing = !formData.clubImage;
+ const isNameMissing = !formData.clubName.trim();
+ const isDepartmentMissing = !formData.department.trim();
+ const isShortIntroMissing = !formData.shortIntro.trim();
+ const isBasicInfoMissing = isDepartmentMissing || isShortIntroMissing;
+ const isCategoryMissing = !formData.category;
+ const isRecruitTypeMissing = !formData.recruitType.trim();
+ const isSnsMissing = !isSNSComplete;
+ const isDescriptionMissing = !formData.clubDescription.trim();
+
const isComplete =
- !!formData.clubImage &&
- !!formData.clubName.trim() &&
- !!formData.category &&
- !!formData.department.trim() &&
- !!formData.shortIntro.trim() &&
- !!formData.recruitType.trim() &&
+ !isImageMissing &&
+ !isNameMissing &&
+ !isCategoryMissing &&
+ !isBasicInfoMissing &&
+ !isRecruitTypeMissing &&
isSNSComplete &&
- !!formData.clubDescription.trim() &&
+ !isDescriptionMissing &&
(minActivityPeriodMode === "none" ||
!!formData.minActivityPeriodInput.trim());
@@ -1061,6 +1128,11 @@ const ClubInfoEditScreen = () => {
department={formData.department}
shortIntro={formData.shortIntro}
showDepartmentDropdown={showDepartmentDropdown}
+ isImageMissing={isImageMissing}
+ isNameMissing={isNameMissing}
+ isBasicInfoMissing={isBasicInfoMissing}
+ isDepartmentMissing={isDepartmentMissing}
+ isShortIntroMissing={isShortIntroMissing}
onSelectImage={handleSelectImage}
onClubNameChange={handleClubNameChange}
onToggleDepartmentDropdown={handleToggleDepartmentDropdown}
@@ -1069,11 +1141,13 @@ const ClubInfoEditScreen = () => {
@@ -1095,17 +1169,21 @@ const ClubInfoEditScreen = () => {
-
-
+
+
void;
+ required?: boolean;
+ isMissing?: boolean;
+ missingText?: string;
};
-const ClubSnsInputList = ({ urls, onChange }: Props) => {
+const DEFAULT_HELPER_TEXT = "SNS 링크를 1개 이상 입력해주세요 (최대 3개)";
+
+const ClubSnsInputList = ({
+ urls,
+ onChange,
+ required = false,
+ isMissing = true,
+ missingText = DEFAULT_HELPER_TEXT,
+}: Props) => {
+ const showError = required && isMissing;
+
const updateUrl = (index: number, value: string) => {
onChange(urls.map((url, urlIndex) => (urlIndex === index ? value : url)));
};
@@ -32,7 +45,7 @@ const ClubSnsInputList = ({ urls, onChange }: Props) => {
return (
- 동아리 SNS
+ {required ? "*" : ""}동아리 SNS
{urls.map((url, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: The index is the row identity while editable URLs may be empty or duplicated.
@@ -42,7 +55,7 @@ const ClubSnsInputList = ({ urls, onChange }: Props) => {
color={Colors.BODYTEXT_DISABLED}
/>
{
SNS 링크 추가
)}
-
- SNS 링크를 1개 이상 입력해주세요 (최대 3개)
-
+ {isMissing && {missingText}}
);
};
@@ -95,6 +106,7 @@ const styles = StyleSheet.create({
fieldLabel: {
...typography.headerXLSemibold,
color: Colors.BODYTEXT_SUB,
+ paddingLeft: s(5),
},
inputRow: {
flexDirection: "row",
@@ -112,6 +124,9 @@ const styles = StyleSheet.create({
borderRadius: 8,
backgroundColor: Colors.WHITE,
},
+ inputError: {
+ borderColor: Colors.BUTTON_DESTRUCTIVE,
+ },
removeButton: {
width: ms(32),
height: ms(32),