@volr/react-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/dist/index.cjs +3141 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +236 -0
- package/dist/index.d.ts +236 -0
- package/dist/index.js +3124 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# @volr/react-ui 사용 가이드
|
|
2
|
+
|
|
3
|
+
Volr의 UI 컴포넌트 라이브러리입니다. 미니멀하고 모던한 디자인의 로그인 모달을 제공합니다.
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @volr/react-ui @volr/react
|
|
9
|
+
# 또는
|
|
10
|
+
yarn add @volr/react-ui @volr/react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 기본 설정
|
|
14
|
+
|
|
15
|
+
### 1. VolrUIProvider로 앱 래핑
|
|
16
|
+
|
|
17
|
+
앱의 최상위 레벨에서 `VolrUIProvider`로 앱을 래핑합니다.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { VolrUIProvider } from '@volr/react-ui';
|
|
21
|
+
import { VolrConfig } from '@volr/react';
|
|
22
|
+
|
|
23
|
+
const volrConfig: VolrConfig = {
|
|
24
|
+
apiBaseUrl: 'https://api.volr.dev',
|
|
25
|
+
defaultChainId: 1,
|
|
26
|
+
projectApiKey: 'your-project-api-key',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
return (
|
|
31
|
+
<VolrUIProvider
|
|
32
|
+
config={volrConfig}
|
|
33
|
+
accentColor="#3b82f6" // 버튼, 링크 등 강조 요소에 사용되는 색상
|
|
34
|
+
enabledLoginMethods={['email', 'social', 'siwe']} // 활성화할 로그인 방식
|
|
35
|
+
socialProviders={['google', 'twitter', 'apple']} // 활성화할 소셜 제공자
|
|
36
|
+
>
|
|
37
|
+
<YourApp />
|
|
38
|
+
</VolrUIProvider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 2. LoginModal 사용
|
|
44
|
+
|
|
45
|
+
로그인 모달을 표시하려면 `LoginModal` 컴포넌트를 사용합니다.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useState } from 'react';
|
|
49
|
+
import { LoginModal } from '@volr/react-ui';
|
|
50
|
+
|
|
51
|
+
function LoginButton() {
|
|
52
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<>
|
|
56
|
+
<button onClick={() => setIsOpen(true)}>로그인</button>
|
|
57
|
+
|
|
58
|
+
<LoginModal
|
|
59
|
+
isOpen={isOpen}
|
|
60
|
+
onClose={() => setIsOpen(false)}
|
|
61
|
+
onError={(error) => {
|
|
62
|
+
console.error('로그인 오류:', error);
|
|
63
|
+
// 에러 처리 로직
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 설정 옵션
|
|
72
|
+
|
|
73
|
+
### VolrUIProvider Props
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Default | Description |
|
|
76
|
+
|------|------|---------|-------------|
|
|
77
|
+
| `config` | `VolrConfig` | **필수** | Volr 설정 (apiBaseUrl, defaultChainId, projectApiKey) |
|
|
78
|
+
| `accentColor` | `string` | `'#303030'` | 버튼, 링크 등 강조 요소에 사용되는 색상 |
|
|
79
|
+
| `appName` | `string` | `undefined` | 앱 이름 |
|
|
80
|
+
| `enabledLoginMethods` | `('email' \| 'social' \| 'siwe')[]` | `['email', 'social', 'siwe']` | 활성화할 로그인 방식 |
|
|
81
|
+
| `socialProviders` | `('google' \| 'twitter' \| 'apple')[]` | `['google', 'twitter', 'apple']` | 활성화할 소셜 제공자 |
|
|
82
|
+
| `volrLogoUrl` | `string` | `undefined` | Volr 로고 URL |
|
|
83
|
+
| `providerPolicy` | `object` | `{ enforceOnFirstLogin: true }` | Provider 정책 설정 |
|
|
84
|
+
|
|
85
|
+
### LoginModal Props
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
|------|------|---------|-------------|
|
|
89
|
+
| `isOpen` | `boolean` | **필수** | 모달 열림/닫힘 상태 |
|
|
90
|
+
| `onClose` | `() => void` | **필수** | 모달 닫기 콜백 |
|
|
91
|
+
| `onError` | `(error: Error) => void` | `undefined` | 에러 발생 시 콜백 |
|
|
92
|
+
|
|
93
|
+
## 사용 예시
|
|
94
|
+
|
|
95
|
+
### 기본 사용
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { useState } from 'react';
|
|
99
|
+
import { LoginModal } from '@volr/react-ui';
|
|
100
|
+
|
|
101
|
+
function App() {
|
|
102
|
+
const [showLogin, setShowLogin] = useState(false);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<button onClick={() => setShowLogin(true)}>로그인</button>
|
|
107
|
+
<LoginModal
|
|
108
|
+
isOpen={showLogin}
|
|
109
|
+
onClose={() => setShowLogin(false)}
|
|
110
|
+
/>
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 이메일 로그인만 활성화
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<VolrUIProvider
|
|
120
|
+
config={volrConfig}
|
|
121
|
+
accentColor="#6366f1"
|
|
122
|
+
enabledLoginMethods={['email']}
|
|
123
|
+
>
|
|
124
|
+
<App />
|
|
125
|
+
</VolrUIProvider>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Google과 Apple만 활성화
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<VolrUIProvider
|
|
132
|
+
config={volrConfig}
|
|
133
|
+
accentColor="#8b5cf6"
|
|
134
|
+
enabledLoginMethods={['social']}
|
|
135
|
+
socialProviders={['google', 'apple']}
|
|
136
|
+
>
|
|
137
|
+
<App />
|
|
138
|
+
</VolrUIProvider>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 커스텀 에러 처리
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
function App() {
|
|
145
|
+
const [showLogin, setShowLogin] = useState(false);
|
|
146
|
+
const [error, setError] = useState<string | null>(null);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<>
|
|
150
|
+
{error && (
|
|
151
|
+
<div style={{
|
|
152
|
+
padding: '12px',
|
|
153
|
+
backgroundColor: '#fef2f2',
|
|
154
|
+
color: '#991b1b',
|
|
155
|
+
borderRadius: '8px',
|
|
156
|
+
marginBottom: '16px'
|
|
157
|
+
}}>
|
|
158
|
+
{error}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
<button onClick={() => setShowLogin(true)}>로그인</button>
|
|
163
|
+
|
|
164
|
+
<LoginModal
|
|
165
|
+
isOpen={showLogin}
|
|
166
|
+
onClose={() => {
|
|
167
|
+
setShowLogin(false);
|
|
168
|
+
setError(null);
|
|
169
|
+
}}
|
|
170
|
+
onError={(err) => {
|
|
171
|
+
setError(err.message);
|
|
172
|
+
// 3초 후 에러 메시지 자동 숨김
|
|
173
|
+
setTimeout(() => setError(null), 3000);
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 로그인 플로우
|
|
182
|
+
|
|
183
|
+
### 이메일 로그인
|
|
184
|
+
1. 사용자가 "Email Login" 버튼 클릭
|
|
185
|
+
2. 이메일 주소 입력 화면 표시
|
|
186
|
+
3. 인증 코드 전송
|
|
187
|
+
4. 6자리 코드 입력 화면 표시
|
|
188
|
+
5. 코드 검증
|
|
189
|
+
6. **신규 사용자**: Passkey 설정 화면으로 이동
|
|
190
|
+
7. **기존 사용자**: 성공 화면으로 이동
|
|
191
|
+
|
|
192
|
+
### 소셜 로그인 (Google, Twitter, Apple)
|
|
193
|
+
1. 사용자가 소셜 로그인 버튼 클릭
|
|
194
|
+
2. OAuth 페이지로 리다이렉트
|
|
195
|
+
3. 소셜 계정으로 로그인 및 권한 승인
|
|
196
|
+
4. 백엔드 콜백 처리 후 프론트엔드로 리다이렉트
|
|
197
|
+
5. **신규 사용자**: Passkey 설정 화면으로 이동
|
|
198
|
+
6. **기존 사용자**: 성공 화면으로 이동
|
|
199
|
+
|
|
200
|
+
### 지갑 로그인 (SIWE)
|
|
201
|
+
1. 사용자가 "Wallet Login" 버튼 클릭
|
|
202
|
+
2. MetaMask 또는 다른 지갑 연결
|
|
203
|
+
3. SIWE 메시지에 서명
|
|
204
|
+
4. 백엔드에서 서명 검증
|
|
205
|
+
5. **신규 사용자**: Passkey 설정 화면으로 이동
|
|
206
|
+
6. **기존 사용자**: 성공 화면으로 이동
|
|
207
|
+
|
|
208
|
+
## Passkey 설정
|
|
209
|
+
|
|
210
|
+
신규 사용자의 경우 자동으로 Passkey 지갑이 생성됩니다:
|
|
211
|
+
|
|
212
|
+
1. **Passkey 생성**: WebAuthn API를 사용하여 플랫폼 인증자(생체 인증) 생성
|
|
213
|
+
2. **Wrap Key 파생**: Passkey PRF에서 Wrap Key 생성
|
|
214
|
+
3. **Master Seed 생성 및 암호화**:
|
|
215
|
+
- 32바이트 무작위 Master Seed 생성
|
|
216
|
+
- Wrap Key로 AES-256-GCM 암호화
|
|
217
|
+
4. **S3 업로드**: 암호화된 Master Seed를 S3에 업로드
|
|
218
|
+
5. **Provider 등록**: 백엔드에 Provider 정보 등록
|
|
219
|
+
6. **EVM 키 파생**: Master Seed에서 BIP-32로 EVM 키 쌍 파생
|
|
220
|
+
7. **완료**: 지갑 주소 확인 및 로그인 완료
|
|
221
|
+
|
|
222
|
+
## 디자인 원칙
|
|
223
|
+
|
|
224
|
+
- **미니멀리즘**: 깔끔하고 모던한 디자인, 불필요한 장식 없음
|
|
225
|
+
- **accentColor 사용**: 버튼, 링크 등 강조 요소에 config의 accentColor 활용
|
|
226
|
+
- **이모지 금지**: 모든 UI 요소에서 이모지 사용 안 함, SVG 아이콘만 사용
|
|
227
|
+
- **일관된 여백과 타이포그래피**: 명확한 시각적 계층
|
|
228
|
+
|
|
229
|
+
## 백엔드 설정
|
|
230
|
+
|
|
231
|
+
로그인 기능을 사용하려면 백엔드에 다음 환경 변수를 설정해야 합니다:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Google OAuth
|
|
235
|
+
GOOGLE_CLIENT_ID=your_google_client_id
|
|
236
|
+
GOOGLE_CLIENT_SECRET=your_google_client_secret
|
|
237
|
+
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
|
|
238
|
+
|
|
239
|
+
# Twitter OAuth
|
|
240
|
+
TWITTER_CLIENT_ID=your_twitter_client_id
|
|
241
|
+
TWITTER_CLIENT_SECRET=your_twitter_client_secret
|
|
242
|
+
TWITTER_REDIRECT_URI=http://localhost:3000/auth/twitter/callback
|
|
243
|
+
|
|
244
|
+
# Apple OAuth
|
|
245
|
+
APPLE_CLIENT_ID=your_apple_client_id
|
|
246
|
+
APPLE_REDIRECT_URI=http://localhost:3000/auth/apple/callback
|
|
247
|
+
|
|
248
|
+
# Frontend URL (OAuth 콜백 후 리다이렉트)
|
|
249
|
+
FRONTEND_URL=http://localhost:5173
|
|
250
|
+
|
|
251
|
+
# API Base URL
|
|
252
|
+
API_BASE_URL=http://localhost:3000
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## API 엔드포인트
|
|
256
|
+
|
|
257
|
+
백엔드에서 제공해야 하는 엔드포인트:
|
|
258
|
+
|
|
259
|
+
### 이메일 로그인
|
|
260
|
+
- `POST /auth/email/send` - 인증 코드 전송
|
|
261
|
+
- `POST /auth/email/verify` - 인증 코드 검증 및 로그인
|
|
262
|
+
|
|
263
|
+
### 소셜 로그인
|
|
264
|
+
- `GET /auth/google` - Google OAuth 시작
|
|
265
|
+
- `GET /auth/google/callback` - Google OAuth 콜백
|
|
266
|
+
- `GET /auth/twitter` - Twitter OAuth 시작
|
|
267
|
+
- `GET /auth/twitter/callback` - Twitter OAuth 콜백
|
|
268
|
+
- `GET /auth/apple` - Apple OAuth 시작
|
|
269
|
+
- `POST /auth/apple/callback` - Apple OAuth 콜백
|
|
270
|
+
|
|
271
|
+
### SIWE 로그인
|
|
272
|
+
- `POST /auth/siwe/nonce` - Nonce 생성
|
|
273
|
+
- `POST /auth/siwe/verify` - SIWE 서명 검증
|
|
274
|
+
|
|
275
|
+
### Passkey 관련
|
|
276
|
+
- `POST /blob/presign` - S3 업로드 URL 생성
|
|
277
|
+
- `POST /wallet/provider/register` - Provider 등록
|
|
278
|
+
|
|
279
|
+
## 완전한 예제
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
import { useState } from 'react';
|
|
283
|
+
import { VolrUIProvider, LoginModal } from '@volr/react-ui';
|
|
284
|
+
import { VolrConfig } from '@volr/react';
|
|
285
|
+
|
|
286
|
+
const volrConfig: VolrConfig = {
|
|
287
|
+
apiBaseUrl: process.env.VITE_API_BASE_URL || 'http://localhost:3000',
|
|
288
|
+
defaultChainId: 1,
|
|
289
|
+
projectApiKey: process.env.VITE_PROJECT_API_KEY || '',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
function LoginPage() {
|
|
293
|
+
const [isLoginOpen, setIsLoginOpen] = useState(false);
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<>
|
|
297
|
+
<button onClick={() => setIsLoginOpen(true)}>로그인</button>
|
|
298
|
+
|
|
299
|
+
<LoginModal
|
|
300
|
+
isOpen={isLoginOpen}
|
|
301
|
+
onClose={() => setIsLoginOpen(false)}
|
|
302
|
+
onError={(error) => {
|
|
303
|
+
console.error('로그인 오류:', error);
|
|
304
|
+
alert(error.message);
|
|
305
|
+
}}
|
|
306
|
+
/>
|
|
307
|
+
</>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function App() {
|
|
312
|
+
return (
|
|
313
|
+
<VolrUIProvider
|
|
314
|
+
config={volrConfig}
|
|
315
|
+
accentColor="#6366f1"
|
|
316
|
+
enabledLoginMethods={['email', 'social', 'siwe']}
|
|
317
|
+
socialProviders={['google', 'twitter', 'apple']}
|
|
318
|
+
>
|
|
319
|
+
<LoginPage />
|
|
320
|
+
</VolrUIProvider>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export default App;
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## 문제 해결
|
|
328
|
+
|
|
329
|
+
### OAuth 리다이렉트 실패
|
|
330
|
+
- OAuth Client ID와 Secret 확인
|
|
331
|
+
- Redirect URI가 OAuth 앱 설정과 일치하는지 확인
|
|
332
|
+
- CORS 설정 확인
|
|
333
|
+
|
|
334
|
+
### SIWE 서명 실패
|
|
335
|
+
- MetaMask 또는 다른 지갑이 설치되어 있는지 확인
|
|
336
|
+
- 지갑이 올바른 네트워크에 연결되어 있는지 확인
|
|
337
|
+
|
|
338
|
+
### Passkey 생성 실패
|
|
339
|
+
- HTTPS 또는 localhost에서만 작동 (HTTP는 불가)
|
|
340
|
+
- 브라우저가 WebAuthn을 지원하는지 확인
|
|
341
|
+
- 디바이스에 생체 인증이 활성화되어 있는지 확인
|