Skip to Content
Native App내부 인원 전용 기능 (internal features)

내부 인원 전용 기능 (internal features)

apps/native-app프로덕션 앱 안에서 내부 인원에게만 보이는 기능을 구조화해서 다룬다(예: 지식마당 뉴스 파일럿). 산발적인 if (내부인원) 게이팅 대신 판단(hook) · 선언(registry) · 표시(indicator) 세 조각으로 나눈다.

ℹ️ devtools 빌드와 다르다. devtools 빌드는 공개 빌드에 코드가 아예 없는 별도 바이너리다. 여기서 다루는 “내부 인원 전용 기능”은 공개(스토어) 빌드에 그대로 들어 있고, 런타임에 내부 인원에게만 노출된다. 법적 계정 접근 같은 민감 기능은 devtools 빌드를, 파일럿·점진 공개 같은 것은 이 구조를 쓴다.

모듈: apps/native-app/src/features/internal/

”내부 인원”의 정의

내부 인원 = “한국프롭테크” 접두 집행부(region)에 인증 소유자로 소속된 유저. 사내 직원이 내부 검증용으로 이 집행부에 들어가 있다. 이메일/role 이 아니라 region 소속을 쓰는 이유는, dev/prod 환경마다 집행부 이름이 조금씩 달라도(연도·“테스트” 접미) 접두어 하나로 양쪽에서 같은 코드가 동작하기 때문이다.

판단은 useIsInternalUser() 한 곳에만 있다. 접두어를 바꾸거나 판단 기준을 바꿀 일이 생기면 useIsInternalUser.ts 만 고친다.

세 조각

조각파일역할
useIsInternalUser()useIsInternalUser.ts이 유저가 내부 인원인가(판단). suspense 쿼리
useRegisterInternalFeature()registry.ts화면에 내부 기능이 떠 있음을 선언(등록)
InternalIndicatorInternalIndicator.tsx우상단 코너 띠 표시 — _layout에 이미 마운트됨

InternalIndicator는 앱 루트(app/_layout.tsx)에 한 번 마운트돼 있으니, 새 기능을 추가할 때 인디케이터를 직접 다룰 일은 없다. 아래 두 가지만 하면 된다.

새 내부 전용 기능 추가하기

1. 노출을 useIsInternalUser로 게이트

내부 인원에게만 렌더할 부분을 이 hook 으로 감싼다. suspense 쿼리라 hook 호출 컴포넌트는 Suspense 경계 안에 있어야 한다 — 라우트 컴포넌트에서 직접 호출하지 말고 경계 안쪽 컴포넌트로 분리한다.

import {Suspense} from 'react'; import {useIsInternalUser} from '@/features/internal'; function KnowledgeSpaceScreen() { // useIsInternalUser 는 suspense 쿼리 → 경계 안쪽에서 호출 return ( <Suspense fallback={<Loading />}> <KnowledgeSpaceContent /> </Suspense> ); } function KnowledgeSpaceContent() { const isInternal = useIsInternalUser(); // 내부 인원이 아니면 뉴스 서브탭 없이 기존 화면 그대로 return isInternal ? <WithNewsTab /> : <QuizOnly />; }

2. 그 컴포넌트 안에서 useRegisterInternalFeature로 등록

내부 전용 컴포넌트(= 위 게이트 뒤에서만 렌더되는 컴포넌트)의 본문에서 호출한다. 이러면 그 화면을 보고 있는 동안만 인디케이터가 “내부 기능 있음”을 표시한다.

import {useRegisterInternalFeature} from '@/features/internal'; export default function NewsBriefList() { useRegisterInternalFeature({ id: 'news-brief', label: 'AI 뉴스 요약', description: '한국프롭테크 집행부 인증 소유자 대상 파일럿. 전사 롤아웃 시 일반 공개 예정.', }); // ... }

id는 안정적인 문자열(해제 키), label·description은 인디케이터 시트에 그대로 보인다. 끝이다 — 인디케이터는 자동으로 뜬다.

인디케이터가 뜨는 조건

우상단 코너 띠(InternalIndicator)는 다음 AND일 때만 보인다:

// InternalIndicator.tsx if (!isInternal || features.length === 0) return null;
  • isInternaluseIsInternalUser() (내부 인원인가)
  • features.length > 0 — 현재 포커스된 화면에 등록된 내부 기능이 1개 이상인가

띠 on/off 를 실제로 만드는 것은 등록/해제다. 띠를 탭하면 그 화면에 등록된 기능 목록(label + description)이 바텀시트로 뜬다.

등록은 마운트가 아니라 “포커스” 기준

useRegisterInternalFeatureuseEffect가 아니라 **useFocusEffect**로 등록한다. 바텀탭 네비게이터는 다른 탭으로 가도 이전 탭 화면을 언마운트하지 않고 유지하므로, 마운트 기준이면 다른 탭에서도 띠가 계속 뜬다. 포커스 기준이면 “지금 보고 있는 화면”의 기능만 집계된다.

  • 화면 포커스 → 등록 → 띠
  • 다른 탭/화면으로 이동(blur) → 해제 → 띠 사라짐
  • 컴포넌트 언마운트(예: 서브탭 전환) → 해제

주의

  • Suspense: useIsInternalUsergetMyRegions suspense 쿼리라 호출부를 Suspense로 감싼다. InternalIndicator_layout에서 Suspense fallback={null}로 감싸 마운트돼 있다.
  • 전사 공개(GA) 시: 파일럿 게이트를 걷을 땐 그 기능의 useIsInternalUser 게이트만 제거하면 된다(등록은 그대로 두거나 함께 제거). 판단 로직은 useIsInternalUser 한 곳이라 파급이 작다.
  • 일반 유저 영향 없음: 내부 컴포넌트가 게이트 뒤에서만 렌더되면 일반 유저에겐 등록 자체가 일어나지 않아, 띠도 뜨지 않는다.

관련 티켓: HMH-9237.

Last updated on