// 이 파일이 하는 일: "React Router로 멀티 페이지 SPA" 코스 — 새로고침 없이 화면이 바뀌는
// SPA에서 '페이지'가 무엇인지에서 출발해, 라우트 선언·URL 파라미터·중첩 레이아웃·쿼리스트링·
// 프로그래매틱 이동·보호 라우트까지 7개 섹션으로 안내하는 정적 학습 페이지.
// 프레이밍은 "주소창이 곧 상태다" — URL을 공유·북마크 가능한 상태 저장소로 보는 관점을 심고,
// 블로그 프론트(목록·상세·글쓰기)를 라우팅으로 엮는 실전 감각까지 데려간다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──
// 학습 포인트: 템플릿 리터럴 안에서 문자열 조립은 ' + '를 쓴다 — 상수 안에 백틱·보간이
// 들어가면 이 파일 자체가 깨지기 때문. 예제 코드도 그 규칙에 맞춰 작성했다.

const CODE_SPA_VS_MPA = `전통 웹(MPA) ──────────────── SPA(Single Page App)
(페이지마다 HTML을 새로 받음)   (HTML은 한 번, 이후엔 JS가 화면 교체)

링크 클릭 → 서버에 요청         링크 클릭 → JS가 주소만 바꾸고
→ 흰 화면 → 새 HTML 도착        → 필요한 컴포넌트로 화면 교체
→ 화면 전체 다시 그림           → 새로고침 없음, 상태 유지

핵심: SPA에서 '페이지 이동'은 실제 이동이 아니라
"주소를 바꾸고, 그 주소에 맞는 컴포넌트를 그리는" 연출이에요.
그 연출을 브라우저 표준 History API(pushState)가 받쳐 주고,
React Router는 그 위에 얹힌 '주소 ↔ 컴포넌트' 매칭 담당입니다.`;

const CODE_ROUTES = `// App.jsx — 경로와 컴포넌트를 짝지어 선언한다
import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/posts" element={<PostListPage />} />
        <Route path="/write" element={<WritePage />} />
      </Routes>
    </BrowserRouter>
  );
}

// 읽는 법: "주소가 /posts면 PostListPage를 그려라."
// 라우트 선언은 지도이지 순서도가 아니에요 — 어떤 주소에
// 어떤 화면이 대응하는지를 한눈에 보여 주는 목차입니다.`;

const CODE_LINK = `// 이동은 <a>가 아니라 <Link>로
import { Link, NavLink } from 'react-router-dom';

// X 이렇게 하면 SPA가 무너져요 — 브라우저가 서버에 새 HTML을
//   요청해서 앱 전체가 새로고침(상태 전부 증발)됩니다.
<a href="/posts">글 목록</a>

// O Link는 History API로 주소만 바꾸고, 라우터가 화면을 교체
<Link to="/posts">글 목록</Link>

// NavLink는 '지금 이 주소에 있음'을 아는 Link —
// 현재 경로와 일치하면 isActive가 true가 되어 메뉴 강조에 딱
<NavLink to="/posts"
  style={(nav) => (nav.isActive ? { fontWeight: 700 } : undefined)}>
  글 목록
</NavLink>`;

const CODE_PARAMS = `// ① 선언: :id 자리는 "무엇이든 올 수 있는 빈칸"
<Route path="/posts/:id" element={<PostDetailPage />} />

// ② 꺼내기: PostDetailPage 안에서 useParams로
import { useParams } from 'react-router-dom';

function PostDetailPage() {
  const { id } = useParams();   // /posts/42 로 왔다면 id === "42"

  // ③ 그 값으로 상세 API를 조회한다 (문자열임에 주의!)
  useEffect(() => {
    fetch('/api/posts/' + id)
      .then((res) => {
        if (res.status === 404) throw new Error('NOT_FOUND');
        return res.json();
      })
      .then(setPost)
      .catch(() => setNotFound(true));   // ④ 없는 글이면 화면에 반영
  }, [id]);

  if (notFound) return <p>존재하지 않는 글이에요. 목록으로 돌아가세요.</p>;
  // ...
}`;

const CODE_NESTED = `// 중첩 라우트 — 공통 뼈대(레이아웃)는 부모, 내용은 자식
import { Outlet } from 'react-router-dom';

function BlogLayout() {
  return (
    <div>
      <header>블로그 공통 헤더 · 메뉴</header>
      <Outlet />   {/* ← 자식 라우트가 그려지는 '자리' */}
      <footer>공통 푸터</footer>
    </div>
  );
}

<Routes>
  <Route path="/posts" element={<BlogLayout />}>
    <Route index element={<PostListPage />} />      {/* /posts */}
    <Route path=":id" element={<PostDetailPage />} /> {/* /posts/42 */}
  </Route>

  {/* 관리 구역도 같은 원리 — /admin/* 전체가 AdminLayout 안에서 */}
  <Route path="/admin" element={<AdminLayout />}>
    <Route path="users" element={<AdminUsersPage />} />
    <Route path="stats" element={<AdminStatsPage />} />
  </Route>
</Routes>

// Outlet은 '액자'예요. 액자(레이아웃)는 그대로 두고
// 그림(자식 페이지)만 갈아 끼우니, 헤더가 깜빡이지 않습니다.`;

const CODE_SEARCH = `// 검색 조건을 URL에 싣기 — /posts?keyword=react&page=2
import { useSearchParams } from 'react-router-dom';

function PostListPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  const keyword = searchParams.get('keyword') || '';  // 없으면 빈 문자열
  const page = Number(searchParams.get('page') || 1);

  // 검색 버튼을 누르면 state가 아니라 'URL'을 바꾼다
  function handleSearch(nextKeyword) {
    setSearchParams({ keyword: nextKeyword, page: 1 });
  }

  // 이 URL을 복사해 친구에게 보내면, 친구도 '같은 검색 결과'를 봅니다.
  // 새로고침해도 keyword가 URL에 살아 있으니 결과가 유지되고요.
  // useState였다면? 새로고침 순간 검색어는 증발합니다.
}`;

const CODE_NAVIGATE = `// 코드가 이동을 '결정'해야 할 때 — useNavigate
import { useNavigate } from 'react-router-dom';

function WritePage() {
  const navigate = useNavigate();

  async function handleSubmit(form) {
    const res = await fetch('/api/posts', { method: 'POST', body: form });
    const saved = await res.json();

    // 저장 성공 → 방금 쓴 글의 상세 페이지로 데려가기
    navigate('/posts/' + saved.id);
  }
}

// 자주 쓰는 변형 3가지:
navigate(-1);                        // 뒤로 가기 — '취소' 버튼에 제격
navigate('/login', { replace: true }); // 현재 기록을 '교체' — 뒤로 가기로
                                       // 못 돌아오게 (로그인 후 이동 등)
navigate('/posts/7', { state: { from: 'write' } }); // 데이터 살짝 동봉
// state는 새로고침엔 살아남지만 'URL 직접 입력'으론 재현이 안 돼요.
// 그러니 없어도 화면이 그려지게 — 보조 정보만 담는 게 안전합니다.`;

const CODE_PROTECTED = `// ① 없는 주소 안내 — 와일드카드(*)는 항상 맨 마지막 그물
<Routes>
  <Route path="/" element={<HomePage />} />
  <Route path="/posts/:id" element={<PostDetailPage />} />
  <Route path="*" element={<NotFoundPage />} />  {/* 어디에도 안 걸리면 */}
</Routes>

// ② 보호 라우트 — 문 앞에서 로그인 여부를 확인하는 문지기
import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const location = useLocation();
  const isLoggedIn = Boolean(localStorage.getItem('accessToken'));

  if (!isLoggedIn) {
    // 어디를 가려다 막혔는지(state.from)를 들려 보낸다
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return children;
}

<Route path="/write" element={
  <ProtectedRoute><WritePage /></ProtectedRoute>
} />

// ③ 로그인 성공 후 — 원래 가려던 곳으로 되돌려 보내기
function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();

  async function handleLogin(form) {
    await login(form);
    const from = location.state?.from?.pathname || '/';
    navigate(from, { replace: true });  // 글쓰기를 가려다 막혔다면 → /write로
  }
}`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: 'SPA의 페이지란' },
  { n: 2, label: '라우트 선언과 이동' },
  { n: 3, label: 'URL 파라미터' },
  { n: 4, label: '중첩 라우트·레이아웃' },
  { n: 5, label: '쿼리스트링' },
  { n: 6, label: '프로그래매틱 이동' },
  { n: 7, label: '404와 보호 라우트' },
];

export default function Lv2ReactRoutingPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 중급 과정</div>
        <h1>React Router로<br />멀티 페이지 SPA</h1>
        <p>
          "React 앱인데 페이지가 여러 개인 것처럼 움직여요. 어떻게요?" — 비밀은{' '}
          <strong>주소창이 곧 상태</strong>라는 데 있어요. URL 파라미터로 상세 페이지를 열고,
          중첩 레이아웃으로 구역을 나누고, 보호 라우트로 문을 지키면 — 화면 한 장짜리 연습작이{' '}
          <strong>진짜 앱의 골격</strong>을 갖춥니다. 블로그 프론트에 목록·상세·글쓰기를 이어
          붙이며 그 골격을 손에 익혀 봅시다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 60분</span>
          <span className="chip">실습: 블로그 목록·상세·글쓰기 라우팅</span>
          <span className="chip">선수 지식: React 컴포넌트·fetch 기초</span>
        </div>
      </div>

      {/* 섹션 바로가기 */}
      <nav className="pill-nav">
        {SECTIONS.map((s) => (
          <a key={s.n} href={`#sec-${s.n}`}>
            <b>{s.n}</b>
            {s.label}
          </a>
        ))}
      </nav>

      <Section n={1} title="SPA에서 '페이지'란 무엇인가" sub="새로고침 없이 화면이 바뀌는 원리">
        <p>
          전통적인 웹에선 링크를 누를 때마다 브라우저가 서버에 <strong>새 HTML 문서</strong>를
          요청했어요. 흰 화면이 잠깐 스치고, 페이지 전체가 다시 그려지죠. 반면 우리가 만드는{' '}
          <strong>SPA(Single Page App)</strong>는 HTML을 처음에 딱 한 번만 받고, 그다음부터는{' '}
          <strong>자바스크립트가 화면을 갈아 끼웁니다.</strong> 페이지 '이동'처럼 보이는 것은
          사실 "주소를 바꾸고, 그 주소에 맞는 컴포넌트를 그리는" 연출이에요.
        </p>
        <Code>{CODE_SPA_VS_MPA}</Code>
        <p>
          이 연출을 받쳐 주는 브라우저 표준 기능이 <strong>History API</strong>예요.{' '}
          <span className="icode">history.pushState()</span>를 부르면 브라우저는{' '}
          <strong>서버에 요청을 보내지 않고</strong> 주소창의 URL만 바꾸고, 방문 기록에 한 줄을
          쌓습니다. 그래서 뒤로 가기 버튼도 자연스럽게 동작하죠. React Router는 이 API 위에
          얹혀서 "지금 주소가 뭐지? → 그럼 이 컴포넌트를 그리자"를 대신해 주는 도구입니다.
        </p>
        <p>
          여기서 이 코스 전체를 관통하는 관점 하나를 챙겨 가세요.{' '}
          <strong>URL은 공유·북마크가 가능한 상태 저장소</strong>라는 관점이에요.{' '}
          <span className="icode">/posts/42</span>라는 주소엔 "42번 글을 보는 중"이라는 상태가,{' '}
          <span className="icode">?keyword=react</span>엔 "react로 검색한 상태"가 담겨 있죠.
          이 주소를 복사해 동료에게 보내면 동료도 <strong>같은 화면</strong>을 봅니다.{' '}
          <span className="icode">useState</span> 속 상태는 절대 못 하는 일이에요.
        </p>
        <div className="tip">
          <b>역할 분담</b> 그럼 서버 라우팅은 필요 없어진 걸까요? 아니에요. 서버는{' '}
          <strong>어떤 주소로 와도 같은 index.html을 내주는 일</strong>(그리고{' '}
          <span className="icode">/api/*</span> 데이터 응답)을 맡고, "그 주소에 어떤 화면을
          보여 줄지"는 <strong>클라이언트(React Router)</strong>가 맡습니다. 배포 후{' '}
          <span className="icode">/posts/42</span>에서 새로고침하면 404가 뜨는 단골 사고가
          바로 이 분담 설정(모든 경로 → index.html)이 빠졌을 때 생겨요.
        </div>
      </Section>

      <Section n={2} title="라우트 선언과 이동" sub="Routes·Route로 지도 그리기, Link로 걷기">
        <p>
          라우팅의 절반은 <strong>선언</strong>이에요. "이 주소면 이 컴포넌트"라는 짝을{' '}
          <span className="icode">&lt;Routes&gt;</span> 안에{' '}
          <span className="icode">&lt;Route&gt;</span>로 나열합니다. 우리 블로그 실습이라면
          목록(<span className="icode">/posts</span>)·글쓰기(<span className="icode">/write</span>)
          부터 시작하죠.
        </p>
        <Code>{CODE_ROUTES}</Code>
        <p>
          나머지 절반은 <strong>이동</strong>입니다. 그리고 여기에 SPA 초보가 가장 많이 밟는
          지뢰가 있어요. 습관처럼 <span className="icode">&lt;a href&gt;</span>를 쓰는 것.{' '}
          <span className="icode">&lt;a&gt;</span>는 브라우저의 기본 동작 — "서버에 새 문서
          요청" — 을 일으켜서 <strong>앱 전체가 새로고침</strong>됩니다. 애써 쌓은 상태(입력
          중이던 글, 스크롤 위치)가 전부 증발하죠. 그래서 앱 내부 이동은 반드시{' '}
          <span className="icode">&lt;Link&gt;</span>를 씁니다.
        </p>
        <Code>{CODE_LINK}</Code>
        <div className="tip">
          <b>Link와 NavLink, 언제 뭘?</b> 본문 속 일반 링크는 <span className="icode">Link</span>,
          상단 메뉴·사이드바처럼 <strong>"내가 지금 여기 있음"을 표시</strong>해야 하는 곳은{' '}
          <span className="icode">NavLink</span>예요. <span className="icode">NavLink</span>는
          현재 주소와 자기 <span className="icode">to</span>가 일치하는지를{' '}
          <span className="icode">isActive</span>로 알려 주니, 활성 메뉴 강조를 직접 계산할
          필요가 없습니다.
        </div>
      </Section>

      <Section n={3} title="URL 파라미터로 상세 페이지" sub="/posts/:id — 주소의 빈칸에서 값을 꺼낸다">
        <p>
          블로그 글이 1,000개라면 라우트를 1,000개 선언해야 할까요? 당연히 아니죠. 경로에{' '}
          <strong>빈칸</strong>을 뚫어 두면 됩니다. <span className="icode">/posts/:id</span>의{' '}
          <span className="icode">:id</span>가 그 빈칸이에요. <span className="icode">/posts/1</span>도{' '}
          <span className="icode">/posts/999</span>도 전부 이 한 줄에 걸리고, 빈칸에 들어온 실제
          값은 컴포넌트 안에서 <span className="icode">useParams()</span>로 꺼냅니다.
        </p>
        <Code>{CODE_PARAMS}</Code>
        <p>
          흐름을 말로 풀면 이래요. 사용자가 목록에서 글을 클릭 →{' '}
          <span className="icode">/posts/42</span>로 이동 → 상세 컴포넌트가{' '}
          <span className="icode">useParams()</span>로 <span className="icode">"42"</span>를 꺼냄 →
          그 값으로 <span className="icode">/api/posts/42</span>를 조회 → 응답을 화면에 그림.{' '}
          <strong>주소가 곧 조회 조건</strong>인 셈이죠.
        </p>
        <p>
          한 가지 더 — 성실한 상세 페이지는 <strong>실패도 그립니다.</strong> 사용자가 주소창에{' '}
          <span className="icode">/posts/99999</span>를 직접 쳐서 들어올 수 있으니까요. 서버가
          404를 돌려주면 "존재하지 않는 글" 안내와 목록으로 돌아가는 길을 보여 주는 것까지가
          상세 페이지의 일입니다. 위 코드의 <span className="icode">notFound</span> 분기가 그
          역할이에요.
        </p>
        <div className="tip">
          <b>파라미터는 항상 문자열</b> <span className="icode">useParams()</span>가 주는 값은{' '}
          <span className="icode">42</span>(숫자)가 아니라 <span className="icode">"42"</span>
          (문자열)이에요. 숫자 비교나 계산이 필요하면 <span className="icode">Number(id)</span>로
          바꿔 쓰세요. <span className="icode">id === 42</span>가 조용히 false가 되는 버그의
          단골 원인입니다.
        </div>
      </Section>

      <Section n={4} title="중첩 라우트와 레이아웃" sub="Outlet — 액자는 두고 그림만 갈아 끼우기">
        <p>
          목록과 상세 페이지엔 공통점이 있어요. 둘 다 같은 헤더, 같은 사이드바를 씁니다.
          페이지마다 헤더를 복사해 붙일 수도 있지만, 그러면 메뉴 하나 바꿀 때마다 모든 파일을
          고쳐야 하죠. React Router의 답은 <strong>레이아웃 라우트</strong>예요. 공통 뼈대를{' '}
          <strong>부모 라우트</strong>로 두고, 각 페이지를 <strong>자식 라우트</strong>로
          중첩시키는 겁니다.
        </p>
        <Code>{CODE_NESTED}</Code>
        <p>
          열쇠는 <span className="icode">&lt;Outlet /&gt;</span>이에요. 부모 레이아웃 안에서{' '}
          <strong>"자식 라우트가 그려질 자리"</strong>를 표시하는 구멍입니다.{' '}
          <span className="icode">/posts</span>로 오면 그 구멍에 목록이,{' '}
          <span className="icode">/posts/42</span>로 오면 상세가 끼워지죠. 액자(헤더·푸터)는
          한 번 그려진 채 그대로 있고 <strong>그림만 바뀌니</strong>, 페이지를 오가도 헤더가
          깜빡이지 않고 헤더 안의 상태(예: 열어 둔 드롭다운)도 유지됩니다.
        </p>
        <p>
          이 구조는 <strong>구역 나누기</strong>에도 그대로 쓰여요. 위 코드의{' '}
          <span className="icode">/admin</span>처럼 관리 영역 전체를{' '}
          <span className="icode">AdminLayout</span> 밑에 두면, 관리 전용 사이드바·권한 확인을{' '}
          <strong>한 곳</strong>에 두고 하위 페이지를 늘려 갈 수 있습니다. 섹션 7의 보호
          라우트를 레이아웃에 걸면 "구역 전체를 한 번에 잠그는" 것도 되고요.
        </p>
        <div className="tip">
          <b>index 라우트</b> 자식 중 <span className="icode">index</span>가 붙은 라우트는
          "부모 주소 <strong>그 자체</strong>(<span className="icode">/posts</span>)로 왔을 때"
          그려지는 기본 화면이에요. path가 없는 대신 index가 그 역할을 표시합니다. 폴더의{' '}
          <span className="icode">index.html</span>과 같은 감각이죠.
        </div>
      </Section>

      <Section n={5} title="쿼리스트링으로 상태 공유" sub="?keyword=react — 검색 조건을 URL에 싣기">
        <p>
          블로그에 검색을 붙인다고 해 봅시다. 검색어를 <span className="icode">useState</span>에
          담으면 동작은 해요. 하지만 새로고침하는 순간 검색어가 증발하고, "이 검색 결과 좀 봐"
          하고 링크를 보내 줄 방법도 없죠. 검색 조건처럼 <strong>화면의 정체성을 이루는
          상태</strong>는 URL의 <strong>쿼리스트링</strong>(<span className="icode">?keyword=react&amp;page=2</span>)
          에 싣는 게 정석입니다.
        </p>
        <Code>{CODE_SEARCH}</Code>
        <p>
          <span className="icode">useSearchParams</span>는 <span className="icode">useState</span>와
          닮은꼴이에요 — 읽는 값과 바꾸는 함수의 쌍. 다른 점은 저장소가 메모리가 아니라{' '}
          <strong>주소창</strong>이라는 것뿐이죠. 덕분에 새로고침해도, 북마크했다가 다음 주에
          열어도, 링크를 받은 동료가 열어도 <strong>같은 검색 결과</strong>가 재현됩니다.
        </p>
        <p>
          그럼 뭐든 URL에 실으면 될까요? 판단 기준은 이거예요.{' '}
          <strong>"이 상태가 담긴 링크를 남에게 보냈을 때 의미가 있는가?"</strong> 검색어·페이지
          번호·정렬 순서·선택한 탭 → 의미 있음, URL로. 입력 중인 글 초안·모달 열림 여부·마우스
          호버 상태 → 남에게 보내도 의미 없음, <span className="icode">useState</span>로.
          경로(<span className="icode">/posts/:id</span>)가 "어느 방에 있나"라면, 쿼리스트링은
          "그 방을 어떻게 보고 있나"입니다.
        </p>
        <div className="tip">
          <b>파라미터 vs 쿼리스트링</b> 자원의 <strong>식별</strong>은 경로 파라미터
          (<span className="icode">/posts/42</span>), 조회의 <strong>옵션</strong>은 쿼리스트링
          (<span className="icode">?page=2&amp;sort=recent</span>) — 백엔드 API 설계에서 배우는
          그 관례가 프론트 라우팅에도 똑같이 적용돼요. 프론트와 서버가 같은 문법으로 대화하는
          셈입니다.
        </div>
      </Section>

      <Section n={6} title="프로그래매틱 이동" sub="useNavigate — 클릭이 아니라 코드가 이동을 결정할 때">
        <p>
          <span className="icode">Link</span>는 <strong>사용자가 클릭해서</strong> 이동하는
          도구예요. 그런데 이동의 주도권이 코드에 있어야 할 때가 있죠. 대표 장면이 우리 실습의{' '}
          <strong>글 작성 완료</strong>입니다. 저장 버튼 → API 호출 → 성공 응답이 오면{' '}
          <strong>그때</strong> 방금 쓴 글의 상세 페이지로 데려가야 해요. "성공하면"이라는
          조건이 붙는 순간, 필요한 건 <span className="icode">useNavigate</span>입니다.
        </p>
        <Code>{CODE_NAVIGATE}</Code>
        <p>
          변형 세 가지도 눈에 익혀 두세요. <span className="icode">navigate(-1)</span>은{' '}
          <strong>뒤로 가기</strong> — 글쓰기 화면의 '취소' 버튼처럼 "왔던 곳이 어디든 그리로
          돌아가기"에 알맞아요. <span className="icode">replace: true</span>는 방문 기록에 줄을{' '}
          <strong>추가하지 않고 현재 줄을 교체</strong>합니다. 로그인 성공 후 이동에 쓰면,
          사용자가 뒤로 가기를 눌러도 로그인 화면으로 되돌아가지 않죠(이미 로그인했는데 또
          로그인 폼이 뜨면 이상하니까요).
        </p>
        <p>
          <span className="icode">state</span> 옵션은 이동하면서 <strong>데이터를 살짝 동봉</strong>하는
          방법이에요. 다만 규칙이 있습니다 — state는 주소창에 보이지 않아서, 그 URL을{' '}
          <strong>직접 입력해 들어온 사람에겐 없어요.</strong> 그러니 화면을 그리는 데 꼭 필요한
          값(글 id 같은 것)은 반드시 URL(파라미터·쿼리)에 싣고, state엔 "없어도 동작에 지장
          없는 보조 정보"(어느 화면에서 왔는지 등)만 담는 게 안전한 사용법입니다.
        </p>
        <div className="tip">
          <b>navigate 남용 주의</b> 사용자가 클릭해서 가는 이동까지{' '}
          <span className="icode">onClick + navigate</span>로 짜지 마세요.{' '}
          <span className="icode">Link</span>는 진짜 <span className="icode">&lt;a&gt;</span> 태그를
          만들어 주므로 "새 탭에서 열기", 마우스 오버 시 주소 미리보기, 접근성까지 공짜로
          얻습니다. <strong>클릭 이동은 Link, 조건부 이동은 navigate</strong> — 이 한 줄이면
          충분해요.
        </div>
      </Section>

      <Section n={7} title="404와 보호 라우트" sub="없는 페이지 안내, 그리고 문 앞의 문지기">
        <p>
          마지막으로 앱의 <strong>가장자리</strong>를 다듬을 차례예요. 첫째, 사용자가 어떤
          엉뚱한 주소로 들어와도 흰 화면 대신 안내를 보여 줘야 합니다. 라우트 경로의{' '}
          <strong>와일드카드 <span className="icode">*</span></strong>는 "위의 어떤 라우트에도
          걸리지 않은 <strong>나머지 전부</strong>"를 받아 내는 그물이에요. 여기에 404 페이지를
          걸어 둡니다.
        </p>
        <p>
          둘째, 우리 실습의 마지막 요구사항 — <strong>미로그인 사용자가 글쓰기
          (<span className="icode">/write</span>)에 접근하면 로그인 페이지로 보내기.</strong>{' '}
          라우트와 페이지 사이에 <strong>문지기 컴포넌트</strong>를 세우는{' '}
          <span className="icode">ProtectedRoute</span> 패턴으로 풉니다.
        </p>
        <Code>{CODE_PROTECTED}</Code>
        <p>
          문지기의 동작을 따라가 볼까요. <span className="icode">/write</span> 요청이 오면{' '}
          <span className="icode">ProtectedRoute</span>가 먼저 렌더링되고, 로그인 상태를
          확인해요. 로그인돼 있으면 <span className="icode">children</span>(글쓰기 페이지)을
          그대로 통과시키고, 아니면 <span className="icode">&lt;Navigate&gt;</span>로 로그인
          페이지에 <strong>돌려보냅니다.</strong> 이때 두 가지 배려가 들어가요.{' '}
          <span className="icode">replace</span>로 기록을 교체해서 뒤로 가기가 다시 문 앞으로
          오지 않게 하고, <span className="icode">state.from</span>에{' '}
          <strong>원래 가려던 곳</strong>을 들려 보냅니다.
        </p>
        <p>
          그 <span className="icode">from</span>이 빛나는 순간이 로그인 성공 직후예요. "글을
          쓰려다 로그인 페이지로 튕긴" 사용자를 로그인이 끝나자마자{' '}
          <strong>원래 목적지(<span className="icode">/write</span>)로 되돌려 보내는</strong> 거죠.
          홈으로 던져 놓고 "다시 찾아가세요" 하는 앱과, 가려던 곳에 데려다주는 앱 — 사용자는
          이 차이를 반드시 느낍니다.
        </p>
        <div className="tip">
          <b>프론트 보호는 UX, 진짜 보안은 서버</b> <span className="icode">ProtectedRoute</span>는
          어디까지나 <strong>화면 안내</strong>예요. 개발자 도구를 열 줄 아는 사용자는 프론트
          검사를 우회할 수 있습니다. 글 저장을 실제로 막는 최후의 방어선은{' '}
          <strong>서버의 인증 검사</strong>(토큰 검증)예요. 프론트 문지기는 "정상 사용자가
          헛걸음하지 않게 하는 배려", 서버 검사는 "누구도 뚫을 수 없는 잠금장치" — 둘 다
          있어야 완성입니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🧭 여기까지 왔다면</h3>
          <p className="muted">
            이제 주소창을 보는 눈이 달라졌을 거예요. <strong>URL은 공유·북마크되는 상태
            저장소</strong>이고, 라우트 선언은 앱의 지도이며, Outlet은 그림만 갈아 끼우는
            액자, ProtectedRoute는 문 앞의 문지기 — 블로그의 목록·상세·글쓰기를 잇는 골격이
            전부 손에 들어왔습니다. 그런데 글쓰기 페이지의 문을 열고 들어간 사용자가 마주하는
            건 결국 <strong>폼</strong>이에요. 빈 제목으로 저장을 누르면? 본문이 10만 자면?
            악의적인 입력이 서버까지 닿으면? 다음{' '}
            <Link to="/learn/lv2-form-validation"><strong>폼 검증: 사용자를 돕고 서버를
            지키기</strong></Link> 강좌에서, 라우팅으로 세운 골격에 믿을 수 있는 입력 처리를
            채워 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
