Repository: team-da-sh/dash-client
Pull request #455: [Refactor] 2차 QA 수정 반영
Author: login: heesunee
Labels: name: 🪄 Refactor
description: 코드 리팩토링
name: 희선
description: 자중해
Created: 2025-05-26T14:45:31Z | Merged: 2025-05-27T12:24:09Z

## 📌 Related Issues
- close #442 

## ✅ 체크 리스트 
- [x] PR 제목의 형식을 잘 작성했나요? e.g. [Feat] PR 템플릿 작성
- [x] 빌드가 성공했나요? (pnpm build)
- [x] 리뷰어와 라벨을 지정했나요?

## 📄 Tasks
- 프로필 기본 이미지 png -> svg
- 내정보 수정 유효성 관련 피그마에 맞게 수정
- 내정보수정 전화번호 숫자만 입력할 수 있게 수정
- 텍스트 필드 focus 관련 문제 해결 
- 유효성 검사시 첫글자 인식 안되는거 해결

## ⭐ PR Point 
```ts
  name: z.string().superRefine((val, ctx) => {
    if (val.length === 0) {
      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.REQUIRED });
      return;
    }
    if (!/^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/.test(val)) {
      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.INVALID });
      return;
    }
    if (val.length < MIN_NAME_LENGTH) {
      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.REQUIRED });
      return;
    }
    if (val.length > MAX_NAME_LENGTH) {
      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.TOO_LONG });
      return;
    }
  }),
```

닉네임이나 이름 입력 시, 최소 길이와 특수기호 및 공백에 대한 유효성 검사가 겹치면서 에러 메시지가 제대로 출력되지 않는 문제가 있었습니다.
예를 들어, 첫 글자가 띄어쓰기일 경우 이를 인식하지 못했고,
첫 글자가 한 글자만 입력된 경우에는 닉네임 최소 길이 에러 메시지만 출력되었으며,
두 글자 이상부터는 특수기호나 띄어쓰기 여부를 검사하는 로직이 작동했습니다.
이 문제를 해결하기 위해 superRefine 기능을 활용하여 입력값의 상태에 따라 상황별로 적절한 에러 메시지를 개별적으로 처리하도록 구현했습니다.

### 내정보 수정 전화번호 필드 숫자만 받게 수정
React Hook Form은 내부적으로 onChange, onBlur 이벤트를 통해 값을 수직하고 validation을 실행합니다. 
처음에는 정규식(숫자)에 맞지 않으면 value 업데이트 자체를 막는 방식으로 가려고 했었습니다.
```ts
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = e.target.value;
  if (/^\d*$/.test(value)) {
    setValue('phoneNumber', value); // react-hook-form 값 업데이트
  }
};
```

대충 위와 같은 식으로 가려고 했습니다. 근데 직접 onChange를 넣으면 react hook form의 register 이벤트 흐름을 덮어쓰기 하게 되면서 의도와는 맞지 않는 결과가 발생했습니다. custom onChange를 쓰려면 register만으로는 어렵기 때문에 Controller를  사용해서 제어하면 좋다는데, 일단 그냥 간단하게 onKeyDown과 onPaste를 사용해서 UX 직관적으로 입력 자체를 막았습니다. 

hook form 공부를 좀 더 해야할거 같으네여 ....


### input 내 focus 문제 해결

input 내 불필요한 focus, blur가 있어서 지우고, 상위에서 준걸로 받게 했습니다. (다른 input 클릭시 해제되게 하기 위해서) isFocused 옵션이 누락되어있어서 추가해서 테두리 뜨게 했습니다 !

### 고민

기획한테 질문은 한 상태인데

https://github.com/user-attachments/assets/287dab80-0588-4182-96e2-a42fd6c9427b

> 현재 닉네임에 오류가 발생한 상태에서 네임이나 전화번호 입력란에 포커스를 옮겨도 닉네임의 오류 상태가 그대로 남아 있습니다. 반면,  error가 없는 input 필드간 focus 해제는 잘 됩니다 !
> 
> 이런 경우, 닉네임처럼 오류가 발생한 입력란은 사용자에게 명확한 피드백을 주기 위해 오류 상태를 계속 유지하는 것이 맞는지, 아니면 다른 입력란으로 포커스가 이동하면 오류 상태를 해제하고 확인버튼 을 누르거나 하려고 할때 다시 띄워줘야하는지 .. 흐음 ...

답 왔는데 에러인건 포커스 유무 상관없이 그냥 계속 보여주는게 맞다네여 ~~ 다행이당 ㅎㅎ




## 📷 Screenshot

## 🔔 ETC


Linked issue #442: [Refactor] 마이페이지 내 오류사항 수정
## 🛠 Task Description
[Refactor] 마이페이지 내 오류사항 수정

## 🔔ETC


Diff:
diff --git a/src/pages/editProfiles/components/FormField/FormField.tsx b/src/pages/editProfiles/components/FormField/FormField.tsx
index 945494816..a10f23f7e 100644
--- a/src/pages/editProfiles/components/FormField/FormField.tsx
+++ b/src/pages/editProfiles/components/FormField/FormField.tsx
@@ -1,6 +1,7 @@
 import type { UseFormRegister } from 'react-hook-form';
 import * as styles from '@/pages/editProfiles/components/FormField/formField.css';
 import type { ProfileFormValues } from '@/pages/editProfiles/schema/profileSchema';
+import { allowOnlyNumberKey, allowOnlyNumberPaste } from '@/pages/editProfiles/utils/inputUtils';
 import { MAX_PHONENUMBER_LENGTH, MAX_NAME_LENGTH } from '@/pages/onboarding/constants';
 import Input from '@/shared/components/Input/Input';
 import Text from '@/shared/components/Text/Text';
@@ -26,10 +27,12 @@ const FormField = ({
   error,
   readOnly = false,
   validationMessage,
-  isFocused,
   onFocus,
   onBlur,
+  isFocused,
 }: FormFieldPropTypes) => {
+  const isPhoneNumber = name === 'phoneNumber';
+
   return (
     <div className={styles.fieldWrapperStyle}>
       <label>
@@ -41,8 +44,10 @@ const FormField = ({
         isError={!!error}
         maxLength={name === 'phoneNumber' ? MAX_PHONENUMBER_LENGTH : MAX_NAME_LENGTH}
         readOnly={readOnly}
+        isFocused={isFocused}
         onFocus={onFocus}
         onBlur={onBlur}
+        {...(isPhoneNumber && { inputMode: 'numeric', onKeyDown: allowOnlyNumberKey, onPaste: allowOnlyNumberPaste })}
       />
       <div className={styles.errorMessageStyle({ hasError: !!(error && error.message) })}>
         {error?.message && (
@@ -50,7 +55,7 @@ const FormField = ({
             {error.message}
           </Text>
         )}
-        {isFocused && validationMessage}
+        {validationMessage}
       </div>
     </div>
   );
diff --git a/src/pages/editProfiles/components/ProfileForm/ProfileForm.tsx b/src/pages/editProfiles/components/ProfileForm/ProfileForm.tsx
index f56c0093f..78fc30cfb 100644
--- a/src/pages/editProfiles/components/ProfileForm/ProfileForm.tsx
+++ b/src/pages/editProfiles/components/ProfileForm/ProfileForm.tsx
@@ -41,14 +41,6 @@ const ProfileForm = ({ defaultValues }: ProfileFormPropTypes) => {
     mode: 'onChange',
   });
 
-  const handleFocus = (fieldName: string) => {
-    setFocusedField(fieldName);
-  };
-
-  const handleBlur = () => {
-    setFocusedField(null);
-  };
-
   const { field } = useController({
     name: 'profileImageUrl',
     control,
@@ -98,6 +90,14 @@ const ProfileForm = ({ defaultValues }: ProfileFormPropTypes) => {
     editMyProfile(submitData);
   };
 
+  const handleFocus = (fieldName: string) => {
+    setFocusedField(fieldName);
+  };
+
+  const handleBlur = () => {
+    setFocusedField(null);
+  };
+
   const handleSelectImage = () => {
     handleUploaderClick();
   };
@@ -124,9 +124,9 @@ const ProfileForm = ({ defaultValues }: ProfileFormPropTypes) => {
           onFocus={() => handleFocus('nickname')}
           onBlur={handleBlur}
           validationMessage={
-            <Text
-              tag="b3_r"
-              color={errors.nickname ? 'alert3' : 'main4'}>{`${nickname?.length || 0}/${MAX_NICKNAME_LENGTH}`}</Text>
+            <Text tag="c1_m" color={errors.nickname ? 'alert3' : focusedField === 'nickname' ? 'main4' : 'gray9'}>
+              {`${nickname?.length || 0}/${MAX_NICKNAME_LENGTH}`}
+            </Text>
           }
         />
 
@@ -140,7 +140,9 @@ const ProfileForm = ({ defaultValues }: ProfileFormPropTypes) => {
           onFocus={() => handleFocus('name')}
           onBlur={handleBlur}
           validationMessage={
-            <Text tag="b3_r" color={errors.name ? 'alert3' : 'main4'}>{`${name?.length || 0}/${MAX_NAME_LENGTH}`}</Text>
+            <Text tag="c1_m" color={errors.name ? 'alert3' : focusedField === 'name' ? 'main4' : 'gray9'}>
+              {`${name?.length || 0}/${MAX_NAME_LENGTH}`}
+            </Text>
           }
         />
 
@@ -154,11 +156,9 @@ const ProfileForm = ({ defaultValues }: ProfileFormPropTypes) => {
           onFocus={() => handleFocus('phoneNumber')}
           onBlur={handleBlur}
           validationMessage={
-            <Text
-              tag="b3_r"
-              color={
-                errors.phoneNumber ? 'alert3' : 'main4'
-              }>{`${phoneNumber?.length || 0}/${MAX_PHONENUMBER_LENGTH}`}</Text>
+            <Text tag="c1_m" color={errors.phoneNumber ? 'alert3' : focusedField === 'phoneNumber' ? 'main4' : 'gray9'}>
+              {`${phoneNumber?.length || 0}/${MAX_PHONENUMBER_LENGTH}`}
+            </Text>
           }
         />
       </div>
diff --git a/src/pages/editProfiles/constants/validationMessage.ts b/src/pages/editProfiles/constants/validationMessage.ts
index 8eb833b5d..ec2459a10 100644
--- a/src/pages/editProfiles/constants/validationMessage.ts
+++ b/src/pages/editProfiles/constants/validationMessage.ts
@@ -7,13 +7,13 @@ export const PROFILE_IMAGE_ERRORS = {
 export const NICKNAME_ERRORS = {
   REQUIRED: '댄서네임을 입력해주세요',
   TOO_LONG: '최대 8자까지 입력 가능합니다',
-  INVALID: '특수기호/띄어쓰기는 입력할 수 없어요',
+  INVALID: '특수기호, 띄어쓰기는 입력할 수 없어요',
 };
 
 export const NAME_ERRORS = {
   REQUIRED: '이름을 입력해주세요',
   TOO_LONG: '최대 8자까지 입력 가능합니다',
-  INVALID: '특수기호/띄어쓰기는 입력할 수 없어요',
+  INVALID: '특수기호, 띄어쓰기는 입력할 수 없어요',
 };
 
 export const PHONE_NUMBER_ERRORS = {
diff --git a/src/pages/editProfiles/schema/profileSchema.ts b/src/pages/editProfiles/schema/profileSchema.ts
index 1e2f5d8b4..e714af447 100644
--- a/src/pages/editProfiles/schema/profileSchema.ts
+++ b/src/pages/editProfiles/schema/profileSchema.ts
@@ -30,17 +30,44 @@ export const profileSchema = z.object({
         PROFILE_IMAGE_ERRORS.INVALID_TYPE
       ),
   ]),
-  nickname: z
-    .string()
-    .min(MIN_NICKNAME_LENGTH, NICKNAME_ERRORS.REQUIRED)
-    .max(MAX_NICKNAME_LENGTH, NICKNAME_ERRORS.TOO_LONG)
-    .regex(/^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/, NICKNAME_ERRORS.INVALID),
+  nickname: z.string().superRefine((val, ctx) => {
+    if (val.length === 0) {
+      ctx.addIssue({ code: 'custom', message: NICKNAME_ERRORS.REQUIRED });
+      return;
+    }
+    if (!/^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/.test(val)) {
+      ctx.addIssue({ code: 'custom', message: NICKNAME_ERRORS.INVALID });
+      return;
+    }
+    if (val.length < MIN_NICKNAME_LENGTH) {
+      ctx.addIssue({ code: 'custom', message: NICKNAME_ERRORS.REQUIRED });
+      return;
+    }
+    if (val.length > MAX_NICKNAME_LENGTH) {
+      ctx.addIssue({ code: 'custom', message: NICKNAME_ERRORS.TOO_LONG });
+      return;
+    }
+  }),
+
   phoneNumber: z.string().regex(/^[0-9]\d{10}$/, PHONE_NUMBER_ERRORS.INVALID),
-  name: z
-    .string()
-    .min(MIN_NAME_LENGTH, NAME_ERRORS.REQUIRED)
-    .max(MAX_NAME_LENGTH, NAME_ERRORS.TOO_LONG)
-    .regex(/^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/, NAME_ERRORS.INVALID),
+  name: z.string().superRefine((val, ctx) => {
+    if (val.length === 0) {
+      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.REQUIRED });
+      return;
+    }
+    if (!/^[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]+$/.test(val)) {
+      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.INVALID });
+      return;
+    }
+    if (val.length < MIN_NAME_LENGTH) {
+      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.REQUIRED });
+      return;
+    }
+    if (val.length > MAX_NAME_LENGTH) {
+      ctx.addIssue({ code: 'custom', message: NAME_ERRORS.TOO_LONG });
+      return;
+    }
+  }),
 });
 
 export type ProfileFormValues = z.infer<typeof profileSchema>;
diff --git a/src/pages/editProfiles/utils/inputUtils.ts b/src/pages/editProfiles/utils/inputUtils.ts
new file mode 100644
index 000000000..b198e6f32
--- /dev/null
+++ b/src/pages/editProfiles/utils/inputUtils.ts
@@ -0,0 +1,11 @@
+import type { KeyboardEvent, ClipboardEvent } from 'react';
+
+export const allowOnlyNumberKey = (e: KeyboardEvent<HTMLInputElement>) => {
+  const isNumber =
+    (e.key >= '0' && e.key <= '9') || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
+  if (!isNumber) e.preventDefault();
+};
+export const allowOnlyNumberPaste = (e: ClipboardEvent<HTMLInputElement>) => {
+  const paste = e.clipboardData.getData('text');
+  if (!/^\d+$/.test(paste)) e.preventDefault();
+};
diff --git a/src/pages/instructorRegister/InstructorRegister.tsx b/src/pages/instructorRegister/InstructorRegister.tsx
index 91c064764..45360fe24 100644
--- a/src/pages/instructorRegister/InstructorRegister.tsx
+++ b/src/pages/instructorRegister/InstructorRegister.tsx
@@ -179,12 +179,14 @@ const InstructorRegister = () => {
               </Head>
             </div>
 
-            <ImageUploadSection
-              imgRef={imgRef}
-              previewImg={previewImg}
-              uploadImgFile={uploadImgFile}
-              handleUploaderClick={handleUploaderClick}
-            />
+            <div className={styles.profileImageWrapperStyle}>
+              <ImageUploadSection
+                imgRef={imgRef}
+                previewImg={previewImg}
+                uploadImgFile={uploadImgFile}
+                handleUploaderClick={handleUploaderClick}
+              />
+            </div>
 
             <IntroductionSection register={register} error={errors.detail} detail={detail} />
           </div>
diff --git a/src/pages/instructorRegister/components/ImageUploadSection/ImageUploadSection.tsx b/src/pages/instructorRegister/components/ImageUploadSection/ImageUploadSection.tsx
index 9f1692d7e..c063720f4 100644
--- a/src/pages/instructorRegister/components/ImageUploadSection/ImageUploadSection.tsx
+++ b/src/pages/instructorRegister/components/ImageUploadSection/ImageUploadSection.tsx
@@ -1,6 +1,6 @@
 import * as styles from '@/pages/instructorRegister/components/ImageUploadSection/imageUploadSection.css';
 import IcCameraMain0624 from '@/shared/assets/svg/IcCameraMain0624';
-import defaultProfile from '/images/image_profile_basic.png';
+import IcProfileBasic from '@/shared/assets/svg/IcProfileBasic';
 
 interface ImageUploadSectionPropTypes {
   handleUploaderClick?: () => void;
@@ -21,8 +21,9 @@ const ImageUploadSection = ({
     <section className={styles.containerStyle} onClick={onClick}>
       <div
         className={styles.previewImgStyle}
-        style={previewImg ? { backgroundImage: `url(${previewImg})` } : { backgroundImage: `url(${defaultProfile})` }}
-        onClick={handleUploaderClick}>
+        onClick={handleUploaderClick}
+        style={previewImg ? { backgroundImage: `url(${previewImg})` } : undefined}>
+        {!previewImg && <IcProfileBasic />}
         <IcCameraMain0624 width={24} height={24} className={styles.icCameraStyle} />
       </div>
 
diff --git a/src/pages/instructorRegister/instructorRegister.css.ts b/src/pages/instructorRegister/instructorRegister.css.ts
index 27aa3c16a..e81f2ad08 100644
--- a/src/pages/instructorRegister/instructorRegister.css.ts
+++ b/src/pages/instructorRegister/instructorRegister.css.ts
@@ -31,3 +31,9 @@ export const buttonContainerStyle = style({
   padding: '2.4rem 2rem',
   backgroundColor: vars.colors.white,
 });
+
+export const profileImageWrapperStyle = style({
+  display: 'flex',
+  justifyContent: 'center',
+  width: '100%',
+});
diff --git a/src/pages/mypage/components/TabWrapper/components/StudentContent/StudentContent.tsx b/src/pages/mypage/components/TabWrapper/components/StudentContent/StudentContent.tsx
index 74204b76b..4f21249a0 100644
--- a/src/pages/mypage/components/TabWrapper/components/StudentContent/StudentContent.tsx
+++ b/src/pages/mypage/components/TabWrapper/components/StudentContent/StudentContent.tsx
@@ -24,7 +24,7 @@ const StudentContent = () => {
           profileImageUrl={data.profileImageUrl}
           mainText={<Text tag="b1_sb">{data.nickname}</Text>}
           subContent={
-            <Text tag="b3_r" color="gray6">
+            <Text tag="b3_r" color="gray6" className={styles.noUnderlineStyle}>
               {data.name} · {formatPhoneNumberNoSpace(data.phoneNumber)}
             </Text>
           }
diff --git a/src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts b/src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts
index 956162a67..85805bd21 100644
--- a/src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts
+++ b/src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts
@@ -25,3 +25,16 @@ export const menuButtonContainerStyle = style({
 
   backgroundColor: vars.colors.white,
 });
+
+export const noUnderlineStyle = style({
+  textDecoration: 'none',
+  color: 'inherit',
+  pointerEvents: 'none',
+  cursor: 'default',
+
+  selectors: {
+    '&::-webkit-any-link': {
+      textDecoration: 'none',
+    },
+  },
+});
diff --git a/src/shared/components/Input/Input.tsx b/src/shared/components/Input/Input.tsx
index fb616b576..af8fae56c 100644
--- a/src/shared/components/Input/Input.tsx
+++ b/src/shared/components/Input/Input.tsx
@@ -1,36 +1,23 @@
 import clsx from 'clsx';
 import type { ForwardedRef, InputHTMLAttributes, ReactNode } from 'react';
-import { forwardRef, useState } from 'react';
+import { forwardRef } from 'react';
 import * as style from '@/shared/components/Input/input.css';
 
-interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
+interface InputPropTypes extends InputHTMLAttributes<HTMLInputElement> {
   isError?: boolean;
+  isFocused?: boolean;
   rightAddOn?: ReactNode;
   onFocusChange?: (isFocused: boolean) => void;
 }
 
 const Input = (
-  { isError, className, value, rightAddOn, onFocusChange, ...props }: InputProps,
+  { isError, className, value, rightAddOn, isFocused, onFocusChange, ...props }: InputPropTypes,
   ref: ForwardedRef<HTMLInputElement>
 ) => {
-  const [isFocused, setIsFocused] = useState(false);
-
-  const handleFocus = () => {
-    setIsFocused(true);
-    onFocusChange?.(true);
-  };
-
-  const handleBlur = () => {
-    setIsFocused(false);
-    onFocusChange?.(false);
-  };
-
   const defineInputState = (isError?: boolean, isFocused?: boolean) => {
-    if (isError) {
-      return 'error';
-    } else if (isFocused) {
-      return 'focus';
-    }
+    if (isError) return 'error';
+    if (isFocused) return 'focus';
+    return undefined;
   };
 
   const inputState = defineInputState(isError, isFocused);
@@ -41,8 +28,6 @@ const Input = (
         ref={ref}
         type="text"
         className={clsx(className, style.inputStyle)}
-        onFocus={handleFocus}
-        onBlur={handleBlur}
         value={value}
         {...props}
         aria-invalid={isError ? 'true' : 'false'}
@@ -52,4 +37,4 @@ const Input = (
   );
 };
 
-export default forwardRef(Input);
\ No newline at end of file
+export default forwardRef(Input);


Reviews:
user: login: rtttr1
body: 고생하셨습니다~~ controller를 사용할때 보통 커스텀훅으로 구현해두고 사용하는데 스프린트 끝나고 컨트롤러 훅 구현한다음 제어 방식으로 관리하고 싶은 input들은 방식 바꿔주면 좋을것 같네요!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-27T02:42:53Z
user: login: constantly-dev
body: 수고하셨습니다~~ 👍 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-27T03:21:31Z
user: login: heesunee
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T04:56:27Z
user: login: heesunee
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T04:57:04Z
user: login: KIMGEONHWI
body: 고생하셨습니다~~
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-27T09:51:50Z
user: login: hansoojeongsj
body: 최고입니다 !
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-27T12:18:59Z
user: login: heesunee
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T12:21:44Z

Review comments:
diff_hunk: @@ -179,12 +179,14 @@ const InstructorRegister = () => {
               </Head>
             </div>
 
-            <ImageUploadSection
-              imgRef={imgRef}
-              previewImg={previewImg}
-              uploadImgFile={uploadImgFile}
-              handleUploaderClick={handleUploaderClick}
-            />
+            <div className={styles.profileImageWrapperStyle}>
+              <ImageUploadSection
+                imgRef={imgRef}
+                previewImg={previewImg}
+                uploadImgFile={uploadImgFile}
+                handleUploaderClick={handleUploaderClick}
+              />
+            </div>
path: src/pages/instructorRegister/InstructorRegister.tsx
user: login: constantly-dev
body: 엇 이 QA 제 브랜치에서 수정하고 1차 완료 표기했던 것 같은데, 잘 수정해주셔서 제 쪽에서 삭제하겠슴다
created_at: 2025-05-27T03:18:27Z
author_association: MEMBER
diff_hunk: @@ -25,3 +25,16 @@ export const menuButtonContainerStyle = style({
 
   backgroundColor: vars.colors.white,
 });
+
+export const noUnderlineStyle = style({
+  textDecoration: 'none',
+  color: 'inherit',
+  pointerEvents: 'none',
+  cursor: 'default',
+
+  selectors: {
+    '&::-webkit-any-link': {
+      textDecoration: 'none',
+    },
+  },
+});
path: src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts
user: login: constantly-dev
body: 이렇게 하면 전화번호 underline 기본 스타일이 사라지는군요! 👍굿굿~ 
created_at: 2025-05-27T03:21:13Z
author_association: MEMBER
diff_hunk: @@ -179,12 +179,14 @@ const InstructorRegister = () => {
               </Head>
             </div>
 
-            <ImageUploadSection
-              imgRef={imgRef}
-              previewImg={previewImg}
-              uploadImgFile={uploadImgFile}
-              handleUploaderClick={handleUploaderClick}
-            />
+            <div className={styles.profileImageWrapperStyle}>
+              <ImageUploadSection
+                imgRef={imgRef}
+                previewImg={previewImg}
+                uploadImgFile={uploadImgFile}
+                handleUploaderClick={handleUploaderClick}
+              />
+            </div>
path: src/pages/instructorRegister/InstructorRegister.tsx
user: login: heesunee
body: 굿굿!
created_at: 2025-05-27T04:56:27Z
author_association: COLLABORATOR
diff_hunk: @@ -25,3 +25,16 @@ export const menuButtonContainerStyle = style({
 
   backgroundColor: vars.colors.white,
 });
+
+export const noUnderlineStyle = style({
+  textDecoration: 'none',
+  color: 'inherit',
+  pointerEvents: 'none',
+  cursor: 'default',
+
+  selectors: {
+    '&::-webkit-any-link': {
+      textDecoration: 'none',
+    },
+  },
+});
path: src/pages/mypage/components/TabWrapper/components/StudentContent/studentContent.css.ts
user: login: heesunee
body: 000-0000-0000
이렇게 하이픈이 바로 붙는 경우는 바로 번호로 인식하는 경우도 있다고 해서 그 안의 스타일을 다 없앴습니다 !!
created_at: 2025-05-27T04:57:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,10 @@
+export const allowOnlyNumberKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+  const isNumber =
+    (e.key >= '0' && e.key <= '9') || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
+  if (!isNumber) e.preventDefault();
+};
+
+export const allowOnlyNumberPaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
+  const paste = e.clipboardData.getData('text');
+  if (!/^\d+$/.test(paste)) e.preventDefault();
+};
path: src/pages/editProfiles/utils/inputUtils.ts
user: login: KIMGEONHWI
body: 
```suggestion
import type { KeyboardEvent, ClipboardEvent } from 'react';

export const allowOnlyNumberKey = (e: KeyboardEvent<HTMLInputElement>) => {
  const isNumber =
    (e.key >= '0' && e.key <= '9') || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
  if (!isNumber) e.preventDefault();
};

export const allowOnlyNumberPaste = (e: ClipboardEvent<HTMLInputElement>) => {
  const paste = e.clipboardData.getData('text');
  if (!/^\d+$/.test(paste)) e.preventDefault();
};
```

다음과 같이 타입을 명시적으로 임포트해서 사용하는 것이 어떨까요?? 해당 부분을 컨벤션을 안맞추어서 이야기 해봐야될 것 같긴한데.
`import type`은 번들링 시 제거되므로 최종 번들 크기를 줄일 수 있어 최적화 측면에서도 유리하고, TypeScript 공식문서에서도 추천하고 있는 방식입니다. 관련 링크 남길게요!

https://www.typescriptlang.org/ko/docs/handbook/release-notes/typescript-3-8.html  
created_at: 2025-05-27T09:50:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,10 @@
+export const allowOnlyNumberKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+  const isNumber =
+    (e.key >= '0' && e.key <= '9') || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
+  if (!isNumber) e.preventDefault();
+};
+
+export const allowOnlyNumberPaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
+  const paste = e.clipboardData.getData('text');
+  if (!/^\d+$/.test(paste)) e.preventDefault();
+};
path: src/pages/editProfiles/utils/inputUtils.ts
user: login: hansoojeongsj
body: 오 !! 많이 배웁니다 !
created_at: 2025-05-27T12:17:37Z
author_association: MEMBER
diff_hunk: @@ -1,36 +1,23 @@
 import clsx from 'clsx';
 import type { ForwardedRef, InputHTMLAttributes, ReactNode } from 'react';
-import { forwardRef, useState } from 'react';
+import { forwardRef } from 'react';
 import * as style from '@/shared/components/Input/input.css';
 
 interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
path: src/shared/components/Input/Input.tsx
user: login: hansoojeongsj
body: PropTypes로 변경해주세요 !
created_at: 2025-05-27T12:18:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,10 @@
+export const allowOnlyNumberKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+  const isNumber =
+    (e.key >= '0' && e.key <= '9') || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
+  if (!isNumber) e.preventDefault();
+};
+
+export const allowOnlyNumberPaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
+  const paste = e.clipboardData.getData('text');
+  if (!/^\d+$/.test(paste)) e.preventDefault();
+};
path: src/pages/editProfiles/utils/inputUtils.ts
user: login: heesunee
body: 오 좋아요! 반영했습니다 ! ㅎㅎ
created_at: 2025-05-27T12:21:44Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-26T14:45:49Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-26T14:46:27Z
body: ✅ Storybook 배포 완료! 🔗 https://67e4fd1fd2c7078dceec04a4-vgwuokqror.chromatic.com/
<!-- thollander/actions-comment-pull-request "455-storybook" -->
author_association: NONE
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-05-27T12:18:43Z
body: ## Deploying dash-client with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>22b71ba</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://84f9f683.dash-client.pages.dev'>https://84f9f683.dash-client.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://refactor--442-mypage-fix.dash-client.pages.dev'>https://refactor--442-mypage-fix.dash-client.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/30f8fbf9324c453f380f6e3c29116567/pages/view/dash-client/84f9f683-405c-4e99-a8fa-22ed3d4ef936)

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-27T12:19:04Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-27T12:21:13Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE