// 이 파일이 하는 일: "API의 이해" 코스 — 식당 메뉴판 비유에서 출발해
// REST 규칙, JSON, 우리 플랫폼의 실제 API, 에러 응답, 직접 호출 실습까지
// 8개 섹션으로 안내하는 정적 학습 페이지.
// (메뉴판 비유 → REST → JSON → 우리 API 목록 → 요청·응답 흐름 → 에러 설계 → 호출 실습 → Postman 부록)
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
// 텍스트 다이어그램·예시 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

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

// ── 텍스트 다이어그램 · 예시 상수들 ──

const CODE_MENU = `식당과 API — 등장인물이 정확히 일치해요

  식당                          웹 서비스
─────────────────────────────────────────────────
  손님                          프론트엔드(React, 브라우저)
  메뉴판                        API 문서 (뭘 시킬 수 있는지 목록)
  주문서                        요청(Request)
  주방                          백엔드(Spring Boot) + DB(PostgreSQL)
  나온 음식                     응답(Response)

핵심 약속 두 가지:
1) 손님은 주방에 직접 들어가지 않는다
   → 프론트는 DB를 직접 만지지 않는다
2) 메뉴판에 있는 것만 시킬 수 있다
   → 정해진 요청에 정해진 응답. 그래서 서로 예측 가능!`;

const CODE_REST = `REST의 두 기둥 — "무엇을(URL)" + "어떻게(메서드)"

자원(무엇을)은 URL로:
  /api/checklists        체크리스트 전체
  /api/checklists/3      그중 3번 하나
  /api/submissions       제출물 전체

행위(어떻게)는 HTTP 메서드로:
  GET     조회   "3번 체크리스트 보여줘"       (읽기만, 안전)
  POST    생성   "새 제출물 등록해줘"          (새로 만듦)
  PUT     수정   "3번 항목 내용을 이걸로 바꿔"  (통째로 교체)
  DELETE  삭제   "3번 항목 지워줘"

같은 URL이라도 메서드가 다르면 전혀 다른 주문이에요:
  GET  /api/checklists/3  → 3번을 '보여줘'
  DELETE /api/checklists/3 → 3번을 '지워줘'

나쁜 예) GET /api/deleteChecklist?id=3
  → 동사를 URL에 넣지 않아요. 자원은 명사, 행위는 메서드!`;

const CODE_JSON = `JSON — 프론트와 백엔드가 쓰는 공용어

{
  "id": 3,
  "title": "Docker 기초 실습",
  "done": false,
  "tags": ["docker", "infra"],
  "author": {
    "name": "김수습",
    "team": "백엔드"
  }
}

읽는 법 딱 4가지만 알면 끝:
1) { }  객체 — "이름표: 값" 묶음 (author처럼 안에 또 객체 가능)
2) [ ]  배열 — 값 여러 개를 순서대로 (tags)
3) 이름표(key)는 항상 큰따옴표 "..."
4) 값은 문자열·숫자·true/false·null·객체·배열 중 하나

함정 주의:
- 작은따옴표 '...' 금지 (JS에선 되지만 JSON에선 에러!)
- 마지막 항목 뒤 쉼표(,) 금지
- 주석(//) 못 씀`;

const CODE_OUR_API = `우리 플랫폼 API — 여러분이 오늘도 이미 다 써본 것들!

메서드  경로                       하는 일             언제 호출됐나
──────────────────────────────────────────────────────────────────
POST   /api/auth/login            로그인, 토큰 발급    로그인 버튼 누를 때
GET    /api/auth/me               내 정보 조회         페이지 새로고침 직후
GET    /api/checklists            체크리스트 목록      체크리스트 화면 진입
PUT    /api/checklists/{id}       항목 완료/해제       체크박스 클릭!
GET    /api/submissions           내 제출물 목록       제출 현황 화면
POST   /api/submissions           과제 제출            제출 버튼 누를 때

→ 체크박스 하나 딸깍한 것도 사실은 PUT 요청 한 발이었어요.
  섹션 7에서 F12로 이걸 직접 눈으로 확인합니다.`;

const CODE_FULL_FLOW = `체크박스를 딸깍 — 그 0.3초 동안 벌어지는 일

① React: 클릭 이벤트 발생
   fetch('/api/checklists/3', { method: 'PUT', ... }) 실행

② 요청이 네트워크를 달린다 (네트워크 코스에서 배운 그 길!)
   PUT /api/checklists/3
   Authorization: Bearer eyJhbGci...   ← "저 로그인한 사람이에요" 신분증
   Content-Type: application/json
   { "done": true }                    ← 본문(body): 주문 상세

③ 서버 도착: Caddy(현관) → Spring Boot(주방)
   - 토큰 검사: 신분증 진짜야? 유효기간 안 지났어?
   - 권한 검사: 3번 항목, 이 사람 거 맞아?
   - PostgreSQL에 UPDATE 실행

④ 응답 귀환
   200 OK
   { "id": 3, "done": true, "updatedAt": "2026-07-16T..." }

⑤ React: 응답을 받아 화면의 체크박스를 채운다 ✓

요청 = 메서드 + URL + 헤더 + (본문)
응답 = 상태코드 + 헤더 + (본문)
이 구조는 어떤 API든 똑같아요.`;

const CODE_ERRORS = `에러 응답 — 숫자 세 자리에 담긴 서버의 대답

401 Unauthorized — "누구세요?"
   신분증(토큰)이 없거나 만료됨.
   식당 비유: 회원 전용 식당인데 회원증을 안 보여줌
   해결: 다시 로그인 (프론트가 로그인 화면으로 보내는 이유!)

403 Forbidden — "누군지는 아는데, 그건 안 돼요"
   로그인은 했지만 권한이 없음.
   식당 비유: 회원증은 있는데 VIP룸 전용 메뉴를 주문함
   예) 수습이 관리자 전용 API를 부르면 403

404 Not Found — "그런 건 없는데요?"
   자원이 존재하지 않음.
   식당 비유: 메뉴판에 없는 음식을 주문함
   예) GET /api/checklists/99999 (없는 번호)

셋을 구분하는 게 디버깅의 절반이에요:
  401 → 내 로그인 상태를 의심
  403 → 내 권한을 의심
  404 → 내가 적은 URL(오타·없는 id)을 의심

그 외 자주 만나는 친구들:
  400 Bad Request     본문이 이상함 (JSON 문법 오류, 필수값 누락)
  500 Internal Error  서버 쪽 버그 — 이건 백엔드 개발자를 부를 차례`;

const CODE_CURL = `# curl — 터미널에서 API를 직접 호출하는 도구 (Windows 10+ 기본 내장)

# 1) 일단 아무나 볼 수 있는 공개 API로 몸풀기:
curl https://edu.awesomedevapp.com/api/health

# 이런 JSON이 오면 성공:
{"status":"UP"}

# 2) 응답 본문뿐 아니라 상태코드·헤더까지 보기 (-i):
curl -i https://edu.awesomedevapp.com/api/health

HTTP/2 200            ← 상태코드가 맨 윗줄에!
content-type: application/json
...

# 3) 로그인 없이 보호된 API를 불러 보기 — 일부러 401 맞아 보기:
curl -i https://edu.awesomedevapp.com/api/auth/me

HTTP/2 401            ← "누구세요?" 방금 배운 그 401이에요.`;

const CODE_FETCH = `// 브라우저 콘솔 fetch — 지금 이 사이트에 로그인돼 있다면
// 브라우저가 이미 내 신분증을 갖고 있어요. 그걸 그대로 써 봅시다.

// 1) 우리 플랫폼에 로그인된 탭에서 F12 → Console 탭 → 붙여넣기:
fetch('/api/auth/me')
  .then((res) => {
    console.log('상태코드:', res.status);   // 200이 나와야 정상!
    return res.json();                      // 본문을 JSON으로 해석
  })
  .then((data) => console.log(data));       // 내 정보 객체가 출력됨

// 2) 이번엔 없는 자원을 불러서 404를 직접 만나 보기:
fetch('/api/checklists/99999')
  .then((res) => console.log('상태코드:', res.status));  // 404!

// 시크릿 창(로그인 안 된 상태)에서 1)을 다시 하면? → 401.
// 401/403/404를 전부 '내 손으로' 만들어 봤다면 이 코스 절반은 끝!`;

const CODE_POSTMAN = `curl과 fetch가 익숙해졌다면 — GUI 도구로 갈아탈 시간

Postman 같은 API 테스트 도구가 해 주는 것:
- 메서드·URL·헤더·본문을 폼으로 입력 (오타 지옥 탈출)
- 보낸 요청을 저장해 두고 재사용 (컬렉션)
- 토큰을 한 번 등록하면 모든 요청에 자동 첨부
- 팀원과 요청 모음을 공유 → "이 API 어떻게 불러요?"가 사라짐

백엔드 개발자의 하루:
  코드 수정 → 도구에서 요청 발사 → 응답 확인 → 반복
  (매번 프론트 화면을 거치지 않고 API만 따로 테스트!)

부록 과제:
1) API 테스트 도구를 하나 설치한다 (멘토에게 팀 표준 도구를 물어보기)
2) 섹션 7의 health 체크 요청을 GUI로 재현한다
3) 로그인 요청(POST /api/auth/login)을 만들어 토큰을 받아 본다
4) 그 토큰으로 /api/auth/me 를 호출해 200을 받으면 졸업!`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. 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: 'API = 메뉴판' },
  { n: 2, label: 'REST 규칙' },
  { n: 3, label: 'JSON 읽고 쓰기' },
  { n: 4, label: '우리 플랫폼 API' },
  { n: 5, label: '요청→응답 흐름' },
  { n: 6, label: '에러 응답 설계' },
  { n: 7, label: '직접 호출 실습' },
  { n: 8, label: 'Postman 부록' },
];

export default function ApiPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 서버와 데이터</div>
        <h1>API의 이해</h1>
        <p>
          여러분이 이 플랫폼에서 누른 로그인 버튼, 딸깍한 체크박스는 전부 API 호출이었어요.
          이미 매일 쓰고 있던 그것의 정체를 메뉴판 비유 하나로 꿰뚫고, 마지막엔
          <strong> 내 손으로 직접 API를 호출</strong>해 봅니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 50분</span>
          <span className="chip">실습 2개 + 부록 과제</span>
          <span className="chip">선수 코스: 네트워크의 이해</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="API는 식당의 메뉴판이에요" sub="정해진 요청, 정해진 응답 — 그게 전부">
        <p>
          <strong>API</strong>(Application Programming Interface)라는 말이 어렵게 들리지만,
          정체는 <strong>"프로그램끼리 대화하는 약속된 창구"</strong>예요. 식당에 비유하면
          완벽하게 맞아떨어집니다.
        </p>
        <Code>{CODE_MENU}</Code>
        <p>
          손님(프론트엔드)은 주방(DB)이 어떻게 생겼는지 몰라도 돼요. 메뉴판(API)에 있는
          대로 주문하면, 정해진 음식(응답)이 나온다는 것만 알면 됩니다. 반대로 주방은
          손님이 누구든 <strong>주문서 양식만 지키면</strong> 요리를 해 줘요. 이렇게
          서로의 속사정을 몰라도 협업할 수 있게 해 주는 게 API의 진짜 힘이에요 —
          그래서 우리 회사에서도 프론트 담당과 백엔드 담당이 <strong>API 명세부터 합의</strong>하고
          각자 개발을 시작합니다.
        </p>
        <div className="tip">
          <b>용어 한 입</b> "인터페이스(Interface)"는 <strong>서로 다른 두 세계가 만나는 접점</strong>이라는
          뜻이에요. 키보드는 사람↔컴퓨터의 인터페이스, API는 프로그램↔프로그램의 인터페이스.
        </div>
      </Section>

      <Section n={2} title="REST — 메뉴판을 쓰는 문법" sub="자원은 URL로, 행위는 메서드로">
        <p>
          메뉴판을 아무렇게나 쓰면 손님이 헷갈리겠죠? 그래서 업계에는 메뉴판 작성의
          표준 문법이 있어요 — 그게 <strong>REST</strong>입니다. 규칙은 딱 두 줄로 요약돼요.
          <strong> "무엇을"은 URL에, "어떻게"는 HTTP 메서드에</strong> 담는다.
        </p>
        <Code>{CODE_REST}</Code>
        <p>
          이 규칙의 좋은 점: URL과 메서드만 봐도 <strong>무슨 일이 일어날지 읽혀요</strong>.
          <span className="icode">DELETE /api/submissions/7</span>을 보면 설명 없이도
          "7번 제출물을 지우는구나"를 알 수 있죠. 마치 잘 정리된 메뉴판처럼요.
        </p>
        <svg className="diagram" viewBox="0 0 640 285" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="REST 구조도: 자원 URL과 HTTP 메서드 GET, POST, PUT, DELETE의 조합 매트릭스">
          <text x="107" y="22" textAnchor="middle" fontSize="12" fill="var(--muted)">무엇을 — 자원 URL (명사)</text>
          <text x="421" y="22" textAnchor="middle" fontSize="12" fill="var(--muted)">어떻게 — HTTP 메서드 (행위)</text>
          <rect x="210" y="34" width="98" height="26" rx="13" fill="var(--card)" stroke="var(--teal)" />
          <text x="259" y="51" textAnchor="middle" fontSize="12" fontWeight="700" fill="var(--teal)">GET · 조회</text>
          <rect x="318" y="34" width="98" height="26" rx="13" fill="var(--card)" stroke="var(--primary)" />
          <text x="367" y="51" textAnchor="middle" fontSize="12" fontWeight="700" fill="var(--primary)">POST · 생성</text>
          <rect x="426" y="34" width="98" height="26" rx="13" fill="var(--card)" stroke="var(--amber)" />
          <text x="475" y="51" textAnchor="middle" fontSize="12" fontWeight="700" fill="var(--amber)">PUT · 수정</text>
          <rect x="534" y="34" width="98" height="26" rx="13" fill="var(--card)" stroke="var(--rose)" />
          <text x="583" y="51" textAnchor="middle" fontSize="12" fontWeight="700" fill="var(--rose)">DELETE · 삭제</text>
          <rect x="12" y="72" width="190" height="46" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="22" y="91" fontSize="13" fontWeight="600" fill="var(--ink)">/api/checklists</text>
          <text x="22" y="108" fontSize="11" fill="var(--muted)">체크리스트 전체</text>
          <rect x="210" y="72" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--teal)" />
          <text x="259" y="99" textAnchor="middle" fontSize="12" fill="var(--ink)">전체 조회</text>
          <text x="367" y="99" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <text x="475" y="99" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <text x="583" y="99" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <rect x="12" y="126" width="190" height="46" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="22" y="145" fontSize="13" fontWeight="600" fill="var(--ink)">/api/checklists/3</text>
          <text x="22" y="162" fontSize="11" fill="var(--muted)">그중 3번 하나</text>
          <rect x="210" y="126" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--teal)" />
          <text x="259" y="153" textAnchor="middle" fontSize="12" fill="var(--ink)">3번 조회</text>
          <text x="367" y="153" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <rect x="426" y="126" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--amber)" />
          <text x="475" y="153" textAnchor="middle" fontSize="12" fill="var(--ink)">3번 교체</text>
          <rect x="534" y="126" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--rose)" />
          <text x="583" y="153" textAnchor="middle" fontSize="12" fill="var(--ink)">3번 삭제</text>
          <rect x="12" y="180" width="190" height="46" rx="8" fill="var(--card)" stroke="var(--line)" />
          <text x="22" y="199" fontSize="13" fontWeight="600" fill="var(--ink)">/api/submissions</text>
          <text x="22" y="216" fontSize="11" fill="var(--muted)">제출물 전체</text>
          <rect x="210" y="180" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--teal)" />
          <text x="259" y="207" textAnchor="middle" fontSize="12" fill="var(--ink)">목록 조회</text>
          <rect x="318" y="180" width="98" height="46" rx="8" fill="var(--card)" stroke="var(--primary)" />
          <text x="367" y="207" textAnchor="middle" fontSize="12" fill="var(--ink)">새로 등록</text>
          <text x="475" y="207" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <text x="583" y="207" textAnchor="middle" fontSize="12" fill="var(--muted)">—</text>
          <text x="320" y="248" textAnchor="middle" fontSize="12" fill="var(--ink)">같은 URL(/api/checklists/3)이라도 메서드가 다르면 전혀 다른 주문!</text>
          <text x="320" y="266" textAnchor="middle" fontSize="12" fill="var(--muted)">동사를 URL에 넣지 않기 — 자원은 명사로, 행위는 메서드로.</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: REST 매트릭스 — 자원(URL) × 행위(메서드)가 만나 하나의 주문이 돼요</p>
        <div className="warn">
          <b>초보 단골 실수</b> — "조회니까 아무거나 GET으로" 는 맞지만, 반대로
          <strong> 데이터를 바꾸는 일을 GET에 넣으면 사고</strong>가 나요. GET은 브라우저·검색엔진이
          "안전하다"고 믿고 마음대로 미리 불러가기도 하거든요. 삭제 링크를 GET으로 만들었다가
          크롤러가 지나가며 데이터를 전부 지운 전설의 사고가 실제로 있었습니다.
        </div>
      </Section>

      <Section n={3} title="JSON — 요청서와 응답서의 언어" sub="중괄호와 대괄호만 읽으면 끝">
        <p>
          주문서(요청 본문)와 음식(응답 본문)에 담기는 데이터는 대부분 <strong>JSON</strong>이라는
          형식으로 적어요. 프론트(JavaScript)와 백엔드(Java)가 언어는 달라도, JSON이라는
          <strong> 공용어</strong>로 대화하는 거죠.
        </p>
        <Code>{CODE_JSON}</Code>
        <p>
          생긴 게 JavaScript 객체와 거의 같아서(이름부터 JavaScript Object Notation)
          React에서 다루기 편해요. Spring Boot 쪽에서도 Java 객체를 JSON으로,
          JSON을 Java 객체로 자동 변환해 줍니다 — 양쪽 다 "번역기"를 내장하고 있는 셈이에요.
        </p>
        <div className="tip">
          <b>직접 확인해 보기</b> 브라우저 콘솔(<span className="kbd">F12</span> →
          <span className="kbd">Console</span>)에서{' '}
          <span className="icode">{'JSON.parse(\'{"a": 1,}\')'}</span> 를 실행해 보세요 —
          마지막 쉼표 때문에 에러가 나요. 쉼표를 지우고 다시 실행하면 성공.
          방금 위에서 배운 "함정"을 몸으로 확인한 겁니다. 거꾸로 변환은{' '}
          <span className="icode">{'JSON.stringify({ a: 1 })'}</span> 로 해 보세요.
        </div>
      </Section>

      <Section n={4} title="우리 플랫폼의 API 목록" sub="오늘 여러분이 이미 호출한 것들">
        <p>
          이론은 충분해요. 이제 <strong>진짜 메뉴판</strong>을 봅시다 — 지금 접속해 있는
          이 학습 플랫폼의 API예요. 놀랍게도, 여러분은 이걸 전부 이미 써 봤습니다.
        </p>
        <Code>{CODE_OUR_API}</Code>
        <p>
          섹션 2에서 배운 규칙이 그대로 보이죠? 자원은 명사(<span className="icode">checklists</span>,{' '}
          <span className="icode">submissions</span>), 행위는 메서드(GET·POST·PUT).{' '}
          <span className="icode">{'{id}'}</span>처럼 중괄호로 쓴 부분은 실제 호출 때
          진짜 번호로 바뀌는 자리예요(<span className="icode">/api/checklists/3</span>).
        </p>
        <div className="tip">
          <b>관찰 포인트</b> 로그인(<span className="icode">POST /api/auth/login</span>)만
          유일하게 "자원 생성"이 아닌데도 POST예요. 아이디·비밀번호를 <strong>본문에 숨겨</strong> 보내야
          하기 때문이에요 — GET은 값이 URL에 노출되거든요. 규칙에는 이렇게 "이유 있는 예외"도 있습니다.
        </div>
      </Section>

      <Section n={5} title="요청 → 응답, 전체 흐름 복습" sub="체크박스 한 번의 왕복 여행">
        <p>
          네트워크 코스에서 데이터가 달리는 <strong>길</strong>을 배웠다면, 이번엔 그 길 위를
          달리는 <strong>내용물</strong>을 볼 차례예요. 여러분이 체크리스트에서 체크박스를
          딸깍하는 순간을 슬로모션으로 재생해 봅시다.
        </p>
        <Code>{CODE_FULL_FLOW}</Code>
        <p>
          ②의 <strong>Authorization 헤더</strong>가 핵심이에요. HTTP는 원래 기억력이 없어서
          (매 요청이 처음 보는 손님!) 요청마다 <strong>토큰이라는 신분증</strong>을 들고 가야
          "아, 아까 로그인한 그 사람" 하고 알아봅니다. 로그인할 때 받은 토큰을 브라우저가
          보관했다가 매 요청에 붙여 주는 거예요.
        </p>
        <p>
          그리고 ③을 다시 보세요 — 검사는 전부 <strong>서버</strong>가 해요. 화면에서 버튼을
          숨기는 건 예의일 뿐, 진짜 문지기는 Spring Boot입니다. "프론트는 믿지 않는다"는
          백엔드의 철칙, 다음 섹션의 에러 코드들이 바로 그 문지기의 대답이에요.
        </p>
        <svg className="diagram" viewBox="0 0 640 290" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="체크박스 클릭 한 번의 요청과 응답 왕복 흐름: 브라우저에서 서버로 PUT 요청이 가고 200 OK 응답이 돌아온다">
          <rect x="16" y="80" width="160" height="160" rx="10" fill="var(--card)" stroke="var(--line)" />
          <text x="96" y="110" textAnchor="middle" fontSize="14" fontWeight="700" fill="var(--ink)">브라우저</text>
          <text x="96" y="128" textAnchor="middle" fontSize="11" fill="var(--muted)">React</text>
          <text x="96" y="166" textAnchor="middle" fontSize="12" fill="var(--ink)">① 클릭 → fetch 실행</text>
          <text x="96" y="214" textAnchor="middle" fontSize="12" fill="var(--ink)">⑤ 체크박스 채움 ✓</text>
          <rect x="464" y="80" width="160" height="160" rx="10" fill="var(--card)" stroke="var(--line)" />
          <text x="544" y="110" textAnchor="middle" fontSize="14" fontWeight="700" fill="var(--ink)">서버</text>
          <text x="544" y="128" textAnchor="middle" fontSize="11" fill="var(--muted)">Caddy → Spring Boot</text>
          <text x="544" y="166" textAnchor="middle" fontSize="12" fill="var(--ink)">③ 토큰·권한 검사</text>
          <text x="544" y="188" textAnchor="middle" fontSize="12" fill="var(--ink)">PostgreSQL UPDATE</text>
          <text x="316" y="104" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--primary)">② 요청 (주문서)</text>
          <text x="316" y="124" textAnchor="middle" fontSize="12" fill="var(--ink)">PUT /api/checklists/3</text>
          <line x1="176" y1="138" x2="452" y2="138" stroke="var(--primary)" strokeWidth="2" />
          <polyline points="444,132 454,138 444,144" fill="none" stroke="var(--primary)" strokeWidth="2" strokeLinecap="round" />
          <text x="316" y="158" textAnchor="middle" fontSize="11" fill="var(--muted)">Authorization: Bearer … (신분증)</text>
          <text x="316" y="174" textAnchor="middle" fontSize="11" fill="var(--muted)">{'본문 { "done": true }'}</text>
          <text x="316" y="200" textAnchor="middle" fontSize="13" fontWeight="700" fill="var(--teal)">④ 응답 200 OK</text>
          <line x1="452" y1="210" x2="180" y2="210" stroke="var(--teal)" strokeWidth="2" />
          <polyline points="188,204 178,210 188,216" fill="none" stroke="var(--teal)" strokeWidth="2" strokeLinecap="round" />
          <text x="316" y="230" textAnchor="middle" fontSize="11" fill="var(--muted)">{'{ "id": 3, "done": true }'}</text>
          <text x="320" y="272" textAnchor="middle" fontSize="12" fill="var(--muted)">요청 = 메서드 + URL + 헤더 + (본문) · 응답 = 상태코드 + 헤더 + (본문)</text>
        </svg>
        <p className="muted" style={{ fontSize: 12.5, textAlign: 'center', marginTop: 2 }}>그림: 체크박스 클릭 한 번의 요청→응답 왕복 (①~⑤)</p>
      </Section>

      <Section n={6} title="에러 응답 — 401, 403, 404의 언어" sub="숫자 세 자리를 읽으면 디버깅이 절반">
        <p>
          주문이 항상 성공하는 건 아니에요. 서버는 거절할 때도 <strong>정해진 형식</strong>으로
          거절합니다 — 그게 상태코드예요. 특히 이 세 형제를 구분할 수 있으면, 에러가 났을 때
          어디부터 봐야 할지 바로 알 수 있어요.
        </p>
        <Code>{CODE_ERRORS}</Code>
        <div className="warn">
          <b>401과 403, 진짜 자주 헷갈려요</b> — <strong>401은 "인증" 실패</strong>(너 누구야?),{' '}
          <strong>403은 "인가" 실패</strong>(누군진 아는데 자격이 없어). 학교로 치면
          401은 학생증 없이 교문 통과 시도, 403은 학생증은 있지만 교무실 캐비닛을 열려는 것.
          면접 단골 질문이기도 하니 지금 확실히 잡아 두세요.
        </div>
        <p>
          좋은 API는 상태코드와 함께 <strong>본문에 이유도</strong> 담아 줘요.
          예를 들어 <span className="icode">{'{"error": "토큰이 만료되었습니다"}'}</span> 처럼요.
          숫자는 기계(프론트 코드의 분기 처리)를 위해, 메시지는 사람(디버깅하는 개발자)을 위해.
        </p>
      </Section>

      <Section n={7} title="직접 호출 실습 — curl과 fetch" sub="화면 없이 API만 콕 집어 불러 보기">
        <p>
          지금까지는 화면(버튼)을 통해 간접적으로 API를 불렀지만, 개발자는
          <strong> API만 따로 직접</strong> 호출할 줄 알아야 해요. 도구는 이미 여러분 PC에
          다 있습니다 — 터미널의 <strong>curl</strong>과 브라우저의 <strong>fetch</strong>.
        </p>
        <div className="tip">
          <b>직접 확인해 보기 ①</b> 터미널(PowerShell)을 열고 아래를 순서대로 실행해 보세요.
          맨 마지막 요청에서 <strong>일부러 401을 맞아 보는 것</strong>까지가 실습이에요 —
          에러 코드를 책이 아니라 내 터미널에서 처음 만나면 절대 안 잊어버립니다.
        </div>
        <Code>{CODE_CURL}</Code>
        <div className="tip">
          <b>직접 확인해 보기 ②</b> 이번엔 브라우저 차례. 우리 플랫폼에 <strong>로그인된 탭</strong>에서{' '}
          <span className="kbd">F12</span> → <span className="kbd">Console</span>을 열고
          아래 코드를 한 줄씩 실행해 보세요. 200, 404, (시크릿 창에서) 401 — 세 가지 응답을
          전부 직접 받아 보는 게 목표예요.
        </div>
        <Code>{CODE_FETCH}</Code>
        <p>
          보너스: <span className="kbd">F12</span> → <span className="kbd">Network</span> 탭을
          열어 둔 채 체크리스트 화면에서 체크박스를 딸깍해 보세요. 섹션 4 표에서 본{' '}
          <span className="icode">PUT /api/checklists/...</span> 요청이 진짜로 날아가는 걸
          목격할 수 있어요. 클릭해서 요청 헤더의 <strong>Authorization</strong>, 응답 탭의{' '}
          <strong>JSON 본문</strong>까지 확인하면 이 코스의 모든 조각이 한 화면에 모입니다.
        </p>
      </Section>

      <Section n={8} title="부록 — API 테스트 도구로 한 단계 더" sub="curl 다음은 GUI, 실무자의 작업대">
        <p>
          curl은 훌륭하지만 헤더·토큰·본문이 길어지면 명령어가 금방 지저분해져요.
          그래서 실무에서는 Postman 같은 <strong>API 테스트 도구</strong>를 작업대로 씁니다.
          개념은 이미 다 배웠어요 — 같은 요청을 폼에 채워 넣는 것뿐이에요.
        </p>
        <Code>{CODE_POSTMAN}</Code>
        <div className="tip">
          <b>부록 과제 제출</b> 위 4단계 중 <strong>3번(로그인 → 토큰 받기)</strong> 성공 화면을
          캡처해서 과제로 제출하세요. 토큰 값은 <strong>신분증</strong>이니까 가운데를 가리고
          캡처하는 것 잊지 말고요 — 시크릿을 다루는 습관도 채점 포인트입니다.
        </div>
      </Section>

      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🍽️ 여기까지 왔다면</h3>
          <p className="muted">
            이제 API는 <strong>메뉴판</strong>(정해진 요청·정해진 응답), REST는{' '}
            <strong>메뉴판 작성 문법</strong>(자원=URL, 행위=메서드), JSON은{' '}
            <strong>주문서의 언어</strong>, 401·403·404는 <strong>주방의 거절 멘트</strong>라는 걸
            알아요. 무엇보다 curl과 fetch로 <strong>직접 주문</strong>까지 넣어 봤죠.
            다음은 그 주문을 받는 주방 자체를 만들어 볼 차례 —{' '}
            <Link to="/learn/network"><strong>네트워크의 이해</strong></Link>를 아직 안 봤다면
            먼저 복습하고, 준비됐다면 백엔드(Spring Boot) 코스에서 여러분만의 API를
            직접 열어 보세요. 부록 과제 캡처 제출도 잊지 말고요!
          </p>
        </div>
      </div>
    </div>
  );
}
