지금까지는 정답으로 고른 쪽만 문구를 정할 수 있었고(나머지 줄은 비활성) 한 벌만
나갔다. 두 줄 모두 항상 편집할 수 있게 하고 둘 다 전송한다 — 정답이 아닌 쪽에도
보여 줄 문구가 필요하다.
정답 라디오는 그대로 둔다. 어느 쪽이 정답인지는 여전히 그것이 정한다.
백엔드가 아직 이 필드를 모른다(작업 중). 지금 보내도 폼은 미지의 파라미터를, JSON은
미지의 속성을 버리므로 조용히 무시된다. 이름이 확정되면 QUIZ_ANSWER_MESSAGE_FIELD
한 곳만 고치면 된다.
Co-Authored-By: Claude Opus 5
호버 규칙은 있었는데 색을 ``에 주고 있었다. 칸이 불투명한 바탕(surface-neutral-
default)을 그려서 그 위를 덮어, 누를 수 있는 줄에 마우스를 올려도 아무 변화가 없었다.
고른 줄이 제 색을 지키도록 그 줄은 비켜 간다 — 둘 다 자식 선택자라 순서로만 갈려서,
안 비키면 고른 줄에 마우스를 올릴 때 파란색이 회색으로 죽는다.
Co-Authored-By: Claude Opus 5
넷 다 연결할 기능이 없어 눌러도 아무 일이 없던 자리다. 사용자 아이콘은 바로 옆
프로필 pill과 역할도 겹쳤다.
즐겨찾기는 화면뿐 아니라 도메인·mock까지 걷어낸다 — 그리는 곳이 사이드바뿐이라
남겨 두면 죽은 데이터가 된다.
알림 버튼과 프로필 pill은 남긴다. pill은 지금 유일한 로그아웃 경로다.
Co-Authored-By: Claude Opus 5
`inset-inline: 0`으로 양쪽을 트리거에 묶어 놓아, "기타"처럼 짧은 값이 든 좁은
셀렉트에서는 "초등학교"가 글자 단위로 두 줄이 됐다. 목록의 폭은 트리거가 아니라
항목이 정해야 한다.
트리거보다 좁아지지는 않게 두고(min-inline-size: 100%) 가장 긴 항목에 맞춰
늘어나게 한다. 넓은 셀렉트는 그대로다.
Co-Authored-By: Claude Opus 5
폼이 든 모달이 바깥 클릭으로 닫히면 적어 둔 값이 통째로 사라진다. 조회 모달도
표를 훑다 여백을 눌러 닫히는 일이 잦다.
알림·확인(feedback-host)과 중복 로그인 안내는 그대로 뒀다 — 읽고 넘기는 자리라
바깥 클릭이 곧 취소다.
Co-Authored-By: Claude Opus 5
백엔드 요청 VO에서 quizTitle·quizPoint가 사라지고 keywordList가 들어왔다.
- 키워드는 쉼표로 이은 문자열 대신 목록으로 읽고 보낸다. 항목마다 설명을 갖는데
입력 칸은 공용 키워드 필드에 이미 있었고, 읽을 때만 유실되고 있었다(사용자 확정).
- 포인트는 저장되지 않으므로 숨김 칸과 폼 값에서 뺀다(사용자 확정). 되돌릴 문자열이
없어진 keywordsFromText도 함께 지운다.
Co-Authored-By: Claude Opus 5
백엔드가 콘텐츠 키를 cntntsSn -> cntntsId로, 퀴즈 필드를 quiz* -> cntntsQuiz*로
바꿨다(퀴즈 식별자도 quizSn -> cntntsId). 우리 쪽은 응답에서 옛 이름을 찾다 던지고
있어 금융쇼츠·4컷만화·금융OX퀴즈 목록이 전부 열리지 않는 상태였다.
누적포인트 목록 검색도 searchUserId -> searchLoginId로 바뀌어 맞춘다.
라우트 폴더 이름도 함께 바꿨다 — 동적 세그먼트라 주소에는 값만 나가므로 URL은
그대로다.
Co-Authored-By: Claude Opus 5
Tailwind가 프로젝트에서 빠진 뒤에도 클래스만 남아 아무 스타일도 받지 못하던 자리들이
있었고, 전역 스피너는 만들어만 두고 어디서도 호출하지 않았다. 두 손질이 같은 파일들을
건드려 한 커밋으로 둔다.
Tailwind 제거:
- components/ui의 옛 Tailwind 레이어 16개 삭제. 그중 field·icon-button만 실제로 쓰여
각각 FoxTextArea·FoxFormLabel과 @fox 아이콘 버튼 마크업으로 옮기고 지웠다.
- error.tsx 다섯 곳이 같은 모양이라 PageError로 모은다(PageLoading과 같은 이유).
- 홈·Empty 레이아웃·게시판 첨부 링크는 module.scss로, 행 관리 버튼 묶음은 FoxButtonGroup로.
- 테마 토글은 FoxIconButton을 쓰지 못한다 — label이 필수라 aria-label이 늘 붙고, 그러면
테마별로 바뀌는 안쪽 글자를 스크린리더가 읽지 못한다. @fox 아이콘 버튼의 마크업 계약을
그대로 따라 같은 스타일을 받게 했다.
스피너 호출:
- useBlockingAction — 스피너를 켠 채 도는 transition. 삭제처럼 확인 얼럿이 닫힌 뒤
진행돼 표시가 행 아이콘 하나뿐이던 자리 11곳에 붙였다. 버튼이 스스로 "저장 중"을
보여주는 자리(중복확인·주소검색·엑셀 업로드)는 그대로 뒀다.
- 스피너 막도 토스트와 같은 이유로 popover(top layer)에 얹는다 — 모달 안에서 시작한
동작도 덮어야 한다.
- loading.tsx를 (basic) 한 곳으로 모은다. 다섯 화면에만 있던 것을 모든 하위 화면이
받도록 하고, 새 화면을 추가해도 자동으로 따라오게 한다.
Co-Authored-By: Claude Opus 5
지금까지는 수정 버튼이 "백엔드 준비 중" 토스트만 띄워 등록된 내용을 볼 방법이
없었다. 모달을 열되 값은 바꿀 수 없게 한다.
`IS_EDIT_ENABLED` 한 줄에 전부 걸어 뒀다 — 수정 API가 생기면 그것만 true로 바꾸면
입력칸도 저장 버튼도 그대로 살아난다. 폼 로직은 손대지 않았다.
Co-Authored-By: Claude Opus 5
폼 맨 아래 FoxHelperText에 있던 문구를 토스트로 올린다 — 등록 폼이 길어서 스크롤 밖에
있으면 저장이 왜 안 됐는지 보이지 않았다. 칸별 오류는 해당 입력칸에 그대로 둔다.
메시지도 백엔드 사유를 그대로 쓴다(형제 화면의 failureMessage와 같은 방식) — 고정 문구는
"저장하지 못했습니다"밖에 알려 주지 못했다.
Co-Authored-By: Claude Opus 5
components/ui/toast.tsx와 FeedbackHost가 bg-surface·shadow-toast·fixed inset-x-0
같은 Tailwind 클래스로 그려져 있었는데 이 프로젝트에는 Tailwind가 없다. 정의된 곳이
없어 토스트가 문서 맨 아래에 12px짜리 투명한 줄로 붙었다 — showToast를 부르는 39곳이
전부 아무것도 보이지 않는 상태였다.
- @fox의 FoxToast로 그린다(사용자 확정). 계열 색은 시안에 없어 모양이 하나다. 대신
danger만 urgent로 올려 보조기술이 읽던 것을 끊고 바로 읽게 한다.
- 위치는 호출부 책임이라(@fox 규칙) feedback-host.module.scss가 상단 가운데로 잡는다.
- FoxModal은
관리자 회원을 회원관리 아래로, 금칙어관리·가맹점 관리를 관리자관리 아래로 옮기고
「회원정보관리 → 회원관리」·「관리자정보관리 → 관리자관리」로 이름을 바꿨다.
각 화면의 breadcrumb이 메뉴 경로를 그대로 되풀이하고 있어 여섯 곳을 같이 고쳤다 —
안 고치면 메뉴는 관리자관리인데 화면은 시스템관리라고 말하게 된다.
콘텐츠관리 아이콘이 기본값으로 떨어지던 것도 함께 고쳤다. 이름 `image`가 사이드바
허용 목록에 빠져 있었다.
Co-Authored-By: Claude Opus 5
백엔드가 TB_ADM_USER 계열 이름을 adm* 에서 user* 로 바꿨다(e0ae7fb).
프론트가 옛 이름을 읽어 admUserId가 없다며 목록이 통째로 예외를 냈다.
admUserId->userId, admNm->userNm, admTelNo->userTelNo,
admEmlAddr->userEmlAddr, admRoleCd->roleId. 등록(form)·수정(JSON) 경로도 같다.
Co-Authored-By: Claude Opus 5
백엔드가 중복 로그인 감지를 넣으면서, 내 토큰이 밀리면 이후 모든 요청이
403 + code 905(만료된 토큰입니다)로 떨어진다(JwtAuthenticationFilter).
어느 화면·어느 API에서 걸리든 같은 결말이어야 해서 backendFetch 한 곳에서
처리한다.
- 905/904를 받으면 /logout/revoked로 보낸다. 렌더 중에는 쿠키를 지울 수 없어
라우트 핸들러에서 세션을 파기하고 /login으로 안내와 함께 되돌린다.
- redirect()는 예외로 던져지므로 Server Action의 catch가 삼키면 안 된다.
저장소를 호출하는 catch에 unstable_rethrow를 넣어 통과시킨다.
- 그 경로는 쿠키가 이미 없어도 지나가야 안내가 유지돼 proxy 공개 경로에 넣는다.
Co-Authored-By: Claude Opus 5
백엔드가 /api/v1/common/auth/admin/login을 지우고 /api/v1/mngr/auth/login으로
옮겼다(4b0f0ba). 구 경로는 실제로 404를 반환해 로그인이 전혀 되지 않는 상태였다.
- 로그인 POST /api/v1/mngr/auth/login, 로그아웃 POST /api/v1/mngr/auth/logout
- accessToken 클레임이 adminId/loginId/admRoleCd -> userId/userNm/roleId로 바뀌었다.
loginId는 더 이상 토큰에 없어 입력값을 세션에 담는다. 이름 클레임(userNm)이
생겨 그동안 loginId로 대체하던 표시 이름을 실제 이름으로 되돌린다.
- 중복 로그인: 살아 있는 세션이 있으면 200인데 토큰 대신 최근 접속 정보만 온다.
FoxModal로 안내하고 [로그인]을 누르면 isForceLogin=true로 재요청한다. 재요청에
아이디·비밀번호가 남아 있어야 해서 폼 리셋을 막고 액션을 직접 호출한다.
- 실패 문구는 백엔드 것을 그대로 쓴다(잠김·탈퇴·실패횟수 안내). 다만 404
"로그인 계정이 없습니다"만 일반 문구로 바꿔 계정 존재 여부를 드러내지 않는다.
Co-Authored-By: Claude Opus 5
globals.scss가 `*`에 건다. 스크롤이 어디에 생기는지 미리 알 수 없어서다 — 문서 자체,
셸 본문, 사이드바, 표 래퍼, 화면이 나중에 만드는 상자까지 한 번에 덮인다. 특이도가
0,0,0이라 어느 자리든 자기 규칙으로 덮을 수 있다.
@fox 컴포넌트 넷의 `fox.scroll()` 호출은 겹치지만 남긴다 — @fox는 이 파일 없이 다른
프로젝트로 복사돼도 제 모양이 나와야 한다.
카탈로그의 "붙이지 않았을 때" 예시는 뺐다. 전역이 된 뒤로는 같은 모양이라 오해만 준다.
Co-Authored-By: Claude Opus 5
시안 scroll(7341:2675)이다. 컴포넌트가 아니라 스크롤이 생기는 요소에 입히는
외형이라 `fox.scroll()` 믹스인과 `.fox-scroll` 클래스로 냈다.
둘레 4px 여백은 트랙 padding이 아니라 막대의 투명 테두리로 만든다 — padding으로
주면 막대가 트랙 끝까지 가지 못한다. Chrome 121+가 표준 속성을 보면 웹킷 규칙을
통째로 무시하므로 Firefox 대비는 @supports로 갈라 놓았다.
이미 스크롤이 있던 @fox 컴포넌트 넷(셀렉트 목록·칩 셀렉트·모달·파일목록)에 적용했다.
Co-Authored-By: Claude Opus 5
세 가지가 겹쳤다. 파일업로드 칸이 `flex-shrink: 0`이라 좁은 그릇에서 버티고,
모달 안 form이 `align-items: flex-start`에 걸려 폭을 안 채우고, 모달은
`overflow-x: hidden`이라 넘친 만큼 그대로 잘렸다. 400px 모달에서 191px가 잘렸다.
칸은 늘어나지는 않고 줄어들기만 하게 두어 넓은 자리에서는 시안의 320px를 지킨다.
팝업은 md로 올려 그 320px가 그대로 들어간다.
Co-Authored-By: Claude Opus 5