- 전수 순회: IA 전 라우트 412개 기능 인벤토리를 체크리스트 백로그로 관리 (docs/ops/e2e-full-sweep-2026-07-27.md), 신규 full-sweep 스펙 10파일 추가. RED→GREEN으로 결함 12건 수정: 관리자 무한 렌더 프리즈(TanStack autoReset 루프), 복수 코호트 저장 유실, 페르소나 보관 503(SQL 컬럼 모호성), PII 과잉 마스킹, /admin/ai watchdog 오탐 오버레이, 모바일 겹침 2건, 설정 스크롤 스파이, 온보딩 전화번호 무검증, 리뷰 조사·난도 라벨, pending 피드백 등. - 소유자 결정 구현: 설정 아바타 변경, 동의 철회·재동의 전체 흐름, 신규 학습자 기초 우선 추천, 학생 분석 테이블 가상화(@tanstack/react-virtual), 저작 모드 죽은 레일 정리, 감정 밸런스 타임라인 차트(deep turn_valence + 결정론 파생 폴백). - 디자인 감사(142차): 라이트 팔레트 AA 대비, 다크 토큰 별칭 통일, 미정의 CSS 변수 정리, 한글 keep-all 전역화, 탭 타깃 24px, LCP preconnect. - 검증: npm run e2e 병렬 432 수집 GREEN + 직렬 49/49 exit 0, 백엔드 pytest 421, gateway 29, typecheck/build/design-ssot/dead-code/중복 게이트 통과, layout-visual-gate 15/15, session-layout 8/8. 상세는 SSOT 대시보드 142~144차 노트.
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Button, Icon } from "../components/ui";
|
|
import { surfaceClassName } from "../components/ui/Surface";
|
|
import { AuthShell } from "../components/auth/AuthShell";
|
|
import { roleHomePath, useAuth } from "../lib/auth";
|
|
import "./pending-approval.css";
|
|
|
|
export default function PendingApproval() {
|
|
const navigate = useNavigate();
|
|
const { user, logout, refresh } = useAuth();
|
|
const [checking, setChecking] = useState(false);
|
|
const [checkResult, setCheckResult] = useState<string | null>(null);
|
|
|
|
const checkAgain = async () => {
|
|
if (checking) return;
|
|
setChecking(true);
|
|
setCheckResult(null);
|
|
try {
|
|
const nextUser = await refresh();
|
|
if (nextUser?.accountStatus === "approved") {
|
|
navigate(
|
|
nextUser.onboardingCompletedAt == null ? "/onboarding" : roleHomePath(nextUser.role),
|
|
{ replace: true },
|
|
);
|
|
return;
|
|
}
|
|
const at = new Date().toLocaleTimeString("ko-KR", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
});
|
|
if (nextUser) {
|
|
setCheckResult(`${at} 기준 아직 승인 전입니다. 잠시 후 다시 확인해 주세요.`);
|
|
} else {
|
|
setCheckResult(`${at} 상태 확인에 실패했습니다. 네트워크 연결 후 다시 시도해 주세요.`);
|
|
}
|
|
} finally {
|
|
setChecking(false);
|
|
}
|
|
};
|
|
|
|
const signOut = async () => {
|
|
await logout();
|
|
navigate("/login", { replace: true });
|
|
};
|
|
|
|
const statusText = user?.accountStatus === "suspended" ? "접속 보류" : "승인 대기";
|
|
|
|
return (
|
|
<>
|
|
<AuthShell className="pa-page" aria-label="계정 승인 대기">
|
|
<section className={surfaceClassName("pa-panel")}>
|
|
<div className="pa-mark" aria-hidden="true">
|
|
<Icon name="shield" size={30} strokeWidth={1.9} />
|
|
</div>
|
|
<p className="pa-kicker">{statusText}</p>
|
|
<h1>계정 확인이 끝나면 바로 이용할 수 있습니다.</h1>
|
|
<p className="pa-copy">
|
|
{user?.email ? <b>{user.email}</b> : "현재 계정"}으로 가입 요청이 접수되었습니다.
|
|
운영자가 수업 또는 연구 참여 범위를 확인하는 동안 Vignette 화면은 열리지 않습니다.
|
|
</p>
|
|
<div className="pa-status" role="status">
|
|
<span aria-hidden="true" />
|
|
<div>
|
|
<b>접속 권한 확인 중</b>
|
|
<small>
|
|
{checkResult ??
|
|
"승인되면 새로고침 후 온보딩 또는 역할 홈으로 이동합니다."}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div className="pa-actions">
|
|
<Button
|
|
type="button"
|
|
size="lg"
|
|
leading={<Icon name="refresh" size={16} />}
|
|
onClick={() => void checkAgain()}
|
|
disabled={checking}
|
|
>
|
|
{checking ? "확인 중" : "승인 상태 새로고침"}
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
size="lg"
|
|
variant="secondary"
|
|
leading={<Icon name="logout" size={16} />}
|
|
onClick={() => void signOut()}
|
|
>
|
|
다른 계정으로 로그인
|
|
</Button>
|
|
</div>
|
|
</section>
|
|
</AuthShell>
|
|
</>
|
|
);
|
|
}
|