Repository: team-da-sh/dash-client
Pull request #440: [Refactor] SP2 강사 등록/수정 페이지 QA 반영
Author: login: constantly-dev
Labels: name: 🪄 Refactor
description: 코드 리팩토링
name: 진혁
description: 이지녁
Created: 2025-05-20T10:23:41Z | Merged: 2025-05-22T14:29:24Z

## 📌 Related Issues
- close #417 

## ✅ 체크 리스트 
- [x] PR 제목의 형식을 잘 작성했나요? e.g. [Feat] PR 템플릿 작성
- [x] 빌드가 성공했나요? (pnpm build)
- [x] 리뷰어와 라벨을 지정했나요?

## 📄 Tasks
### 📢 수정한 내용이 굉장히 많습니다. 따라서 작업 단위로 커밋을 잘 나누었으니 커밋 단위로 확인하시는 것을 추천드립니다!! 📢

크게 작업을 리스트화 해보자면
1. (디자인) 잘못 설정한 색상이나 padding 등 디자인 수정
2. (기능) input list에서 삭제 시 auto focus 기능 삭제
3. (기능) react hook form의 isValid와 isDirty를 활용하도록 등록, 수정 버튼 활성화 로직 변경
4. (기능) textarea height auto setting을 위해 useLayoutEffect 활용하도록 변경
5. (기능) sns section @ prefix input 구현
6. 기타...


## ⭐ PR Point 
디자인 수정 사항 제외, 기능에서도 큰 문제가 없는 사항은 제외하고 서로 논의하면서 좋은 생각이 나올만한 부분만 작성하였습니다!!

### 1. textarea auto layout (height) 설정
강사 등록 중 1번째 section인 소개 부분에서 `textarea`가 쓰이는데 이 `textarea`는 높이가 자동으로 조절되어야 합니다. 처음에는 default로 `height `값이 `5.2rem`를 가지지만, 작성을 하면 text에 따라서 자동으로 높이가 조절되어야 합니다!

그래서 처음에는 text값이 바뀌는 것을 추적해주는 `onChange`에 높이 조절하는 로직을 달아서 `text를 작성할 때 높이를 계산`해서 바꿔주도록 설정했습니다.

하지만 이렇게 되면 문제가 강사 등록 페이지는 강사 수정과 동일한 코드가 사용됩니다. `userRole`을 API로 받아서 분기처리가 될 뿐 나머지 로직이나 스타일은 동일하게 사용이 되는데, 만약 강사 수정의 경우에는 이 소개 부분에 이전 데이터를 받아와 설정하는 로직이 포함되어야 합니다.

이때 이전에 로직은 높이를 자동으로 설정해주는 것이 `text를 작성할 때!` 를 trigger로 두었기 때문에 강사 수정의 경우 이전 데이터가 여러줄이라도 trigger하는 text작성을 하기 전까지는 높이가 자동으로 설정되지 않습니다. (아래와 같이)
<br/>
![image](https://github.com/user-attachments/assets/3b3f6485-4ca6-489e-9a62-a5ca60d69559)


그래서 이를 고민하다가 `useEffect`로 높이조절을 하게 했었고, 이후에 이 작업이 DOM이 업데이트 되기 전에 실행되어 paint되는 것이 맞다고 판단하여 `useLayoutEffect`로 수정하였습니다.

더 좋은 방법이 있다면.. 얼마든지 환영입니다!!! 👍 

```ts
// 수정한 코드!!
// textarea 높이 자동으로 조절
useLayoutEffect(() => {
  if (textAreaRef.current) {
    textAreaRef.current.style.height = 'auto';
    textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
  }
}, [detail]);
```

<br/>

### 2. 등록일 때는 isValid, 수정일 때는 isDirty와 isValid 사용하도록 변경
react-hook-form은 복잡한 form을 관리하기 편하게 해줍니다! 그래서 다양한 기능을 제공해주는데
- isValid : form이 유효한지 boolean값 return (=errors 판단)
- isDirty : 초기 값과 하나라도 다른지 boolean값 return

이런 좋은 기능을 활용해 버튼 활성화 로직을 수정했습니다.

그래서 등록일 때는 isValid를 활용해서 버튼 활성화 로직을 구현했고, 수정에는 기획 의도에서 하나라도 값이 바뀌어야만 활성화 되도록 요청이 있어서 isDirty와 isValid를 함께 사용해 값이 바뀔 때 && 값이 유효할 때를 기준으로 버튼 활성화를 시켰습니다!!

```ts
// 구현 일부
const isEditMode = userRole === USER_ROLE.TEACHER;
const isButtonActive = isEditMode ? isDirty && isValid : isValid;
```

<br/> 

### 3. custom @ prefix input 구현
focus를 할 때 앞에 @가 prefix로 수정이 안되도록 고정이 되는 input을 구현해야 해서 공통 컴포넌트로 구현하기엔 너무 복잡성이 늘기 때문에 따로 구현을 하였습니다!

따라서 따로 focus state를 관리하여 이 상태 값을 기준으로 focus 되면 앞에 @ icon이 추가되도록 설계하였습니다!
또한 이 아이콘이 focus 상태에 따라 색이 변해야해서 svg 컴포넌트 내부에 fill을 prop으로 받아서 설정할 수 있도록 수정하였습니다.

```ts
// 코드 일부
{isInstagramFocused || instagram ? (
      <div className={styles.prefixIconStyle}>
        <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
      </div>
    ) : (
      <div className={styles.prefixIconStyle}>
        <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
      </div>
    )}
    <input
      ref={instagramRef}
      type="text"
      name={instagramName}
      className={styles.inputStyle}
      onChange={instagramOnChange}
      onFocus={handleInstagramFocus}
      onBlur={handleInstagramBlur}
      value={instagram}
      placeholder={'dashdash.kr'}
    />
  </div>
```
```ts
const SvgIcAt12 = ({ fill = '#091120', ...props }: SVGProps<SVGSVGElement>) => (
  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 13 12" {...props}>
    <path
      fill={fill} // 이렇게 동적으로 바뀌도록 하고 위에서 default 파라미터 설
    // ... 생략
    />
  </svg>
);
export default SvgIcAt12;
```


## 📷 Screenshot
https://github.com/user-attachments/assets/89f3c790-315d-4ff7-950e-50dd3fa6e35b



## 🔔 ETC


Linked issue #417: [Refactor] 2차 스프린트 QA 수정 사항 반영 (강사 등록 & 수정)
## 🛠 Task Description
<!-- 여기에 작업에 대한 설명을 작성 -->

## 📝 TO-DO
- [ ] 해야 할 일 1
- [ ] 해야 할 일 2

## 🔔ETC


Diff:
diff --git a/public/svg/ic_at_12.svg b/public/svg/ic_at_12.svg
new file mode 100644
index 000000000..51947b2fe
--- /dev/null
+++ b/public/svg/ic_at_12.svg
@@ -0,0 +1,3 @@
+<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.03571 8.79902C5.6103 8.79902 5.22678 8.68944 4.88516 8.47029C4.54676 8.25113 4.27927 7.93046 4.08267 7.50827C3.8893 7.08286 3.79262 6.57848 3.79262 5.99515C3.79262 5.40537 3.89253 4.901 4.09234 4.48203C4.29216 4.05984 4.56127 3.74077 4.89966 3.52485C5.24128 3.30892 5.62158 3.19934 6.04055 3.19612C6.43696 3.19934 6.75763 3.2928 7.00256 3.4765C7.2475 3.6602 7.42798 3.87613 7.544 4.12429H7.60685V3.33148H8.9411V6.97167C8.94755 7.20694 8.9975 7.38258 9.09096 7.4986C9.18442 7.6114 9.32301 7.6678 9.50671 7.6678C9.73875 7.6678 9.94179 7.5824 10.1158 7.41159C10.2931 7.24078 10.4284 7.00229 10.5219 6.69612C10.6186 6.38672 10.6669 6.02899 10.6669 5.62291C10.6669 4.84299 10.4929 4.15652 10.1448 3.56352C9.79676 2.97052 9.30689 2.51126 8.67522 2.18576C8.04676 1.86025 7.32162 1.6975 6.4998 1.6975C5.67153 1.70072 4.94156 1.88926 4.30988 2.2631C3.68143 2.63373 3.19478 3.14616 2.84994 3.8004C2.5051 4.45141 2.33267 5.18461 2.33267 5.99998C2.33267 6.88304 2.48737 7.64685 2.79676 8.29142C3.10938 8.93599 3.57186 9.4323 4.18419 9.78037C4.79653 10.1284 5.54423 10.3025 6.42729 10.3025C6.92038 10.3025 7.36352 10.2783 7.75671 10.23C8.15312 10.1848 8.54791 10.1107 8.9411 10.0076V11.2693C8.17084 11.4885 7.34418 11.5981 6.46113 11.5981C5.33313 11.5981 4.36145 11.3692 3.54607 10.9116C2.73069 10.4572 2.10708 9.80938 1.67522 8.96821C1.24658 8.12383 1.03226 7.13442 1.03226 5.99998C1.02904 4.93 1.25947 3.9696 1.72356 3.11877C2.19087 2.26794 2.84027 1.60242 3.67176 1.12222C4.50325 0.642018 5.44593 0.401917 6.4998 0.401917C7.56011 0.401917 8.50602 0.625904 9.33751 1.07388C10.169 1.52185 10.8152 2.14064 11.276 2.93023C11.7369 3.71983 11.9673 4.60933 11.9673 5.59874C11.9673 6.27554 11.8594 6.86048 11.6434 7.35357C11.4307 7.84345 11.1294 8.21729 10.7394 8.47512C10.3527 8.73295 9.90634 8.86186 9.40035 8.86186C9.10063 8.86186 8.82669 8.81835 8.57853 8.73134C8.33037 8.6411 8.12572 8.51702 7.96458 8.3591C7.80344 8.19796 7.70192 8.01587 7.66002 7.81283H7.60201C7.47632 8.07388 7.28617 8.30431 7.03157 8.50413C6.78019 8.70072 6.44824 8.79902 6.03571 8.79902ZM5.28157 5.99031C5.27835 6.29326 5.32347 6.56237 5.41693 6.79764C5.51039 7.02968 5.64575 7.21177 5.82301 7.34391C6.00026 7.47604 6.21136 7.54211 6.45629 7.54211C6.83336 7.54533 7.12342 7.40836 7.32646 7.1312C7.5295 6.85081 7.63424 6.47052 7.64069 5.99031C7.63424 5.51011 7.5295 5.13465 7.32646 4.86393C7.12664 4.58999 6.83659 4.45141 6.45629 4.44819C6.20813 4.44819 5.99543 4.51426 5.81817 4.64639C5.64092 4.77531 5.50556 4.95578 5.41209 5.18783C5.32186 5.41987 5.27835 5.68737 5.28157 5.99031Z" fill="#091120"/>
+</svg>
diff --git a/src/assets/svg/IcAt12.tsx b/src/assets/svg/IcAt12.tsx
new file mode 100644
index 000000000..49277ecdc
--- /dev/null
+++ b/src/assets/svg/IcAt12.tsx
@@ -0,0 +1,11 @@
+import type { SVGProps } from 'react';
+
+const SvgIcAt12 = ({ fill = '#091120', ...props }: SVGProps<SVGSVGElement>) => (
+  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 13 12" {...props}>
+    <path
+      fill={fill}
+      d="M6.036 8.799q-.64 0-1.15-.329t-.803-.962q-.29-.638-.29-1.513 0-.885.3-1.513.3-.633.807-.957a2.1 2.1 0 0 1 1.14-.329q.595.005.963.28.367.276.541.648h.063v-.793H8.94v3.64q.01.354.15.528.14.168.416.169a.84.84 0 0 0 .609-.256q.265-.257.406-.716.145-.464.145-1.073 0-1.17-.522-2.06a3.55 3.55 0 0 0-1.47-1.377q-.942-.489-2.175-.488-1.242.004-2.19.565A3.8 3.8 0 0 0 2.85 3.8q-.517.977-.517 2.2 0 1.325.464 2.291.468.967 1.387 1.49.919.52 2.243.521.74 0 1.33-.072a8.4 8.4 0 0 0 1.184-.222v1.261q-1.155.33-2.48.33-1.692 0-2.915-.687a4.56 4.56 0 0 1-1.87-1.944Q1.031 7.702 1.031 6q-.005-1.605.692-2.881a5.1 5.1 0 0 1 1.948-1.997Q4.919.402 6.5.402q1.59 0 2.838.672a4.87 4.87 0 0 1 1.938 1.856q.69 1.185.691 2.669 0 1.015-.324 1.755-.319.735-.904 1.121a2.36 2.36 0 0 1-1.339.387q-.45 0-.821-.13a1.7 1.7 0 0 1-.614-.373 1.07 1.07 0 0 1-.305-.546h-.058a1.96 1.96 0 0 1-.57.691q-.378.295-.996.295M5.282 5.99q-.006.454.135.808.14.348.406.546.265.198.633.198.566.004.87-.41.305-.421.315-1.142-.01-.72-.315-1.126-.3-.411-.87-.416-.372 0-.638.198-.265.194-.406.542-.135.349-.13.802"
+    />
+  </svg>
+);
+export default SvgIcAt12;
diff --git a/src/pages/dancer/components/DancerInfo/DancerInfo.tsx b/src/pages/dancer/components/DancerInfo/DancerInfo.tsx
index 7c0c81e4a..4bac1c4fa 100644
--- a/src/pages/dancer/components/DancerInfo/DancerInfo.tsx
+++ b/src/pages/dancer/components/DancerInfo/DancerInfo.tsx
@@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom';
 import DancerClassItem from '@/pages/dancer/components/DancerInfo/DancerClassItem/DancerClassItem';
 import * as styles from '@/pages/dancer/components/DancerInfo/dancerInfo.css';
 import type { DancerDetailResponseTypes } from '@/pages/dancer/types/api';
-import { extractInstaHandleFromUrl, extractYouTubeHandleFromUrl } from '@/pages/dancer/utils/url';
+import { expandInstagramUrl, expandYouTubeUrl } from '@/pages/dancer/utils/url';
 import { ROUTES_CONFIG } from '@/routes/routesConfig';
 import IcInstagram20 from '@/shared/assets/svg/IcInstagram20';
 import IcYoutube20 from '@/shared/assets/svg/IcYoutube20';
@@ -14,9 +14,6 @@ import { sprinkles } from '@/shared/styles/sprinkles.css';
 const DancerInfo = ({ dancerData }: { dancerData: DancerDetailResponseTypes }) => {
   const { instagram, youtube, detail, nickname, lessons } = dancerData;
 
-  const instagramHandle = extractInstaHandleFromUrl(instagram);
-  const youtubeHandle = extractYouTubeHandleFromUrl(youtube);
-
   const navigate = useNavigate();
 
   const handleClassClick = (lessonId: number) => {
@@ -28,25 +25,25 @@ const DancerInfo = ({ dancerData }: { dancerData: DancerDetailResponseTypes }) =
     <>
       <section
         className={sprinkles({ display: 'flex', flexDirection: 'column', pt: 20, pr: 20, pl: 20, pb: 32, gap: 20 })}>
-        {(instagramHandle || youtubeHandle) && (
+        {(instagram || youtube) && (
           <div className={sprinkles({ display: 'flex', gap: 28 })}>
-            {instagramHandle && (
-              <a href={instagram} target="_blank" rel="noopener noreferrer">
+            {instagram && (
+              <a href={expandInstagramUrl(instagram)} target="_blank" rel="noopener noreferrer">
                 <div className={sprinkles({ display: 'flex', flexDirection: 'row', gap: 9 })}>
                   <IcInstagram20 width="2rem" />
                   <Text tag="b2_m" color="gray5" className={styles.linkStyle}>
-                    {instagramHandle}
+                    {instagram}
                   </Text>
                 </div>
               </a>
             )}
 
-            {youtubeHandle && (
-              <a href={youtube} target="_blank" rel="noopener noreferrer">
+            {youtube && (
+              <a href={expandYouTubeUrl(youtube)} target="_blank" rel="noopener noreferrer">
                 <div className={sprinkles({ display: 'flex', flexDirection: 'row', gap: 9 })}>
                   <IcYoutube20 width="2rem" height="2rem" />
                   <Text tag="b2_m" color="gray5" className={styles.linkStyle}>
-                    {youtubeHandle}
+                    {youtube}
                   </Text>
                 </div>
               </a>
diff --git a/src/pages/dancer/utils/url.ts b/src/pages/dancer/utils/url.ts
index 80a216159..cfd50d828 100644
--- a/src/pages/dancer/utils/url.ts
+++ b/src/pages/dancer/utils/url.ts
@@ -1,32 +1,7 @@
-const isValidUrl = (url?: string) => {
-  try {
-    return url ? new URL(url) : null;
-  } catch {
-    return null;
-  }
+export const expandYouTubeUrl = (channelName?: string): string => {
+  return channelName ? `https://www.youtube.com/@${channelName}` : '';
 };
 
-export const extractYouTubeHandleFromUrl = (url?: string): string => {
-  const parsed = isValidUrl(url);
-  if (!parsed) return '';
-
-  const { hostname, pathname } = parsed;
-  if (!hostname.includes('youtube.com')) return '';
-
-  const handle = pathname.trim().slice(1);
-  const decodedHandle = decodeURIComponent(handle);
-
-  return handle.startsWith('@') && decodedHandle.length > 1 ? decodedHandle : '';
-};
-
-export const extractInstaHandleFromUrl = (url?: string): string => {
-  const parsed = isValidUrl(url);
-  if (!parsed) return '';
-
-  const { hostname, pathname } = parsed;
-  if (!hostname.includes('instagram.com')) return '';
-
-  const [handle] = pathname.split('/').filter(Boolean);
-  const decodedHandle = decodeURIComponent(handle);
-  return decodedHandle ?? '';
+export const expandInstagramUrl = (nickName?: string): string => {
+  return nickName ? `https://www.instagram.com/${nickName}` : '';
 };
diff --git a/src/pages/instructorRegister/InstructorRegister.tsx b/src/pages/instructorRegister/InstructorRegister.tsx
index a9d302f9d..91c064764 100644
--- a/src/pages/instructorRegister/InstructorRegister.tsx
+++ b/src/pages/instructorRegister/InstructorRegister.tsx
@@ -1,6 +1,6 @@
 import { zodResolver } from '@hookform/resolvers/zod';
 import { useQueryClient } from '@tanstack/react-query';
-import { useEffect, useState } from 'react';
+import { useEffect } from 'react';
 import { useController, useForm } from 'react-hook-form';
 import { useNavigate } from 'react-router-dom';
 import {
@@ -13,14 +13,6 @@ import ImageUploadSection from '@/pages/instructorRegister/components/ImageUploa
 import IntroductionSection from '@/pages/instructorRegister/components/IntroductionSection/IntroductionSection';
 import PersonalSNSSection from '@/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection';
 import VideoLinkSection from '@/pages/instructorRegister/components/VideoLinkSection/VideoLinkSection';
-import {
-  MIN_CAREER_INPUT_COUNT,
-  MIN_EDUCATION_INPUT_COUNT,
-  MIN_INTRODUCTION_LENGTH,
-  MIN_PRIZE_INPUT_COUNT,
-  MIN_VIDEO_INPUT,
-} from '@/pages/instructorRegister/constants/registerSection';
-import useInstructorRegisterForm from '@/pages/instructorRegister/hooks/useInstructorRegisterForm';
 import * as styles from '@/pages/instructorRegister/instructorRegister.css';
 import { instructorRegisterSchema } from '@/pages/instructorRegister/schema/instructorRegisterSchema';
 import { ROUTES_CONFIG } from '@/routes/routesConfig';
@@ -52,62 +44,49 @@ const InstructorRegister = () => {
     watch,
     setValue,
     control,
-    formState: { errors },
+    formState: { errors, isValid, isDirty },
     reset,
   } = useForm({
     resolver: zodResolver(instructorRegisterSchema),
-    mode: 'onChange',
+    mode: 'onTouched',
     defaultValues: {
-      imageUrls: '',
+      detail: '',
       instagram: '',
       youtube: '',
+      imageUrls: '',
       educations: [''],
       experiences: [''],
       prizes: [''],
-      detail: '',
-      videoUrls: [{ value: '' }],
+      videoUrls: [''],
+      isEduNoneChecked: false,
+      isCareerNoneChecked: false,
+      isPrizeNoneChecked: false,
+      isVideoNoneChecked: false,
     },
   });
 
-  const { detail, instagram, youtube, educations, experiences, prizes, videoUrls, imageUrls } = watch();
-  const { field } = useController({
-    name: 'imageUrls',
-    control,
-  });
-
   const {
+    detail,
+    instagram,
+    youtube,
+    educations,
+    experiences,
+    prizes,
+    videoUrls,
+    imageUrls,
     isEduNoneChecked,
     isCareerNoneChecked,
     isPrizeNoneChecked,
-    handleEducationCheck,
-    handleCareerCheck,
-    handlePrizeCheck,
-  } = useInstructorRegisterForm();
-
-  const [isVideoNoneChecked, setIsVideoNoneChecked] = useState(false);
-
-  // 버튼 활성화 조건 체크 함수
-  const hasDetailedInfo = () => detail.trim().length >= MIN_INTRODUCTION_LENGTH;
-  const hasImage = () => !!imageUrls;
-  const hasSocialId = () => !!instagram || !!youtube;
-  const hasDancerBackground = () => {
-    const hasEducation = educations.some((edu) => edu.trim().length >= MIN_EDUCATION_INPUT_COUNT);
-    const hasCareer = experiences.some((exp) => exp.trim().length >= MIN_CAREER_INPUT_COUNT);
-    const hasPrize = prizes.some((prize) => prize.trim().length >= MIN_PRIZE_INPUT_COUNT);
-
-    const educationValid = hasEducation || isEduNoneChecked;
-    const careerValid = hasCareer || isCareerNoneChecked;
-    const prizeValid = hasPrize || isPrizeNoneChecked;
-
-    return educationValid && careerValid && prizeValid;
-  };
-  const hasVideoUrls = () => isVideoNoneChecked || videoUrls.some((url) => url.value.trim().length >= MIN_VIDEO_INPUT);
+    isVideoNoneChecked,
+  } = watch();
 
-  const buttonActive = () => {
-    return (
-      !errors.detail && hasImage() && hasSocialId() && hasDancerBackground() && hasVideoUrls() && hasDetailedInfo()
-    );
-  };
+  const { field } = useController({
+    name: 'imageUrls',
+    control,
+  });
+
+  const isEditMode = userRole === USER_ROLE.TEACHER;
+  const isButtonActive = isEditMode ? isDirty && isValid : isValid;
 
   // 이미지 업로드 관련
   const handleImageUploadSuccess = (url: string) => {
@@ -126,20 +105,20 @@ const InstructorRegister = () => {
 
     const updatedInfo = {
       imageUrls: [imageUrls],
-      instagram: instagram?.trim() === '' ? null : `https://www.instagram.com/${instagram?.trim()}`,
-      youtube: youtube?.trim() === '' ? null : `https://www.youtube.com/@${youtube?.trim()}`,
+      detail: detail.trim(),
+
+      instagram: instagram?.trim() === '' ? null : `${instagram?.trim()}`,
+      youtube: youtube?.trim() === '' ? null : `${youtube?.trim()}`,
 
       educations: isEduNoneChecked ? [] : educations.filter((education) => education.trim() !== ''),
       experiences: isCareerNoneChecked ? [] : experiences.filter((experience) => experience.trim() !== ''),
       prizes: isPrizeNoneChecked ? [] : prizes.filter((prize) => prize.trim() !== ''),
 
-      detail: detail.trim(),
-      videoUrls: isVideoNoneChecked
-        ? ['https://www.youtube.com/']
-        : videoUrls.map((url) => url.value.trim()).filter((value) => value !== ''),
+      videoUrls: isVideoNoneChecked ? [] : videoUrls.filter((url) => url.trim() !== ''),
     };
 
-    const onSuccess = (response: { data: { accessToken: string; refreshToken: string } }) => {
+    // 강사 수정 성공 함수
+    const onSuccessEdit = (response: { data: { accessToken: string; refreshToken: string } }) => {
       const { accessToken, refreshToken } = response.data;
 
       setAccessToken(accessToken);
@@ -147,6 +126,8 @@ const InstructorRegister = () => {
 
       queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.AUTH_ROLE] });
 
+      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.AUTH_ROLE] });
+
       navigate(ROUTES_CONFIG.instructorRegisterCompletion.path);
     };
 
@@ -154,7 +135,8 @@ const InstructorRegister = () => {
       navigate(ROUTES_CONFIG.error.path);
     };
 
-    if (userRole === USER_ROLE.TEACHER) {
+    // 강사 수정 (이미 강사 등록된 경우)
+    if (isEditMode) {
       instructorPatchMutate(updatedInfo, {
         onSuccess: () => {
           navigate(ROUTES_CONFIG.mypage.path);
@@ -162,7 +144,7 @@ const InstructorRegister = () => {
         onError,
       });
     } else {
-      instructorRegisterMutate(updatedInfo, { onSuccess, onError });
+      instructorRegisterMutate(updatedInfo, { onSuccess: onSuccessEdit, onError });
     }
   };
 
@@ -177,10 +159,12 @@ const InstructorRegister = () => {
       experiences: prevInstructorData.experiences?.length ? prevInstructorData.experiences : [''],
       prizes: prevInstructorData.prizes?.length ? prevInstructorData.prizes : [''],
       detail: prevInstructorData.detail || '',
-      videoUrls:
-        prevInstructorData.videoUrls?.length > 0
-          ? prevInstructorData.videoUrls.map((url: string) => ({ value: url }))
-          : [{ value: '' }],
+      videoUrls: prevInstructorData.videoUrls?.length ? prevInstructorData.videoUrls : [''],
+
+      isEduNoneChecked: !(prevInstructorData.educations?.length > 0),
+      isCareerNoneChecked: !(prevInstructorData.experiences?.length > 0),
+      isPrizeNoneChecked: !(prevInstructorData.prizes?.length > 0),
+      isVideoNoneChecked: !(prevInstructorData.videoUrls?.length > 0),
     });
   }, [prevInstructorData, reset]);
 
@@ -191,7 +175,7 @@ const InstructorRegister = () => {
           <div className={styles.sectionWrapperStyle}>
             <div className={styles.titleStyle}>
               <Head level="h1" tag="h6_sb">
-                {`강사 프로필 ${userRole === USER_ROLE.TEACHER ? '수정' : '등록'}`}
+                {`강사 프로필 ${isEditMode ? '수정' : '등록'}`}
               </Head>
             </div>
 
@@ -216,31 +200,22 @@ const InstructorRegister = () => {
               educations={educations}
               experiences={experiences}
               prizes={prizes}
-              // register={register}
               setValue={setValue}
               isEduNoneChecked={isEduNoneChecked}
               isCareerNoneChecked={isCareerNoneChecked}
               isPrizeNoneChecked={isPrizeNoneChecked}
-              handleEducationCheck={handleEducationCheck}
-              handleCareerCheck={handleCareerCheck}
-              handlePrizeCheck={handlePrizeCheck}
             />
           </div>
           <Divider direction="horizontal" color="gray1" length={'100%'} thickness={'0.8rem'} />
 
           <div className={styles.sectionWrapperStyle}>
-            <VideoLinkSection
-              register={register}
-              setValue={setValue}
-              isNoneChecked={isVideoNoneChecked}
-              setIsNoneChecked={setIsVideoNoneChecked}
-            />
+            <VideoLinkSection videoUrls={videoUrls} setValue={setValue} isNoneChecked={isVideoNoneChecked} />
           </div>
         </div>
 
         <div className={styles.buttonContainerStyle}>
-          <BoxButton variant="primary" isDisabled={!buttonActive()} type="submit">
-            {userRole === USER_ROLE.TEACHER ? '저장' : '등록'}
+          <BoxButton variant="primary" isDisabled={!isButtonActive} type="submit">
+            {isEditMode ? '저장' : '등록'}
           </BoxButton>
         </div>
       </form>
diff --git a/src/pages/instructorRegister/components/CareerSection/CareerSection.tsx b/src/pages/instructorRegister/components/CareerSection/CareerSection.tsx
index 922a9cd48..4b9fffb82 100644
--- a/src/pages/instructorRegister/components/CareerSection/CareerSection.tsx
+++ b/src/pages/instructorRegister/components/CareerSection/CareerSection.tsx
@@ -1,9 +1,13 @@
 import type { UseFormSetValue } from 'react-hook-form';
 import InputSection from '@/pages/instructorRegister/components/CareerSection/InputSection/InputSection';
 import Description from '@/pages/instructorRegister/components/Description/Description';
-import { INFO_KEY } from '@/pages/instructorRegister/constants/registerSection';
+import {
+  INSTRUCTOR_REGISTER_FORM_KEY,
+  INSTRUCTOR_REGISTER_PLACEHOLDER,
+  MAX_CAREER_INPUT_LENGTH,
+} from '@/pages/instructorRegister/constants/registerSection';
+import type { instructorRegisterFormTypes } from '@/pages/instructorRegister/types/instructorRegisterForm';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
-import type { instructorRegisterFormTypes } from '../../types/instructorRegisterForm';
 
 interface CareerSectionPropTypes {
   educations: string[];
@@ -12,11 +16,6 @@ interface CareerSectionPropTypes {
   isEduNoneChecked: boolean;
   isCareerNoneChecked: boolean;
   isPrizeNoneChecked: boolean;
-  handleEducationCheck: () => void;
-  handleCareerCheck: () => void;
-  handlePrizeCheck: () => void;
-
-  // register: UseFormRegister<instructorRegisterFormTypes>;
   setValue: UseFormSetValue<instructorRegisterFormTypes>;
 }
 
@@ -29,57 +28,81 @@ const CareerSection = ({
   isEduNoneChecked,
   isCareerNoneChecked,
   isPrizeNoneChecked,
-  handleEducationCheck,
-  handleCareerCheck,
-  handlePrizeCheck,
 }: CareerSectionPropTypes) => {
+  const defaultSetValueOptions = {
+    shouldValidate: true,
+    shouldTouch: true,
+    shouldDirty: true,
+  };
+
   return (
     <div className={sprinkles({ display: 'flex', flexDirection: 'column', width: '100%', pb: 20 })}>
       <Description title="댄서 이력 등록" subTitle="춤에 관련된 것이라면 자유롭게 입력해 보세요" />
 
       <InputSection
         title="학력"
-        placeholder="대쉬대학교 실용무용학과 졸업"
+        placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.EDUCATION}
         isNoneChecked={isEduNoneChecked}
-        onToggleActive={handleEducationCheck}
+        onToggleActive={() => {
+          setValue(INSTRUCTOR_REGISTER_FORM_KEY.IS_EDU_NONE_CHECKED, !isEduNoneChecked, {
+            shouldValidate: true,
+            shouldDirty: true,
+          });
+        }}
         inputItems={educations.map((value, id) => ({ id: id + 1, value }))}
         onItemsChange={(updatedItems) =>
           setValue(
-            INFO_KEY.EDUCATIONS,
-            updatedItems.map((item) => item.value)
+            INSTRUCTOR_REGISTER_FORM_KEY.EDUCATIONS,
+            updatedItems.map((item) => item.value),
+            defaultSetValueOptions
           )
         }
-        key={INFO_KEY.EDUCATIONS}
+        maxInputLength={MAX_CAREER_INPUT_LENGTH}
+        key={INSTRUCTOR_REGISTER_FORM_KEY.EDUCATIONS}
       />
 
       <InputSection
         title="경력"
-        placeholder="2018 BATTLE LIINEUP 1등"
+        placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.CAREER}
         isNoneChecked={isCareerNoneChecked}
-        onToggleActive={handleCareerCheck}
+        onToggleActive={() => {
+          setValue(INSTRUCTOR_REGISTER_FORM_KEY.IS_CAREER_NONE_CHECKED, !isCareerNoneChecked, {
+            shouldValidate: true,
+            shouldDirty: true,
+          });
+        }}
         inputItems={experiences.map((value, id) => ({ id: id + 1, value }))}
         onItemsChange={(updatedItems) =>
           setValue(
-            INFO_KEY.EXPERIENCES,
-            updatedItems.map((item) => item.value)
+            INSTRUCTOR_REGISTER_FORM_KEY.EXPERIENCES,
+            updatedItems.map((item) => item.value),
+            defaultSetValueOptions
           )
         }
-        key={INFO_KEY.EXPERIENCES}
+        maxInputLength={MAX_CAREER_INPUT_LENGTH}
+        key={INSTRUCTOR_REGISTER_FORM_KEY.EXPERIENCES}
       />
 
       <InputSection
         title="수상"
-        placeholder="2018 BATTLE LIINEUP 1등"
+        placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.PRIZE}
         isNoneChecked={isPrizeNoneChecked}
-        onToggleActive={handlePrizeCheck}
+        onToggleActive={() => {
+          setValue(INSTRUCTOR_REGISTER_FORM_KEY.IS_PRIZE_NONE_CHECKED, !isPrizeNoneChecked, {
+            shouldValidate: true,
+            shouldDirty: true,
+          });
+        }}
         inputItems={prizes.map((value, id) => ({ id: id + 1, value }))}
         onItemsChange={(updatedItems) =>
           setValue(
-            INFO_KEY.PRIZES,
-            updatedItems.map((item) => item.value)
+            INSTRUCTOR_REGISTER_FORM_KEY.PRIZES,
+            updatedItems.map((item) => item.value),
+            defaultSetValueOptions
           )
         }
-        key={INFO_KEY.PRIZES}
+        maxInputLength={MAX_CAREER_INPUT_LENGTH}
+        key={INSTRUCTOR_REGISTER_FORM_KEY.PRIZES}
       />
     </div>
   );
diff --git a/src/pages/instructorRegister/components/CareerSection/InputSection/InputSection.tsx b/src/pages/instructorRegister/components/CareerSection/InputSection/InputSection.tsx
index d3a5ceef3..6ec216e18 100644
--- a/src/pages/instructorRegister/components/CareerSection/InputSection/InputSection.tsx
+++ b/src/pages/instructorRegister/components/CareerSection/InputSection/InputSection.tsx
@@ -3,6 +3,7 @@ import {
   addButtonStyle,
   checkboxStyle,
   containerStyle,
+  hiddenCheckboxStyle,
 } from '@/pages/instructorRegister/components/CareerSection/careerSection.css';
 import type { InputItemTypes } from '@/pages/instructorRegister/types/inputItemTypes';
 import BtnCheck from '@/shared/assets/svg/BtnCheck';
@@ -19,6 +20,8 @@ interface InputSectionPropTypes {
   onToggleActive: () => void;
   inputItems: InputItemTypes[];
   onItemsChange: (updatedItems: InputItemTypes[]) => void;
+  maxInputLength?: number;
+  maxInputCount?: number;
 }
 
 const PLACEHOLDER_VISIBLE_COUNT = 2;
@@ -30,6 +33,8 @@ const InputSection = ({
   onToggleActive,
   inputItems,
   onItemsChange,
+  maxInputLength,
+  maxInputCount,
 }: InputSectionPropTypes) => {
   const [nextID, setNextID] = useState(inputItems.length + 1);
   const lastInputRef = useRef<HTMLInputElement | null>(null);
@@ -39,6 +44,10 @@ const InputSection = ({
       return;
     }
 
+    if (maxInputCount && inputItems.length >= maxInputCount) {
+      return;
+    }
+
     const newItem = { id: nextID, value: '' };
     onItemsChange([...inputItems, newItem]);
     setNextID(nextID + 1);
@@ -50,9 +59,6 @@ const InputSection = ({
     } else {
       onItemsChange(inputItems.filter((item) => item.id !== id));
     }
-    Promise.resolve().then(() => {
-      lastInputRef.current?.focus();
-    });
   };
 
   const renderDeleteIcon = (id: number, value: string) => {
@@ -73,11 +79,11 @@ const InputSection = ({
           <Text tag="b3_m" color="gray6">
             해당없음
           </Text>
-          {isNoneChecked ? (
-            <BtnCheck width={'2rem'} onClick={onToggleActive} />
-          ) : (
-            <div className={checkboxStyle} onClick={onToggleActive} />
-          )}
+          <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', cursor: 'pointer' }}>
+            <input onClick={onToggleActive} type="checkbox" className={hiddenCheckboxStyle} />
+
+            {isNoneChecked ? <BtnCheck width="2rem" /> : <div className={checkboxStyle} />}
+          </label>
         </Flex>
       </Flex>
 
@@ -100,20 +106,23 @@ const InputSection = ({
                 onItemsChange(updatedItems);
               }}
               rightAddOn={renderDeleteIcon(id, value)}
+              maxLength={maxInputLength}
             />
           ))}
 
-          <button
-            type="button"
-            className={addButtonStyle}
-            onClick={() => {
-              addItem();
-              Promise.resolve().then(() => {
-                lastInputRef.current?.focus();
-              });
-            }}>
-            <IcPlusGray0524 width={'2.4rem'} />
-          </button>
+          {(!maxInputCount || inputItems.length < maxInputCount) && (
+            <button
+              type="button"
+              className={addButtonStyle}
+              onClick={() => {
+                addItem();
+                Promise.resolve().then(() => {
+                  lastInputRef.current?.focus();
+                });
+              }}>
+              <IcPlusGray0524 width={'2.4rem'} />
+            </button>
+          )}
         </Flex>
       )}
     </div>
diff --git a/src/pages/instructorRegister/components/CareerSection/careerSection.css.ts b/src/pages/instructorRegister/components/CareerSection/careerSection.css.ts
index 454353747..c42c3244f 100644
--- a/src/pages/instructorRegister/components/CareerSection/careerSection.css.ts
+++ b/src/pages/instructorRegister/components/CareerSection/careerSection.css.ts
@@ -27,9 +27,16 @@ export const checkboxStyle = style({
   height: '2rem',
   outline: `1px solid ${vars.colors.gray06}`,
   borderRadius: '0.2rem',
-  cursor: 'pointer',
+  backgroundColor: vars.colors.white,
 });
 
+export const hiddenCheckboxStyle = style({
+  position: 'absolute',
+  opacity: 0,
+  width: 0,
+  height: 0,
+  pointerEvents: 'none',
+});
 export const inputContainerStyle = style({
   width: '100%',
   position: 'relative',
diff --git a/src/pages/instructorRegister/components/Description/Description.tsx b/src/pages/instructorRegister/components/Description/Description.tsx
index 528c12f58..2acf0c6cb 100644
--- a/src/pages/instructorRegister/components/Description/Description.tsx
+++ b/src/pages/instructorRegister/components/Description/Description.tsx
@@ -13,7 +13,7 @@ const Description = ({ title, subTitle }: DescriptionProps) => {
       <Head level="h1" tag="h6_sb">
         {title}
       </Head>
-      <Text tag="b3_m" color="gray7">
+      <Text tag="b3_m" color="gray6">
         {subTitle}
       </Text>
     </div>
diff --git a/src/pages/instructorRegister/components/IntroductionSection/IntroductionSection.tsx b/src/pages/instructorRegister/components/IntroductionSection/IntroductionSection.tsx
index c4fd0b9cc..7414c45ff 100644
--- a/src/pages/instructorRegister/components/IntroductionSection/IntroductionSection.tsx
+++ b/src/pages/instructorRegister/components/IntroductionSection/IntroductionSection.tsx
@@ -1,13 +1,16 @@
-import { useEffect, useState } from 'react';
+import { useLayoutEffect, useRef, useState } from 'react';
 import type { FieldError, UseFormRegister } from 'react-hook-form';
 import {
   containerStyle,
   textAreaStyle,
 } from '@/pages/instructorRegister/components/IntroductionSection/introductionSection.css';
-import { MAX_INTRODUCTION_LENGTH } from '@/pages/instructorRegister/constants/registerSection';
+import {
+  INSTRUCTOR_REGISTER_PLACEHOLDER,
+  MAX_INTRODUCTION_LENGTH,
+} from '@/pages/instructorRegister/constants/registerSection';
+import type { instructorRegisterFormTypes } from '@/pages/instructorRegister/types/instructorRegisterForm';
 import Text from '@/shared/components/Text/Text';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
-import type { instructorRegisterFormTypes } from '../../types/instructorRegisterForm';
 
 interface IntroductionSectionPropTypes {
   detail: string;
@@ -17,9 +20,22 @@ interface IntroductionSectionPropTypes {
 
 const IntroductionSection = ({ detail, register, error }: IntroductionSectionPropTypes) => {
   const [isFocused, setIsFocused] = useState(false);
+  const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
+  const { name, onBlur, ref, onChange } = register('detail');
 
   const handleFocus = () => setIsFocused(true);
-  const handleBlur = () => setIsFocused(false);
+  const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
+    setIsFocused(false);
+    onBlur(e);
+  };
+
+  // textarea 높이 자동으로 조절
+  useLayoutEffect(() => {
+    if (textAreaRef.current) {
+      textAreaRef.current.style.height = 'auto';
+      textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
+    }
+  }, [detail]);
 
   const defineInputState = (error?: boolean, isFocused?: boolean) => {
     if (error) {
@@ -39,26 +55,25 @@ const IntroductionSection = ({ detail, register, error }: IntroductionSectionPro
   const inputState = defineInputState(!!error, isFocused);
   const counterColor = getCounterColor(!!error, isFocused, !!detail);
 
-  useEffect(() => {
-    const textArea = document.querySelector('#introduction-textarea') as HTMLTextAreaElement | null;
-    if (textArea) {
-      textArea.style.height = `${textArea.scrollHeight}px`;
-    }
-  }, [detail]);
-
   return (
     <section className={containerStyle}>
       <Text tag="b2_sb">강사 소개</Text>
-      <div className={sprinkles({ display: 'flex', flexDirection: 'column', gap: 4 })}>
+      <div className={sprinkles({ display: 'flex', flexDirection: 'column', gap: 8 })}>
         <textarea
           id="introduction-textarea"
-          {...register('detail')}
-          value={detail}
-          placeholder="저는 이런 댄서예요!"
-          className={textAreaStyle({ defineInputState: inputState })}
+          name={name}
+          ref={(e) => {
+            ref(e);
+            textAreaRef.current = e;
+          }}
+          onChange={onChange}
           onFocus={handleFocus}
           onBlur={handleBlur}
+          value={detail}
+          placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.INTRODUCTION}
+          className={textAreaStyle({ defineInputState: inputState })}
           maxLength={MAX_INTRODUCTION_LENGTH}
+          rows={1}
         />
 
         <div className={sprinkles({ display: 'flex', justifyContent: 'space-between' })}>
diff --git a/src/pages/instructorRegister/components/IntroductionSection/introductionSection.css.ts b/src/pages/instructorRegister/components/IntroductionSection/introductionSection.css.ts
index e6178fc69..3ad7deab9 100644
--- a/src/pages/instructorRegister/components/IntroductionSection/introductionSection.css.ts
+++ b/src/pages/instructorRegister/components/IntroductionSection/introductionSection.css.ts
@@ -14,7 +14,7 @@ export const textAreaStyle = recipe({
   base: {
     width: '100%',
     padding: '1.6rem 1.8rem',
-    height: '5.4rem',
+    height: '5.2rem',
     resize: 'none',
     borderRadius: '4px',
     backgroundColor: vars.colors.gray01,
diff --git a/src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx b/src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
index 554aad362..1519a15a3 100644
--- a/src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
+++ b/src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
@@ -1,10 +1,13 @@
+import { useState } from 'react';
 import type { UseFormRegister } from 'react-hook-form';
 import Description from '@/pages/instructorRegister/components/Description/Description';
-import { textAreaContainerStyle } from '@/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css';
-import Input from '@/shared/components/Input/Input';
+import * as styles from '@/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css';
+import type { instructorRegisterFormTypes } from '@/pages/instructorRegister/types/instructorRegisterForm';
+import SvgIcAt12 from '@/assets/svg/IcAt12';
 import Text from '@/shared/components/Text/Text';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
-import type { instructorRegisterFormTypes } from '../../types/instructorRegisterForm';
+import { vars } from '@/shared/styles/theme.css';
+import { INSTRUCTOR_REGISTER_PLACEHOLDER } from '../../constants/registerSection';
 
 interface PersonalSNSSectionPropTypes {
   instagram?: string;
@@ -12,6 +15,14 @@ interface PersonalSNSSectionPropTypes {
   register: UseFormRegister<instructorRegisterFormTypes>;
 }
 const PersonalSNSSection = ({ instagram, youtube, register }: PersonalSNSSectionPropTypes) => {
+  const [isInstagramFocused, setIsInstagramFocused] = useState(false);
+  const [isYoutubeFocused, setIsYoutubeFocused] = useState(false);
+
+  const handleInstagramFocus = () => setIsInstagramFocused(true);
+  const handleInstagramBlur = () => setIsInstagramFocused(false);
+  const handleYoutubeFocus = () => setIsYoutubeFocused(true);
+  const handleYoutubeBlur = () => setIsYoutubeFocused(false);
+
   const { ref: instagramRef, onChange: instagramOnChange, name: instagramName } = register('instagram');
   const { ref: youtubeRef, onChange: youtubeOnChange, name: youtubeName } = register('youtube');
 
@@ -20,26 +31,56 @@ const PersonalSNSSection = ({ instagram, youtube, register }: PersonalSNSSection
       <Description title="개인 SNS 등록" subTitle="두 항목 중 하나는 반드시 입력해 주세요" />
 
       <div className={sprinkles({ flexDirection: 'column', width: '100%' })}>
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">인스타그램</Text>
-          <Input
-            ref={instagramRef}
-            onChange={instagramOnChange}
-            name={instagramName}
-            value={instagram}
-            placeholder="dashdash.kr"
-          />
+          <div className={styles.containerStyle({ isFocused: isInstagramFocused })}>
+            {isInstagramFocused || instagram ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={instagramRef}
+              type="text"
+              name={instagramName}
+              className={styles.inputStyle}
+              onChange={instagramOnChange}
+              onFocus={handleInstagramFocus}
+              onBlur={handleInstagramBlur}
+              value={instagram}
+              placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.INSTAGRAM}
+            />
+          </div>
         </div>
 
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">유튜브 채널</Text>
-          <Input
-            ref={youtubeRef}
-            onChange={youtubeOnChange}
-            name={youtubeName}
-            value={youtube}
-            placeholder="dashofficial"
-          />
+          <div className={styles.containerStyle({ isFocused: isYoutubeFocused })}>
+            {isYoutubeFocused || youtube ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={youtubeRef}
+              type="text"
+              name={youtubeName}
+              className={styles.inputStyle}
+              onChange={youtubeOnChange}
+              onFocus={handleYoutubeFocus}
+              onBlur={handleYoutubeBlur}
+              value={youtube}
+              placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.YOUTUBE}
+            />
+          </div>
         </div>
       </div>
     </section>
diff --git a/src/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css.ts b/src/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css.ts
index 05ac4ce21..f27e42527 100644
--- a/src/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css.ts
+++ b/src/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css.ts
@@ -1,4 +1,6 @@
 import { style } from '@vanilla-extract/css';
+import { recipe } from '@vanilla-extract/recipes';
+import { vars } from '@/shared/styles/theme.css';
 
 export const textAreaContainerStyle = style({
   display: 'flex',
@@ -7,3 +9,38 @@ export const textAreaContainerStyle = style({
   width: '100%',
   padding: '1.2rem 0',
 });
+
+export const containerStyle = recipe({
+  base: {
+    display: 'flex',
+    alignItems: 'center',
+
+    width: '100%',
+    height: '5.2rem',
+    padding: '1.4rem 1.8rem',
+
+    border: 'none',
+    borderRadius: '4px',
+    backgroundColor: vars.colors.gray01,
+  },
+  variants: {
+    isFocused: {
+      true: {
+        outline: `1px solid ${vars.colors.main04}`,
+      },
+    },
+  },
+});
+
+export const prefixIconStyle = style({
+  marginTop: '0.1rem',
+});
+
+export const inputStyle = style({
+  width: '100%',
+  ...vars.fonts.b2_sb_long,
+
+  '::placeholder': {
+    color: vars.colors.gray05,
+  },
+});
diff --git a/src/pages/instructorRegister/components/VideoLinkSection/VideoLinkSection.tsx b/src/pages/instructorRegister/components/VideoLinkSection/VideoLinkSection.tsx
index 626d98bdb..7c540ffb4 100644
--- a/src/pages/instructorRegister/components/VideoLinkSection/VideoLinkSection.tsx
+++ b/src/pages/instructorRegister/components/VideoLinkSection/VideoLinkSection.tsx
@@ -1,53 +1,44 @@
-import { useEffect, useState } from 'react';
-import type { UseFormRegister, UseFormSetValue } from 'react-hook-form';
+import type { UseFormSetValue } from 'react-hook-form';
 import InputSection from '@/pages/instructorRegister/components/CareerSection/InputSection/InputSection';
 import Description from '@/pages/instructorRegister/components/Description/Description';
-import { INFO_KEY } from '@/pages/instructorRegister/constants/registerSection';
-import type { InputItemTypes } from '@/pages/instructorRegister/types/inputItemTypes';
+import {
+  INSTRUCTOR_REGISTER_FORM_KEY,
+  INSTRUCTOR_REGISTER_PLACEHOLDER,
+  MAX_VIDEO_INPUT_COUNT,
+} from '@/pages/instructorRegister/constants/registerSection';
+import type { instructorRegisterFormTypes } from '@/pages/instructorRegister/types/instructorRegisterForm';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
-import type { instructorRegisterFormTypes } from '../../types/instructorRegisterForm';
 
 interface VideoLinkSectionPropTypes {
-  register: UseFormRegister<instructorRegisterFormTypes>;
+  videoUrls: string[];
   setValue: UseFormSetValue<instructorRegisterFormTypes>;
   isNoneChecked: boolean;
-  setIsNoneChecked: (value: boolean) => void;
 }
 
-const VideoLinkSection = ({ setValue, isNoneChecked, setIsNoneChecked }: VideoLinkSectionPropTypes) => {
-  const [videoItems, setVideoItems] = useState<InputItemTypes[]>([{ id: 1, value: '' }]);
-
-  // 폼 필드 값 변경 시 상태 업데이트
-  const handleVideoItemsChange = (updatedItems: InputItemTypes[]) => {
-    setVideoItems(updatedItems);
-  };
-
-  // 해당없음 토글
-  const handleToggleNone = () => {
-    setIsNoneChecked(!isNoneChecked);
-  };
-
-  // videoItems 변경 시 폼 데이터 업데이트
-  useEffect(() => {
-    // 컨트롤러를 통해 폼 값 업데이트
-    const formValues = isNoneChecked
-      ? []
-      : videoItems.map((item) => ({ value: item.value })).filter((item) => item.value.trim() !== '');
-
-    setValue(INFO_KEY.VIDEO_URLS, formValues);
-  }, [videoItems, isNoneChecked, setValue]);
-
+const VideoLinkSection = ({ videoUrls, setValue, isNoneChecked }: VideoLinkSectionPropTypes) => {
   return (
-    <section className={sprinkles({ display: 'flex', flexDirection: 'column', width: '100%', pb: 20 })}>
+    <section className={sprinkles({ display: 'flex', flexDirection: 'column', width: '100%', pb: 27 })}>
       <Description title="유튜브 영상 등록" subTitle="나를 대표하는 댄스 영상을 최대 5개 등록해 주세요" />
 
       <InputSection
         title="링크"
-        placeholder="https://www.youtube.com/watch?v=LPh1c0pGIi"
+        placeholder={INSTRUCTOR_REGISTER_PLACEHOLDER.VIDEO}
         isNoneChecked={isNoneChecked}
-        onToggleActive={handleToggleNone}
-        inputItems={videoItems}
-        onItemsChange={handleVideoItemsChange}
+        onToggleActive={() => {
+          setValue(INSTRUCTOR_REGISTER_FORM_KEY.IS_VIDEO_NONE_CHECKED, !isNoneChecked, {
+            shouldValidate: true,
+            shouldDirty: true,
+          });
+        }}
+        inputItems={videoUrls.map((value, id) => ({ id: id + 1, value }))}
+        onItemsChange={(updatedItems) => {
+          setValue(
+            INSTRUCTOR_REGISTER_FORM_KEY.VIDEO_URLS,
+            updatedItems.map((item) => item.value),
+            { shouldValidate: true, shouldTouch: true, shouldDirty: true }
+          );
+        }}
+        maxInputCount={MAX_VIDEO_INPUT_COUNT}
       />
     </section>
   );
diff --git a/src/pages/instructorRegister/constants/registerSection.ts b/src/pages/instructorRegister/constants/registerSection.ts
index 1efe869a0..f885f847f 100644
--- a/src/pages/instructorRegister/constants/registerSection.ts
+++ b/src/pages/instructorRegister/constants/registerSection.ts
@@ -1,4 +1,4 @@
-export const INFO_KEY = {
+export const INSTRUCTOR_REGISTER_FORM_KEY = {
   IMAGE_URLS: 'imageUrls',
   INSTAGRAM: 'instagram',
   YOUTUBE: 'youtube',
@@ -7,17 +7,32 @@ export const INFO_KEY = {
   PRIZES: 'prizes',
   DETAIL: 'detail',
   VIDEO_URLS: 'videoUrls',
+
+  IS_EDU_NONE_CHECKED: 'isEduNoneChecked',
+  IS_CAREER_NONE_CHECKED: 'isCareerNoneChecked',
+  IS_PRIZE_NONE_CHECKED: 'isPrizeNoneChecked',
+  IS_VIDEO_NONE_CHECKED: 'isVideoNoneChecked',
 } as const;
 
+export const INSTRUCTOR_REGISTER_PLACEHOLDER = {
+  INTRODUCTION: '저는 이런 댄서예요!',
+  INSTAGRAM: 'dashdash.kr',
+  YOUTUBE: 'dashofficial',
+  EDUCATION: '대쉬대학교 실용무용학과 졸업',
+  CAREER: 'NCT127 단독콘서트 ‘the LINK’ 퍼포먼스 디렉터',
+  PRIZE: '2018 BATTLE LIINEUP 1등',
+  VIDEO: 'https://www.youtube.com/watch?v=LPh1c0pGIi',
+};
+
+export const MIN_INTRODUCTION_LENGTH = 30;
+export const MAX_INTRODUCTION_LENGTH = 500;
+
 export const MIN_EDUCATION_INPUT_COUNT = 1;
 export const MIN_CAREER_INPUT_COUNT = 1;
 export const MIN_PRIZE_INPUT_COUNT = 1;
+export const MAX_CAREER_INPUT_LENGTH = 50;
 
-export const MAX_VIDEO_INPUT = 5;
-export const MIN_VIDEO_INPUT = 1;
-
-export const TOTAL_STEP = 6;
+export const MIN_VIDEO_INPUT_COUNT = 1;
+export const MAX_VIDEO_INPUT_COUNT = 5;
 
-export const MIN_INTRODUCTION_LENGTH = 30;
-export const MAX_INTRODUCTION_LENGTH = 500;
 export const INTRODUCTION_LENGTH_ERROR_MSG = '30자 이상 작성해 주세요';
diff --git a/src/pages/instructorRegister/hooks/useInstructorRegisterForm.ts b/src/pages/instructorRegister/hooks/useInstructorRegisterForm.ts
index 63a0edaf2..dc5180092 100644
--- a/src/pages/instructorRegister/hooks/useInstructorRegisterForm.ts
+++ b/src/pages/instructorRegister/hooks/useInstructorRegisterForm.ts
@@ -4,6 +4,7 @@ const useInstructorRegisterForm = () => {
   const [isEduNoneChecked, setEduNoneChecked] = useState(false);
   const [isCareerNoneChecked, setCareerNoneChecked] = useState(false);
   const [isPrizeNoneChecked, setPrizeNoneChecked] = useState(false);
+  const [isVideoNoneChecked, setIsVideoNoneChecked] = useState(false);
 
   const handleEducationCheck = () => {
     setEduNoneChecked((prev) => !prev);
@@ -17,13 +18,19 @@ const useInstructorRegisterForm = () => {
     setPrizeNoneChecked((prev) => !prev);
   };
 
+  const handleVideoCheck = () => {
+    setIsVideoNoneChecked((prev) => !prev);
+  };
+
   return {
     isEduNoneChecked,
     isCareerNoneChecked,
     isPrizeNoneChecked,
+    isVideoNoneChecked,
     handleEducationCheck,
     handleCareerCheck,
     handlePrizeCheck,
+    handleVideoCheck,
   };
 };
 
diff --git a/src/pages/instructorRegister/instructorRegister.css.ts b/src/pages/instructorRegister/instructorRegister.css.ts
index d29a879ac..27aa3c16a 100644
--- a/src/pages/instructorRegister/instructorRegister.css.ts
+++ b/src/pages/instructorRegister/instructorRegister.css.ts
@@ -6,7 +6,7 @@ export const containerStyle = style({
   flexDirection: 'column',
 
   width: '100%',
-  paddingBottom: '10.2rem',
+  // paddingBottom: '10.2rem',
 });
 
 export const titleStyle = style({
@@ -26,7 +26,7 @@ export const sectionWrapperStyle = style({
 
 export const buttonContainerStyle = style({
   width: '100%',
-  position: 'fixed',
+  position: 'sticky',
   bottom: '0',
   padding: '2.4rem 2rem',
   backgroundColor: vars.colors.white,
diff --git a/src/pages/instructorRegister/schema/instructorRegisterSchema.ts b/src/pages/instructorRegister/schema/instructorRegisterSchema.ts
index 534b952d1..970f6206b 100644
--- a/src/pages/instructorRegister/schema/instructorRegisterSchema.ts
+++ b/src/pages/instructorRegister/schema/instructorRegisterSchema.ts
@@ -1,22 +1,58 @@
 import { z } from 'zod';
+import {
+  INTRODUCTION_LENGTH_ERROR_MSG,
+  MIN_CAREER_INPUT_COUNT,
+  MIN_EDUCATION_INPUT_COUNT,
+  MIN_INTRODUCTION_LENGTH,
+  MIN_PRIZE_INPUT_COUNT,
+  MIN_VIDEO_INPUT_COUNT,
+} from '@/pages/instructorRegister/constants/registerSection';
 
 export const instructorRegisterSchema = z
   .object({
-    detail: z.string().min(30, '최소 30자').max(500, '자기소개는 500자 이내로 작성해주세요.'),
+    detail: z.string().min(MIN_INTRODUCTION_LENGTH, INTRODUCTION_LENGTH_ERROR_MSG).max(500),
     instagram: z.string().optional(),
     youtube: z.string().optional(),
-
+    imageUrls: z.string().min(1, '이미지를 업로드해주세요.'),
     educations: z.array(z.string()),
     experiences: z.array(z.string()),
     prizes: z.array(z.string()),
-    imageUrls: z.string().min(1, '이미지를 업로드해주세요.'),
-    videoUrls: z.array(
-      z.object({
-        value: z.string().url('올바른 URL 형식이 아닙니다.'),
-      })
-    ),
+    videoUrls: z.array(z.string()),
+
+    isEduNoneChecked: z.boolean(),
+    isCareerNoneChecked: z.boolean(),
+    isPrizeNoneChecked: z.boolean(),
+    isVideoNoneChecked: z.boolean(),
   })
   .refine((data) => data.instagram?.trim() || data.youtube?.trim(), {
     message: '인스타그램 또는 유튜브 중 하나는 반드시 입력해야 합니다.',
     path: ['instagram'],
-  });
+  })
+  .refine(
+    (data) => data.isEduNoneChecked || data.educations.some((edu) => edu.trim().length >= MIN_EDUCATION_INPUT_COUNT),
+    {
+      message: `최소 ${MIN_EDUCATION_INPUT_COUNT}자 이상인 교육 이력 항목을 하나 이상 작성하거나 "없음"을 선택하세요.`,
+      path: ['educations'],
+    }
+  )
+  .refine(
+    (data) => data.isCareerNoneChecked || data.experiences.some((exp) => exp.trim().length >= MIN_CAREER_INPUT_COUNT),
+    {
+      message: `최소 ${MIN_CAREER_INPUT_COUNT}자 이상인 경력 항목을 하나 이상 작성하거나 "없음"을 선택하세요.`,
+      path: ['experiences'],
+    }
+  )
+  .refine(
+    (data) => data.isPrizeNoneChecked || data.prizes.some((prize) => prize.trim().length >= MIN_PRIZE_INPUT_COUNT),
+    {
+      message: `최소 ${MIN_PRIZE_INPUT_COUNT}자 이상인 수상 내역 항목을 하나 이상 작성하거나 "없음"을 선택하세요.`,
+      path: ['prizes'],
+    }
+  )
+  .refine(
+    (data) => data.isVideoNoneChecked || data.videoUrls.some((url) => url.trim().length >= MIN_VIDEO_INPUT_COUNT),
+    {
+      message: `최소 ${MIN_VIDEO_INPUT_COUNT}자 이상인 영상 URL을 하나 이상 작성하거나 "없음"을 선택하세요.`,
+      path: ['videoUrls'],
+    }
+  );
diff --git a/src/pages/login/Login.tsx b/src/pages/login/Login.tsx
index f3b3ef143..42a0e09ac 100644
--- a/src/pages/login/Login.tsx
+++ b/src/pages/login/Login.tsx
@@ -1,6 +1,5 @@
-import { buttonContainerStyle } from '@/pages/instructorRegister/instructorRegister.css';
 import KakaoButton from '@/pages/login/components/KakaoButton/KakaoButton';
-import { containerStyle } from '@/pages/login/login.css';
+import * as styles from '@/pages/login/login.css';
 import LoginWepm from '@/shared/assets/webm/login.webm';
 import Head from '@/shared/components/Head/Head';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
@@ -8,19 +7,20 @@ import { sprinkles } from '@/shared/styles/sprinkles.css';
 const Login = () => {
   return (
     <>
-      <div className={containerStyle}>
+      <div className={styles.containerStyle}>
         <div className={sprinkles({ display: 'flex', flexDirection: 'column', width: '100%' })}>
           <Head tag="h3_sb">댄스 클래스로의 첫 걸음,</Head>
           <Head tag="h3_sb">지금 시작해 보세요</Head>
         </div>
 
-        <div className={sprinkles({ display: 'flex', justifyContent: 'center', width: '100%', mt: 16 })}>
+        <div className={styles.videoStyle}>
           <video width={300} autoPlay muted loop playsInline>
             <source src={LoginWepm} type="video/webm" />
           </video>
         </div>
       </div>
-      <div className={buttonContainerStyle}>
+
+      <div className={styles.buttonContainerStyle}>
         <KakaoButton />
       </div>
     </>
diff --git a/src/pages/mypage/components/TabWrapper/components/TeacherContent/TeacherContent.tsx b/src/pages/mypage/components/TabWrapper/components/TeacherContent/TeacherContent.tsx
index 9beca2d3c..385a9ec1f 100644
--- a/src/pages/mypage/components/TabWrapper/components/TeacherContent/TeacherContent.tsx
+++ b/src/pages/mypage/components/TabWrapper/components/TeacherContent/TeacherContent.tsx
@@ -1,7 +1,7 @@
 import { useState } from 'react';
 import { useNavigate } from 'react-router-dom';
-import { extractInstaHandleFromUrl, extractYouTubeHandleFromUrl } from '@/pages/dancer/utils/url';
-import { useGetMyTeacherInfo, useGetMyLessonThumbnails, useGetMyPage } from '@/pages/mypage/apis/queries';
+import { expandInstagramUrl, expandYouTubeUrl } from '@/pages/dancer/utils/url';
+import { useGetMyLessonThumbnails, useGetMyPage, useGetMyTeacherInfo } from '@/pages/mypage/apis/queries';
 import BottomList from '@/pages/mypage/components/BottomList/BottomList';
 import EmptyClassList from '@/pages/mypage/components/TabWrapper/components/TeacherContent/components/EmptyClassList/EmptyClassList';
 import TeacherLessons from '@/pages/mypage/components/TabWrapper/components/TeacherContent/components/TeacherLessons/TeacherLessons';
@@ -86,13 +86,13 @@ const TeacherContent = () => {
             <div className={sprinkles({ display: 'flex', alignItems: 'center', gap: 4 })}>
               {data.instagram && (
                 <a
-                  href={data.instagram}
+                  href={expandInstagramUrl(data.instagram)}
                   target="_blank"
                   rel="noopener noreferrer"
                   className={sprinkles({ display: 'flex', gap: 4, alignItems: 'center' })}>
                   <IcInstagram20 width={16} height={12} />
                   <Text tag="b3_m" color="gray6" className={styles.snsUrlStyle}>
-                    {extractInstaHandleFromUrl(data.instagram)}
+                    {data.instagram}
                   </Text>
                 </a>
               )}
@@ -105,13 +105,13 @@ const TeacherContent = () => {
 
               {data.youtube && (
                 <a
-                  href={data.youtube}
+                  href={expandYouTubeUrl(data.youtube)}
                   target="_blank"
                   rel="noopener noreferrer"
                   className={sprinkles({ display: 'flex', gap: 4, alignItems: 'center' })}>
                   <IcYoutube20 width={16} height={12} />
                   <Text tag="b3_m" color="gray6" className={styles.snsUrlStyle}>
-                    {extractYouTubeHandleFromUrl(data.youtube)}
+                    {data.youtube}
                   </Text>
                 </a>
               )}


Reviews:
user: login: KIMGEONHWI
body: useLayoutEffect 사용 부분도 적합한 상황이라고 생각듭니다!!
react-hook-form을 도입하니 확실히 form 관리하기 편해진 것 같아요.

한가지 궁금증이 있다면,
![스크린샷 2025-05-22 오후 4 11 39](https://github.com/user-attachments/assets/c3855c66-2bf9-45c8-9de9-3ac9338231bb)
강사 소개 필드 수정시에 `30/500`이하가 되었을 때 등록시 때처럼 border가 빨간색으로 변해야하는 것이 아닌지 궁금합니다!!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-22T07:18:40Z
user: login: constantly-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T07:38:00Z
user: login: constantly-dev
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-22T07:39:32Z
user: login: hansoojeongsj
body: insta, youtube 경로와 관련한 부분도 세심하게 같이 수정해주셔서 감사합니다 ! 고생하셨습니다  :)
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T08:28:49Z
user: login: rtttr1
body: 수고하셨습니다~~~~ 이제 진짜 QA 끝~~!!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-22T10:54:09Z

Review comments:
diff_hunk: @@ -20,26 +30,56 @@ const PersonalSNSSection = ({ instagram, youtube, register }: PersonalSNSSection
       <Description title="개인 SNS 등록" subTitle="두 항목 중 하나는 반드시 입력해 주세요" />
 
       <div className={sprinkles({ flexDirection: 'column', width: '100%' })}>
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">인스타그램</Text>
-          <Input
-            ref={instagramRef}
-            onChange={instagramOnChange}
-            name={instagramName}
-            value={instagram}
-            placeholder="dashdash.kr"
-          />
+          <div className={styles.containerStyle({ isFocused: isInstagramFocused })}>
+            {isInstagramFocused || instagram ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={instagramRef}
+              type="text"
+              name={instagramName}
+              className={styles.inputStyle}
+              onChange={instagramOnChange}
+              onFocus={handleInstagramFocus}
+              onBlur={handleInstagramBlur}
+              value={instagram}
+              placeholder={'dashdash.kr'}
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: KIMGEONHWI
body: placeholder 텍스트 상수화하는것이 어떨까요?
created_at: 2025-05-22T07:14:22Z
author_association: COLLABORATOR
diff_hunk: @@ -20,26 +30,56 @@ const PersonalSNSSection = ({ instagram, youtube, register }: PersonalSNSSection
       <Description title="개인 SNS 등록" subTitle="두 항목 중 하나는 반드시 입력해 주세요" />
 
       <div className={sprinkles({ flexDirection: 'column', width: '100%' })}>
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">인스타그램</Text>
-          <Input
-            ref={instagramRef}
-            onChange={instagramOnChange}
-            name={instagramName}
-            value={instagram}
-            placeholder="dashdash.kr"
-          />
+          <div className={styles.containerStyle({ isFocused: isInstagramFocused })}>
+            {isInstagramFocused || instagram ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={instagramRef}
+              type="text"
+              name={instagramName}
+              className={styles.inputStyle}
+              onChange={instagramOnChange}
+              onFocus={handleInstagramFocus}
+              onBlur={handleInstagramBlur}
+              value={instagram}
+              placeholder={'dashdash.kr'}
+            />
+          </div>
         </div>
 
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">유튜브 채널</Text>
-          <Input
-            ref={youtubeRef}
-            onChange={youtubeOnChange}
-            name={youtubeName}
-            value={youtube}
-            placeholder="dashofficial"
-          />
+          <div className={styles.containerStyle({ isFocused: isYoutubeFocused })}>
+            {isYoutubeFocused || youtube ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={youtubeRef}
+              type="text"
+              name={youtubeName}
+              className={styles.inputStyle}
+              onChange={youtubeOnChange}
+              onFocus={handleYoutubeFocus}
+              onBlur={handleYoutubeBlur}
+              value={youtube}
+              placeholder={'dashofficial'}
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: KIMGEONHWI
body: 해당 부분도 상수화 해주세요!
created_at: 2025-05-22T07:14:32Z
author_association: COLLABORATOR
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: KIMGEONHWI
body: 인스타그램/유튜브 입력 필드가 구조가 동일해서 공통 컴포넌트 추출도 고려해볼 수 있을거 같은데 어떤가요???
created_at: 2025-05-22T07:15:14Z
author_association: COLLABORATOR
diff_hunk: @@ -20,26 +30,56 @@ const PersonalSNSSection = ({ instagram, youtube, register }: PersonalSNSSection
       <Description title="개인 SNS 등록" subTitle="두 항목 중 하나는 반드시 입력해 주세요" />
 
       <div className={sprinkles({ flexDirection: 'column', width: '100%' })}>
-        <div className={textAreaContainerStyle}>
+        <div className={styles.textAreaContainerStyle}>
           <Text tag="b2_sb">인스타그램</Text>
-          <Input
-            ref={instagramRef}
-            onChange={instagramOnChange}
-            name={instagramName}
-            value={instagram}
-            placeholder="dashdash.kr"
-          />
+          <div className={styles.containerStyle({ isFocused: isInstagramFocused })}>
+            {isInstagramFocused || instagram ? (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} style={{ display: 'block' }} />
+              </div>
+            ) : (
+              <div className={styles.prefixIconStyle}>
+                <SvgIcAt12 width={'1.2rem'} height={'1.2rem'} fill={vars.colors.gray05} style={{ display: 'block' }} />
+              </div>
+            )}
+            <input
+              ref={instagramRef}
+              type="text"
+              name={instagramName}
+              className={styles.inputStyle}
+              onChange={instagramOnChange}
+              onFocus={handleInstagramFocus}
+              onBlur={handleInstagramBlur}
+              value={instagram}
+              placeholder={'dashdash.kr'}
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: constantly-dev
body: 좋은 생각인 것 같습니다 👍 
created_at: 2025-05-22T07:38:00Z
author_association: MEMBER
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: constantly-dev
body: 로직이 크게 다르지 않아 그것도 좋은 생각인 것 같습니다!! 👍 
created_at: 2025-05-22T07:39:32Z
author_association: MEMBER
diff_hunk: @@ -28,25 +25,25 @@ const DancerInfo = ({ dancerData }: { dancerData: DancerDetailResponseTypes }) =
     <>
       <section
         className={sprinkles({ display: 'flex', flexDirection: 'column', pt: 20, pr: 20, pl: 20, pb: 32, gap: 20 })}>
-        {(instagramHandle || youtubeHandle) && (
+        {(instagram || youtube) && (
           <div className={sprinkles({ display: 'flex', gap: 28 })}>
-            {instagramHandle && (
-              <a href={instagram} target="_blank" rel="noopener noreferrer">
+            {instagram && (
+              <a href={expandInstagramUrl(instagram)} target="_blank" rel="noopener noreferrer">
path: src/pages/dancer/components/DancerInfo/DancerInfo.tsx
user: login: hansoojeongsj
body: 최고입니다 👍 
created_at: 2025-05-22T08:18:59Z
author_association: MEMBER
diff_hunk: @@ -52,62 +44,49 @@ const InstructorRegister = () => {
     watch,
     setValue,
     control,
-    formState: { errors },
+    formState: { errors, isValid, isDirty },
     reset,
   } = useForm({
     resolver: zodResolver(instructorRegisterSchema),
-    mode: 'onChange',
+    mode: 'onTouched',
     defaultValues: {
-      imageUrls: '',
+      detail: '',
       instagram: '',
       youtube: '',
+      imageUrls: '',
       educations: [''],
       experiences: [''],
       prizes: [''],
-      detail: '',
-      videoUrls: [{ value: '' }],
+      videoUrls: [''],
+      isEduNoneChecked: false,
+      isCareerNoneChecked: false,
+      isPrizeNoneChecked: false,
+      isVideoNoneChecked: false,
     },
   });
 
-  const { detail, instagram, youtube, educations, experiences, prizes, videoUrls, imageUrls } = watch();
-  const { field } = useController({
-    name: 'imageUrls',
-    control,
-  });
-
   const {
+    detail,
+    instagram,
+    youtube,
+    educations,
+    experiences,
+    prizes,
+    videoUrls,
+    imageUrls,
     isEduNoneChecked,
     isCareerNoneChecked,
     isPrizeNoneChecked,
-    handleEducationCheck,
-    handleCareerCheck,
-    handlePrizeCheck,
-  } = useInstructorRegisterForm();
-
-  const [isVideoNoneChecked, setIsVideoNoneChecked] = useState(false);
-
-  // 버튼 활성화 조건 체크 함수
-  const hasDetailedInfo = () => detail.trim().length >= MIN_INTRODUCTION_LENGTH;
-  const hasImage = () => !!imageUrls;
-  const hasSocialId = () => !!instagram || !!youtube;
-  const hasDancerBackground = () => {
-    const hasEducation = educations.some((edu) => edu.trim().length >= MIN_EDUCATION_INPUT_COUNT);
-    const hasCareer = experiences.some((exp) => exp.trim().length >= MIN_CAREER_INPUT_COUNT);
-    const hasPrize = prizes.some((prize) => prize.trim().length >= MIN_PRIZE_INPUT_COUNT);
-
-    const educationValid = hasEducation || isEduNoneChecked;
-    const careerValid = hasCareer || isCareerNoneChecked;
-    const prizeValid = hasPrize || isPrizeNoneChecked;
-
-    return educationValid && careerValid && prizeValid;
-  };
-  const hasVideoUrls = () => isVideoNoneChecked || videoUrls.some((url) => url.value.trim().length >= MIN_VIDEO_INPUT);
+    isVideoNoneChecked,
+  } = watch();
 
-  const buttonActive = () => {
-    return (
-      !errors.detail && hasImage() && hasSocialId() && hasDancerBackground() && hasVideoUrls() && hasDetailedInfo()
-    );
-  };
+  const { field } = useController({
+    name: 'imageUrls',
+    control,
+  });
+
+  const isEditMode = userRole === USER_ROLE.TEACHER;
path: src/pages/instructorRegister/InstructorRegister.tsx
user: login: hansoojeongsj
body: 빼준 거 좋네요 👍 
created_at: 2025-05-22T08:21:04Z
author_association: MEMBER
diff_hunk: @@ -7,17 +7,22 @@ export const INFO_KEY = {
   PRIZES: 'prizes',
   DETAIL: 'detail',
   VIDEO_URLS: 'videoUrls',
+
+  IS_EDU_NONE_CHECKED: 'isEduNoneChecked',
+  IS_CAREER_NONE_CHECKED: 'isCareerNoneChecked',
+  IS_PRIZE_NONE_CHECKED: 'isPrizeNoneChecked',
+  IS_VIDEO_NONE_CHECKED: 'isVideoNoneChecked',
 } as const;
 
+export const MIN_INTRODUCTION_LENGTH = 30;
+export const MAX_INTRODUCTION_LENGTH = 500;
+
 export const MIN_EDUCATION_INPUT_COUNT = 1;
 export const MIN_CAREER_INPUT_COUNT = 1;
 export const MIN_PRIZE_INPUT_COUNT = 1;
+export const MAX_CAREER_INPUT_LENGTH = 50;
 
-export const MAX_VIDEO_INPUT = 5;
-export const MIN_VIDEO_INPUT = 1;
+export const MIN_VIDEO_INPUT_COUNT = 1;
+export const MAX_VIDEO_INPUT_COUNT = 5;
path: src/pages/instructorRegister/constants/registerSection.ts
user: login: hansoojeongsj
body: 구체적으로 네이밍 바꾼 거 좋습니다 :)
created_at: 2025-05-22T08:21:38Z
author_association: MEMBER
diff_hunk: @@ -6,7 +6,7 @@ export const containerStyle = style({
   flexDirection: 'column',
 
   width: '100%',
-  paddingBottom: '10.2rem',
+  // paddingBottom: '10.2rem',
path: src/pages/instructorRegister/instructorRegister.css.ts
user: login: hansoojeongsj
body: 이 스타일은 주석인가요 ..?! 지워주셔도 될 것 같습니다 !
created_at: 2025-05-22T08:22:31Z
author_association: MEMBER
diff_hunk: @@ -1,9 +1,11 @@
+import { useState } from 'react';
 import type { UseFormRegister } from 'react-hook-form';
 import Description from '@/pages/instructorRegister/components/Description/Description';
-import { textAreaContainerStyle } from '@/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css';
-import Input from '@/shared/components/Input/Input';
+import * as styles from '@/pages/instructorRegister/components/PersonalSNSSection/personalSNSSection.css';
+import SvgIcAt12 from '@/assets/svg/IcAt12';
 import Text from '@/shared/components/Text/Text';
 import { sprinkles } from '@/shared/styles/sprinkles.css';
+import { vars } from '@/shared/styles/theme.css';
 import type { instructorRegisterFormTypes } from '../../types/instructorRegisterForm';
path: src/pages/instructorRegister/components/PersonalSNSSection/PersonalSNSSection.tsx
user: login: hansoojeongsj
body: 이 부분도 경로 통일해주세요 ㅎㅎ
created_at: 2025-05-22T08:23:22Z
author_association: MEMBER
diff_hunk: @@ -1,32 +1,7 @@
-const isValidUrl = (url?: string) => {
-  try {
-    return url ? new URL(url) : null;
-  } catch {
-    return null;
-  }
+export const expandYouTubeUrl = (channelName?: string): string => {
+  return channelName ? `https://www.youtube.com/@${channelName}` : '';
 };
 
-export const extractYouTubeHandleFromUrl = (url?: string): string => {
-  const parsed = isValidUrl(url);
-  if (!parsed) return '';
-
-  const { hostname, pathname } = parsed;
-  if (!hostname.includes('youtube.com')) return '';
-
-  const handle = pathname.trim().slice(1);
-  const decodedHandle = decodeURIComponent(handle);
-
-  return handle.startsWith('@') && decodedHandle.length > 1 ? decodedHandle : '';
-};
-
-export const extractInstaHandleFromUrl = (url?: string): string => {
-  const parsed = isValidUrl(url);
-  if (!parsed) return '';
-
-  const { hostname, pathname } = parsed;
-  if (!hostname.includes('instagram.com')) return '';
-
-  const [handle] = pathname.split('/').filter(Boolean);
-  const decodedHandle = decodeURIComponent(handle);
-  return decodedHandle ?? '';
+export const expandInstagramUrl = (nickName?: string): string => {
+  return nickName ? `https://www.instagram.com/${nickName}` : '';
 };
path: src/pages/dancer/utils/url.ts
user: login: rtttr1
body: 오 간결해졌네요 굳굳~
created_at: 2025-05-22T10:30:28Z
author_association: COLLABORATOR
diff_hunk: @@ -17,9 +17,22 @@ interface IntroductionSectionPropTypes {
 
 const IntroductionSection = ({ detail, register, error }: IntroductionSectionPropTypes) => {
   const [isFocused, setIsFocused] = useState(false);
+  const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
+  const { name, onBlur, ref, onChange } = register('detail');
 
   const handleFocus = () => setIsFocused(true);
-  const handleBlur = () => setIsFocused(false);
+  const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
+    setIsFocused(false);
+    onBlur(e);
+  };
+
+  // textarea 높이 자동으로 조절
+  useLayoutEffect(() => {
+    if (textAreaRef.current) {
+      textAreaRef.current.style.height = 'auto';
+      textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
+    }
+  }, [detail]);
path: src/pages/instructorRegister/components/IntroductionSection/IntroductionSection.tsx
user: login: rtttr1
body: useLayoutEffect 사용 굳굳!
created_at: 2025-05-22T10:39:25Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-20T10:24:01Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-20T10:24:53Z
body: ✅ Storybook 배포 완료! 🔗 https://67e4fd1fd2c7078dceec04a4-pyeqrwgqvq.chromatic.com/
<!-- thollander/actions-comment-pull-request "440-storybook" -->
author_association: NONE
user: login: cloudflare-workers-and-pages[bot]
created_at: 2025-05-21T08:15:12Z
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>9caac40</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://734355fa.dash-client.pages.dev'>https://734355fa.dash-client.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://refactor--417-sp2-instructor.dash-client.pages.dev'>https://refactor--417-sp2-instructor.dash-client.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/30f8fbf9324c453f380f6e3c29116567/pages/view/dash-client/734355fa-6e4c-4d7e-9cef-e8ef99fa8999)

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-21T08:15:37Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-21T08:49:25Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-22T06:47:34Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: constantly-dev
created_at: 2025-05-22T07:37:35Z
body: > 강사 소개 필드 수정시에 `30/500`이하가 되었을 때 등록시 때처럼 border가 빨간색으로 변해야하는 것이 아닌지 궁금합니다!!

react-hook-form에서 form 관리를 `onChange`가 아닌 `onTouched`로 해서, 처음에는 blur처리를 할 때 validation이 trigger되고 이후에는 `onChange`로 실시간 validation이 되도록 했어요!!

기/디 이야기를 했을 때 30자 이하면 에러뜨는데 처음 작성하자마자 뜨는 게 이상해서 `onChange`가 아닌 `onTouched`를 사용했습니다! 그래서 처음에 blur를 하면 validation이 잘 작동합니다!!!
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-05-22T12:52:02Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-22T14:17:22Z
body: ✅ 빌드가 성공적으로 완료되었습니다 🛠️
author_association: NONE