brandapp-ui 시작하기
brandapp-sdk foundation을 먼저 만들고 opt surface add로 필요한 UI 소스만 복사하는 빠른 시작 가이드입니다.
reopt designUpdated
1. 설치 모델
brandapp-ui는 npm runtime component가 아니라 copy-paste Surface source입니다. Surface는 소비자 프로젝트의 @/lib/auth-client, API route, opt-ui theme를 전제로 동작합니다.
SDK가 로직 소유
OAuth, session, EAV, AI credit, typed error는 @reopt-ai/brandapp-sdk가 담당합니다.
Surface는 조립 소유
Button, Avatar, DataTable, opt-chat 등을 제품 UI로 묶습니다.
소비자가 최종 소유
복사 후 copy, loading, empty, server route 정책을 프로젝트 기준으로 수정합니다.
2. 환경 변수
Reopt Studio에서 BrandApp과 OAuth client를 만들고, consumer app의 환경 변수에 client id, client secret, brandapp id를 설정합니다. secret은 서버 전용입니다.
# apps/web/.env.local
BRANDAPP_CLIENT_ID=...
BRANDAPP_CLIENT_SECRET=...
BRANDAPP_ID=...
BETTER_AUTH_SECRET=...
BETTER_AUTH_URL=http://localhost:30003. SDK foundation
권장 설정: 선행 스킬
Surface 자체를 설치하는 스킬은 아닙니다. 먼저 Brandapp SDK, 환경 변수, auth route와 auth client 기반을 만들고, 이후 opt CLI로 필요한 Surface를 복사합니다.
소비자 프로젝트 루트에서 스킬을 에이전트 런타임에 설치합니다. 이미 설치했다면 이 명령은 생략할 수 있습니다.
npx skills add reopt-ai/reopt-skills/brandapp-sdk-install설치 후 에이전트에게 아래처럼 요청하세요. 스킬은 현재 설치 상태를 확인해 신규 설치와 업그레이드를 구분합니다.
brandapp-sdk-install 스킬로 brandapp-ui의 선행 조건인 @reopt-ai/brandapp-sdk 기반을 설정하고 결과까지 검증해줘.스킬은 소비자 프로젝트의 AGENTS.md(없으면 CLAUDE.md)에 reopt marker 블록을 멱등하게 갱신하고, 설치된 패키지 버전의 문서를 읽은 뒤 typecheck·doctor 등 해당 모듈의 검증을 실행합니다.
아래 패키지 설치 명령은 스킬을 사용할 수 없거나 설정 과정을 직접 통제해야 할 때의 수동 대안입니다. 스킬 소스와 최신 지원 범위 확인.
권장 경로는 brandapp-sdk-install 스킬로 환경, SDK, auth route와 auth client 기반을 구성하는 것입니다. 수동 구성 시 최소 파일은 아래와 같습니다.
// lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
import { createReoptBetterAuth } from "@reopt-ai/brandapp-sdk/better-auth";
const reopt = createReoptBetterAuth({
clientId: process.env.BRANDAPP_CLIENT_ID!,
clientSecret: process.env.BRANDAPP_CLIENT_SECRET!,
brandappId: process.env.BRANDAPP_ID!,
additionalScopes: ["offline_access"],
});
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
secret: process.env.BETTER_AUTH_SECRET!,
database: reopt.database,
plugins: [nextCookies(), reopt.oauth],
});// lib/auth-client.ts
"use client";
import { createAuthClient } from "better-auth/react";
import {
createReoptOAuthClient,
REOPT_PROVIDER_ID,
} from "@reopt-ai/brandapp-sdk/better-auth/client";
export const authClient = createAuthClient({
plugins: [createReoptOAuthClient()],
});
export async function signInWithReopt(callbackURL = "/") {
return authClient.signIn.oauth2({
providerId: REOPT_PROVIDER_ID,
callbackURL,
});
}
export async function signOut() {
await authClient.signOut();
}// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";
export const { GET, POST } = toNextJsHandler(auth.handler);4. Surface 설치
필요한 Surface만 복사합니다. 인증 기본 세트는 4개이고, AI/EAV 화면은 서버 계약을 먼저 확인한 뒤 추가합니다.
opt surface add sign-in-with-reopt-button
opt surface add reopt-user-menu
opt surface add sign-in-gate
opt surface add session-expired-dialog
# AI/EAV 화면까지 바로 시작할 때
opt surface add reopt-ai-chat reopt-ai-image-studio reopt-record-table5. Header와 보호 화면에 연결
// app/(app)/layout.tsx 또는 Header 컴포넌트
import { ReoptUserMenu } from "@/components/reopt-user-menu";
import { SessionExpiredDialog } from "@/components/session-expired-dialog";
export function AppHeader() {
return (
<header className="flex items-center justify-between">
<span>Workspace</span>
<ReoptUserMenu callbackURL="/dashboard" />
<SessionExpiredDialog callbackURL="/dashboard" />
</header>
);
}// 보호된 페이지를 SignInGate로 감싸기
import { SignInGate } from "@/components/sign-in-gate";
export default function BillingPage() {
return (
<SignInGate
title="결제 정보는 로그인이 필요합니다"
description="구독 상태를 보려면 Reopt 계정으로 로그인해주세요."
callbackURL="/billing"
>
<BillingDashboard />
</SignInGate>
);
}6. 현재 Surface
전체 라이브 프리뷰는 /explore/brandapp-ui에서 확인합니다.
Auth & session
SignInWithReoptButton
authClient.signIn.oauth2()를 감싼 단일 CTA. ReoptSignInError code를 토스트 copy로 매핑하고 retry/help action을 제공합니다.
ReoptUserMenu
authClient.useSession() 상태에 따라 SkeletonAvatar, 로그인 버튼, Avatar + Dropdown 로그아웃 메뉴를 분기합니다.
SignInGate
보호된 화면을 children으로 감싸고 pending, signed-out, signed-in 상태를 분기합니다. fallback/loadingFallback으로 제품별 UI를 덮어쓸 수 있습니다.
SessionExpiredDialog
세션 truthy to falsy 전이 또는 reopt:session-expired window event를 받아 전역 재로그인 dialog를 엽니다.