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
권장 경로는 brandapp-sdk-init 스킬로 lib/auth.ts, lib/auth-client.ts, auth route, error helper를 생성하는 것입니다. 수동 구성 시 최소 파일은 아래와 같습니다.
// 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를 엽니다.