@taskon/widget-react 0.0.1-beta.1

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.
Files changed (48) hide show
  1. package/README.md +1065 -0
  2. package/dist/CommunityTaskList.css +4893 -0
  3. package/dist/EligibilityInfo.css +2337 -0
  4. package/dist/LeaderboardWidget.css +815 -0
  5. package/dist/PageBuilder.css +54 -0
  6. package/dist/Quest.css +4214 -0
  7. package/dist/TaskOnProvider.css +163 -0
  8. package/dist/TipPopover.css +210 -0
  9. package/dist/UserCenterWidget.css +297 -0
  10. package/dist/UserCenterWidget2.css +3519 -0
  11. package/dist/WidgetShell.css +182 -0
  12. package/dist/chunks/CommunityTaskList-DoPGZsw1.js +6813 -0
  13. package/dist/chunks/EligibilityInfo-C7GZ2G5u.js +22228 -0
  14. package/dist/chunks/LeaderboardWidget-CmYfDeHV.js +1068 -0
  15. package/dist/chunks/PageBuilder-Tmhf2GTS.js +150 -0
  16. package/dist/chunks/Quest-DKFZ-pPU.js +8839 -0
  17. package/dist/chunks/TaskOnProvider-BD6Vp2x8.js +1435 -0
  18. package/dist/chunks/ThemeProvider-wnSXrNQb.js +1118 -0
  19. package/dist/chunks/TipPopover-BrW8jo71.js +2926 -0
  20. package/dist/chunks/UserCenterWidget-BE329iS7.js +3546 -0
  21. package/dist/chunks/UserCenterWidget-BVw_IEEd.js +3989 -0
  22. package/dist/chunks/WidgetShell-D_5OjvNZ.js +1517 -0
  23. package/dist/chunks/common-ja-DWhTaFHb.js +23 -0
  24. package/dist/chunks/common-ko-80ezXsMG.js +23 -0
  25. package/dist/chunks/dynamic-import-helper-DxEFwm31.js +537 -0
  26. package/dist/chunks/index-CwMvO_wZ.js +777 -0
  27. package/dist/chunks/leaderboardwidget-ja-Bj6gz6y1.js +119 -0
  28. package/dist/chunks/leaderboardwidget-ko-f1cLO9ic.js +119 -0
  29. package/dist/chunks/useToast-B-wyO5zL.js +93 -0
  30. package/dist/chunks/useWidgetLocale-JDelxtt8.js +74 -0
  31. package/dist/chunks/usercenter-ja-uu-XfVF9.js +332 -0
  32. package/dist/chunks/usercenter-ko-DYgUOVzd.js +332 -0
  33. package/dist/community-task.d.ts +451 -0
  34. package/dist/community-task.js +9 -0
  35. package/dist/core.d.ts +803 -0
  36. package/dist/core.js +22 -0
  37. package/dist/dynamic-import-helper.css +389 -0
  38. package/dist/index.d.ts +1660 -0
  39. package/dist/index.js +41 -0
  40. package/dist/leaderboard.d.ts +547 -0
  41. package/dist/leaderboard.js +18 -0
  42. package/dist/page-builder.d.ts +20 -0
  43. package/dist/page-builder.js +4 -0
  44. package/dist/quest.d.ts +400 -0
  45. package/dist/quest.js +8 -0
  46. package/dist/user-center.d.ts +1780 -0
  47. package/dist/user-center.js +713 -0
  48. package/package.json +105 -0
package/README.md ADDED
@@ -0,0 +1,1065 @@
1
+ # @taskon/widget-react
2
+
3
+ TaskOn React Widget - Embeddable white-label components for integrating TaskOn quest/task functionality into your application.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @taskon/widget-react @taskon/core
9
+ # or
10
+ pnpm add @taskon/widget-react @taskon/core
11
+ # or
12
+ yarn add @taskon/widget-react @taskon/core
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ // Providers from core (minimal CSS ~3KB)
19
+ import { TaskOnProvider } from "@taskon/widget-react/core";
20
+ // Widgets from their sub-paths (isolated CSS)
21
+ import { QuestWidget } from "@taskon/widget-react/quest";
22
+
23
+ const App = () => (
24
+ <TaskOnProvider
25
+ config={{
26
+ apiKey: "your-api-key",
27
+ }}
28
+ >
29
+ <YourApp />
30
+ </TaskOnProvider>
31
+ );
32
+ ```
33
+
34
+ ## Import Methods
35
+
36
+ ### Recommended: Sub-path Imports (Optimal CSS)
37
+
38
+ ```tsx
39
+ // Core: Providers and hooks only (~3KB CSS for Toast)
40
+ import { TaskOnProvider, ThemeProvider, useTaskOnAuth } from "@taskon/widget-react/core";
41
+
42
+ // Widgets: Each loads only its own CSS
43
+ import { QuestWidget } from "@taskon/widget-react/quest"; // ~26KB CSS
44
+ import { CommunityTaskList } from "@taskon/widget-react/community-task"; // ~51KB CSS
45
+ import { LeaderboardWidget } from "@taskon/widget-react/leaderboard"; // ~17KB CSS
46
+ import { UserCenterWidget } from "@taskon/widget-react/user-center"; // UserCenter CSS only
47
+ ```
48
+
49
+ ### Alternative: Main Entry Import (Loads All CSS)
50
+
51
+ ```tsx
52
+ // Loads ALL widget CSS (~92KB) - not recommended for production
53
+ import { TaskOnProvider, QuestWidget, CommunityTaskList } from "@taskon/widget-react";
54
+ ```
55
+
56
+ ## Usage Example
57
+
58
+ ```tsx
59
+ import { TaskOnProvider, useTaskOnAuth } from "@taskon/widget-react/core";
60
+ import { QuestWidget } from "@taskon/widget-react/quest";
61
+
62
+ const App = () => (
63
+ <TaskOnProvider
64
+ config={{
65
+ apiKey: "your-api-key",
66
+ }}
67
+ >
68
+ <YourApp />
69
+ </TaskOnProvider>
70
+ );
71
+
72
+ // Your app controls the login UI
73
+ const YourApp = () => {
74
+ const { userId, login, logout } = useTaskOnAuth();
75
+ const { user, getSignature } = useYourAuth(); // Your auth hook
76
+
77
+ // When user logs in to your app, login to TaskOn
78
+ useEffect(() => {
79
+ if (user?.email && !userId) {
80
+ const { sign, timestamp } = getSignature(); // Get signature from your backend
81
+ login({ method: "email", value: user.email, sign, timestamp });
82
+ }
83
+ }, [user, userId]);
84
+
85
+ // When user logs out from your app, logout from TaskOn
86
+ useEffect(() => {
87
+ if (!user && userId) {
88
+ logout();
89
+ }
90
+ }, [user, userId]);
91
+
92
+ return (
93
+ <div>
94
+ {userId && <p>TaskOn User ID: {userId}</p>}
95
+ {/* Theme configured in TaskOn Dashboard */}
96
+ <QuestWidget configId="cfg_abc123" />
97
+ </div>
98
+ );
99
+ };
100
+ ```
101
+
102
+ ## Architecture
103
+
104
+ ```
105
+ ┌─ TaskOnProvider ─────────────────────────────────────────┐
106
+ │ config: { apiKey } │
107
+ │ Purpose: Authentication only │
108
+ │ │
109
+ │ ┌─ Mode A: Cloud Config ──────────────────────────────┐ │
110
+ │ │ <QuestWidget configId="cfg_abc123" /> │ │
111
+ │ │ → Theme from TaskOn cloud │ │
112
+ │ └─────────────────────────────────────────────────────┘ │
113
+ │ │
114
+ │ ┌─ Mode B: Local Theme ───────────────────────────────┐ │
115
+ │ │ <ThemeProvider theme={{ mode: 'dark' }}> │ │
116
+ │ │ <QuestWidget /> /* no configId */ │ │
117
+ │ │ </ThemeProvider> │ │
118
+ │ └─────────────────────────────────────────────────────┘ │
119
+ └──────────────────────────────────────────────────────────┘
120
+ ```
121
+
122
+ ### Theme Source (mutually exclusive)
123
+
124
+ | Mode | Theme Source |
125
+ | --------------------- | ------------------------------------ |
126
+ | `configId` provided | Cloud config (ThemeProvider ignored) |
127
+ | No `configId` | ThemeProvider or default theme |
128
+
129
+ ## Security
130
+
131
+ TaskOn uses **API Key** authentication to verify that widget requests come from authorized projects.
132
+
133
+ ### Step 1: Get API Key from TaskOn Dashboard
134
+
135
+ ```
136
+ API Key: /KDiqEFNCaGTVTdTpCFrZOsUj5vDi5uGLSFmwyHeboE= (for X-API-Key header)
137
+ ```
138
+
139
+ ### Step 2: Configure TaskOnProvider
140
+
141
+ ```tsx
142
+ <TaskOnProvider
143
+ config={{
144
+ apiKey: "your-api-key",
145
+ }}
146
+ >
147
+ <YourApp />
148
+ </TaskOnProvider>;
149
+ ```
150
+
151
+ ### HTTP Headers
152
+
153
+ All API requests include:
154
+
155
+ ```
156
+ X-API-Key: your-api-key # Project authorization
157
+ Authorization: Bearer xxx # User authorization (after login)
158
+ ```
159
+
160
+ ### Security Best Practices
161
+
162
+ 1. **Keep API Key secure** - Don't expose in public repositories
163
+ 2. **Use HTTPS** - All communication must be encrypted
164
+
165
+ ## TaskOnProvider
166
+
167
+ The root provider component for authentication. Must wrap your application.
168
+
169
+ ### Props
170
+
171
+ | Prop | Type | Required | Default | Description |
172
+ | ------------ | ------------------------ | -------- | ------- | -------------------- |
173
+ | `config` | `TaskOnProviderConfig` | Yes | - | Configuration object |
174
+ | `children` | `ReactNode` | Yes | - | Child components |
175
+
176
+ ### TaskOnProviderConfig
177
+
178
+ ```typescript
179
+ interface TaskOnProviderConfig {
180
+ // Required: API Key for authentication (X-API-Key header)
181
+ apiKey: string;
182
+
183
+ // Locale setting
184
+ locale?: "en" | "ko" | "ja" | "ru" | "es"; // default: auto-detect
185
+
186
+ // Wallet configuration (only needed if using wallet login)
187
+ walletConfig?: {
188
+ evmAdapter?: WalletAdapter; // Custom EVM wallet adapter
189
+ solanaAdapter?: WalletAdapter; // Custom Solana wallet adapter
190
+ disableAutoDetect?: boolean; // Disable auto-detection
191
+ };
192
+
193
+ // WalletConnect Project ID (required for WalletConnect support)
194
+ // Get your project ID at https://cloud.walletconnect.com
195
+ walletConnectProjectId?: string;
196
+
197
+ // Callback when user needs to login (e.g., clicks login overlay)
198
+ onRequestLogin?: () => void;
199
+ }
200
+ ```
201
+
202
+ ## Internationalization
203
+
204
+ ### Supported Locales
205
+
206
+ | Locale | Language |
207
+ | ------ | ----------------- |
208
+ | `en` | English (default) |
209
+ | `ko` | Korean |
210
+ | `ja` | Japanese |
211
+ | `ru` | Russian |
212
+ | `es` | Spanish |
213
+
214
+ ### Configuration
215
+
216
+ ```tsx
217
+ <TaskOnProvider
218
+ config={{
219
+ apiKey: "your-api-key",
220
+ locale: "ko",
221
+ }}
222
+ >
223
+ <App />
224
+ </TaskOnProvider>
225
+ ```
226
+
227
+ ### Dynamic Locale Switching
228
+
229
+ Control locale via your own state:
230
+
231
+ ```tsx
232
+ const App = () => {
233
+ const [locale, setLocale] = useState("en");
234
+
235
+ return (
236
+ <TaskOnProvider config={{ apiKey: "your-api-key", locale }}>
237
+ <select value={locale} onChange={(e) => setLocale(e.target.value)}>
238
+ <option value="en">English</option>
239
+ <option value="ko">한국어</option>
240
+ <option value="ja">日本語</option>
241
+ </select>
242
+ <QuestWidget configId="cfg_abc123" />
243
+ </TaskOnProvider>
244
+ );
245
+ };
246
+ ```
247
+
248
+ ### Locale Auto-Detection
249
+
250
+ When `locale` is not specified, TaskOn detects from browser language, falling back to `en`.
251
+
252
+ ## ThemeProvider
253
+
254
+ Optional provider for theme configuration. Supports nesting for different theme zones.
255
+
256
+ ### Props
257
+
258
+ | Prop | Type | Required | Default | Description |
259
+ | ------------ | --------------------- | -------- | -------- | -------------------- |
260
+ | `theme` | `TaskOnThemeConfig` | No | - | Theme configuration |
261
+ | `children` | `ReactNode` | Yes | - | Child components |
262
+ | `inherit` | `boolean` | No | `true` | Inherit parent theme |
263
+
264
+ ### TaskOnThemeConfig
265
+
266
+ ```typescript
267
+ interface TaskOnThemeConfig {
268
+ // Theme mode
269
+ mode?: "light" | "dark" | "auto"; // default: 'light'
270
+
271
+ // Compact mode
272
+ compact?: boolean; // default: false
273
+
274
+ // Seed tokens - algorithm input, auto-derives other values
275
+ seed?: SeedToken;
276
+
277
+ // Map tokens - override derived values
278
+ map?: MapToken;
279
+
280
+ // Optional: separate config for light/dark mode
281
+ light?: {
282
+ seed?: SeedToken;
283
+ map?: MapToken;
284
+ };
285
+ dark?: {
286
+ seed?: SeedToken;
287
+ map?: MapToken;
288
+ };
289
+ }
290
+
291
+ interface SeedToken {
292
+ colorPrimary?: string; // e.g., '#6366f1'
293
+ colorSecondary?: string;
294
+ colorSuccess?: string;
295
+ colorWarning?: string;
296
+ colorError?: string;
297
+ borderRadius?: number; // e.g., 8
298
+ fontSize?: number; // e.g., 14
299
+ fontFamily?: string;
300
+ }
301
+
302
+ interface MapToken {
303
+ // Primary colors (derived from seed.colorPrimary)
304
+ colorPrimary?: string;
305
+ colorPrimaryHover?: string;
306
+ colorPrimaryActive?: string;
307
+ colorPrimaryBg?: string;
308
+
309
+ // Background
310
+ colorBg?: string;
311
+ colorBgElevated?: string;
312
+ colorBgSpotlight?: string;
313
+
314
+ // Text
315
+ colorText?: string;
316
+ colorTextSecondary?: string;
317
+ colorTextTertiary?: string;
318
+ colorTextDisabled?: string;
319
+
320
+ // Border
321
+ colorBorder?: string;
322
+ colorBorderSecondary?: string;
323
+
324
+ // Layout
325
+ borderRadius?: number;
326
+ borderRadiusSm?: number;
327
+ borderRadiusLg?: number;
328
+
329
+ // Typography
330
+ fontSize?: number;
331
+ fontSizeSm?: number;
332
+ fontSizeLg?: number;
333
+
334
+ // Spacing
335
+ spacing?: number; // base spacing unit, e.g., 8
336
+ spacingXs?: number; // e.g., 4
337
+ spacingSm?: number; // e.g., 8
338
+ spacingMd?: number; // e.g., 16
339
+ spacingLg?: number; // e.g., 24
340
+ spacingXl?: number; // e.g., 32
341
+ }
342
+ ```
343
+
344
+ ### Token Priority
345
+
346
+ ```
347
+ light/dark.map > light/dark.seed (derived) > map > seed (derived) > default
348
+ ```
349
+
350
+ ### Theme Inheritance
351
+
352
+ Nested ThemeProviders inherit from parent and can override specific values:
353
+
354
+ ```tsx
355
+ <TaskOnProvider config={{ apiKey: "your-api-key" }}>
356
+ <ThemeProvider theme={{ mode: "light", seed: { colorPrimary: "#6366f1" } }}>
357
+ <Header /> {/* light + primary #6366f1 */}
358
+ <ThemeProvider theme={{ mode: "dark" }}>
359
+ <Sidebar /> {/* dark + inherits primary #6366f1 */}
360
+ </ThemeProvider>
361
+ <ThemeProvider theme={{ seed: { colorPrimary: "#ef4444" } }}>
362
+ <DangerZone /> {/* light + primary #ef4444 */}
363
+ </ThemeProvider>
364
+ </ThemeProvider>
365
+ </TaskOnProvider>
366
+ ```
367
+
368
+ ### Light/Dark Separate Config
369
+
370
+ Configure different themes for light and dark modes:
371
+
372
+ ```tsx
373
+ // Different primary colors for each mode
374
+ <ThemeProvider
375
+ theme={{
376
+ mode: 'auto',
377
+ light: { seed: { colorPrimary: '#6366f1' } },
378
+ dark: { seed: { colorPrimary: '#818cf8' } },
379
+ }}
380
+ >
381
+ <App />
382
+ </ThemeProvider>
383
+
384
+ // Override specific derived values
385
+ <ThemeProvider
386
+ theme={{
387
+ mode: 'auto',
388
+ light: {
389
+ seed: { colorPrimary: '#6366f1' },
390
+ map: { colorPrimaryHover: '#4f46e5' },
391
+ },
392
+ dark: {
393
+ seed: { colorPrimary: '#818cf8' },
394
+ map: { colorBg: '#0a0a0a' },
395
+ },
396
+ }}
397
+ >
398
+ <App />
399
+ </ThemeProvider>
400
+ ```
401
+
402
+ ### Disable Inheritance
403
+
404
+ Use `inherit={false}` for completely independent themes:
405
+
406
+ ```tsx
407
+ <ThemeProvider theme={{ mode: "dark" }}>
408
+ <DarkContent />
409
+
410
+ <ThemeProvider theme={{ mode: "light" }} inherit={false}>
411
+ <LightPopup /> {/* Fully independent light theme */}
412
+ </ThemeProvider>
413
+ </ThemeProvider>
414
+ ```
415
+
416
+ ## Widgets
417
+
418
+ ### Cloud Configuration
419
+
420
+ Widgets support cloud configuration via `configId`. Configure in TaskOn Dashboard and load at runtime:
421
+
422
+ ```tsx
423
+ // Cloud config includes: theme, feature flags, custom texts
424
+ <QuestWidget configId="cfg_abc123" />
425
+ ```
426
+
427
+ ### Widget Props
428
+
429
+ ```typescript
430
+ interface WidgetProps {
431
+ // Cloud config ID from TaskOn Dashboard
432
+ configId?: string;
433
+
434
+ // Custom class names for widget parts
435
+ classNames?: {
436
+ root?: string;
437
+ header?: string;
438
+ body?: string;
439
+ footer?: string;
440
+ // ... widget-specific parts
441
+ };
442
+
443
+ // Custom inline styles for widget parts
444
+ styles?: {
445
+ root?: React.CSSProperties;
446
+ header?: React.CSSProperties;
447
+ body?: React.CSSProperties;
448
+ footer?: React.CSSProperties;
449
+ // ... widget-specific parts
450
+ };
451
+ }
452
+ ```
453
+
454
+ ### Styling with classNames and styles
455
+
456
+ Widgets support fine-grained styling via `classNames` and `styles` props:
457
+
458
+ ```tsx
459
+ // Using classNames
460
+ <QuestWidget
461
+ configId="cfg_abc123"
462
+ classNames={{
463
+ root: 'my-quest-widget',
464
+ header: 'my-quest-header',
465
+ body: 'my-quest-body',
466
+ }}
467
+ />
468
+
469
+ // Using inline styles
470
+ <QuestWidget
471
+ configId="cfg_abc123"
472
+ styles={{
473
+ root: { maxWidth: 400 },
474
+ header: { borderBottom: '1px solid #eee' },
475
+ body: { padding: 24 },
476
+ }}
477
+ />
478
+
479
+ // Combining both
480
+ <QuestWidget
481
+ configId="cfg_abc123"
482
+ classNames={{ root: 'custom-widget' }}
483
+ styles={{ header: { backgroundColor: 'transparent' } }}
484
+ />
485
+ ```
486
+
487
+ Each widget documents its available parts in its own API reference.
488
+
489
+ ### Usage Examples
490
+
491
+ ```tsx
492
+ // Example 1: Using cloud config (theme from Dashboard)
493
+ <TaskOnProvider config={{ apiKey: 'your-api-key' }}>
494
+ <QuestWidget configId="cfg_abc123" />
495
+ <TaskWidget configId="cfg_xyz789" />
496
+ </TaskOnProvider>
497
+
498
+ // Example 2: Using local theme (no cloud config)
499
+ <TaskOnProvider config={{ apiKey: 'your-api-key' }}>
500
+ <ThemeProvider theme={{ mode: 'dark', seed: { colorPrimary: '#6366f1' } }}>
501
+ <QuestWidget />
502
+ <TaskWidget />
503
+ </ThemeProvider>
504
+ </TaskOnProvider>
505
+
506
+ // Example 3: Different local themes for different areas
507
+ <TaskOnProvider config={{ apiKey: 'your-api-key' }}>
508
+ <ThemeProvider theme={{ mode: 'light' }}>
509
+ <TaskWidget />
510
+ </ThemeProvider>
511
+
512
+ <ThemeProvider theme={{ mode: 'dark', compact: true }}>
513
+ <QuestWidget />
514
+ </ThemeProvider>
515
+ </TaskOnProvider>
516
+
517
+ // Example 4: Mixed - some with cloud config, some with local theme
518
+ <TaskOnProvider config={{ apiKey: 'your-api-key' }}>
519
+ {/* Cloud config */}
520
+ <QuestWidget configId="cfg_abc123" />
521
+
522
+ {/* Local theme */}
523
+ <ThemeProvider theme={{ mode: 'dark' }}>
524
+ <TaskWidget />
525
+ </ThemeProvider>
526
+ </TaskOnProvider>
527
+ ```
528
+
529
+ ## Login Methods
530
+
531
+ TaskOn uses a unified `login` method with signature verification. All login methods require a backend signature for security.
532
+
533
+ ### Unified Login API
534
+
535
+ ```typescript
536
+ import { useTaskOnAuth } from "@taskon/widget-react";
537
+
538
+ const { login } = useTaskOnAuth();
539
+
540
+ // Login with any method
541
+ await login({
542
+ method: "evm_wallet", // Login method type
543
+ value: "0x1234...", // Address / email / OAuth token
544
+ sign: signatureFromBackend, // Backend signature
545
+ timestamp: 1234567890, // Signature timestamp (seconds)
546
+ });
547
+ ```
548
+
549
+ ### Supported Methods
550
+
551
+ | Method | `method` value | `value` parameter |
552
+ | ------------- | ----------------- | ----------------------- |
553
+ | EVM Wallet | `evm_wallet` | Wallet address (0x...) |
554
+ | Solana Wallet | `solana_wallet` | Wallet address (base58) |
555
+ | Email | `email` | Email address |
556
+ | Discord | `discord` | OAuth token |
557
+ | Twitter | `twitter` | OAuth token |
558
+ | Telegram | `telegram` | OAuth token |
559
+
560
+ ### LoginParams Type
561
+
562
+ ```typescript
563
+ interface LoginParams {
564
+ method: LoginMethod; // Login method type
565
+ value: string; // Address / email / OAuth token
566
+ sign: string; // Backend signature
567
+ timestamp: number; // Signature timestamp (seconds)
568
+ }
569
+
570
+ type LoginMethod =
571
+ | "evm_wallet"
572
+ | "solana_wallet"
573
+ | "email"
574
+ | "discord"
575
+ | "twitter"
576
+ | "telegram";
577
+ ```
578
+
579
+ ## Wallet Integration
580
+
581
+ When tasks or rewards involve blockchain operations (e.g., token rewards, NFT minting, on-chain verification), widgets need to interact with the user's wallet for:
582
+
583
+ - **Connecting wallet** - Get user's wallet address
584
+ - **Signing messages** - Verify wallet ownership
585
+ - **Calling contracts** - Claim on-chain rewards, execute transactions
586
+
587
+ ### Overview
588
+
589
+ TaskOn widgets include built-in wallet management, but also support external wallet providers for seamless integration with existing dApps. When wrapped in an external provider, the widget automatically detects and reuses your wallet management.
590
+
591
+ ### Wallet Management
592
+
593
+ TaskOn provides flexible wallet integration options:
594
+
595
+ | Setup | Behavior |
596
+ | -------------- | ------------------------------------- |
597
+ | Custom adapter | Uses your provided `WalletAdapter` |
598
+ | Default | Uses built-in window.ethereum adapter |
599
+
600
+ ### Custom Wallet Adapter (Recommended)
601
+
602
+ If you want full control over wallet connection (e.g., using RainbowKit, Web3Modal), provide a custom adapter:
603
+
604
+ ```tsx
605
+ import { createWalletAdapter } from "./my-wallet-adapter";
606
+
607
+ <TaskOnProvider
608
+ config={{
609
+ apiKey: "your-api-key",
610
+ walletConfig: {
611
+ evmAdapter: createWalletAdapter(),
612
+ },
613
+ }}
614
+ >
615
+ <App />
616
+ </TaskOnProvider>;
617
+ ```
618
+
619
+ ### Built-in Wallet Support
620
+
621
+ If no custom adapter is provided, TaskOn automatically uses `window.ethereum` to connect to browser wallets like MetaMask:
622
+
623
+ ```tsx
624
+ // No wallet config needed - uses window.ethereum by default
625
+ <TaskOnProvider config={{ apiKey: "your-api-key" }}>
626
+ <App />
627
+ </TaskOnProvider>
628
+ ```
629
+
630
+ ### Built-in Wallet Binding Dialog
631
+
632
+ When tasks require wallet binding (e.g., on-chain verification), TaskOn shows a built-in wallet selection dialog:
633
+
634
+ **Desktop (without adapter):**
635
+ - MetaMask
636
+ - ONTO Wallet
637
+ - Bitget Wallet
638
+ - OKX Wallet
639
+ - WalletConnect (requires `walletConnectProjectId`)
640
+
641
+ **Mobile (non-Dapp browser):**
642
+ - WalletConnect only (requires `walletConnectProjectId`)
643
+
644
+ **Mobile (Dapp browser / wallet app):**
645
+ - Uses injected provider directly
646
+
647
+ To enable WalletConnect in the dialog:
648
+
649
+ ```tsx
650
+ <TaskOnProvider
651
+ config={{
652
+ apiKey: "your-api-key",
653
+ walletConnectProjectId: "your-project-id", // Get from cloud.walletconnect.com
654
+ }}
655
+ >
656
+ <App />
657
+ </TaskOnProvider>
658
+ ```
659
+
660
+ ### Built-in Wallet Management
661
+
662
+ If no external provider is detected, the widget uses its built-in wallet management. No configuration needed:
663
+
664
+ ```tsx
665
+ <TaskOnProvider config={{ apiKey: "your-api-key" }}>
666
+ <App /> {/* Widget handles wallet connection internally */}
667
+ </TaskOnProvider>
668
+ ```
669
+
670
+ ### Configuration Options
671
+
672
+ ```typescript
673
+ interface WalletConfig {
674
+ // EVM wallet adapter (highest priority)
675
+ evmAdapter?: WalletAdapter;
676
+
677
+ // Solana wallet adapter (highest priority)
678
+ solanaAdapter?: WalletAdapter;
679
+
680
+ // Disable auto-detection of external providers
681
+ disableAutoDetect?: boolean;
682
+ }
683
+ ```
684
+
685
+ ### Custom Wallet Adapter
686
+
687
+ For projects with their own wallet management:
688
+
689
+ ```tsx
690
+ // Example: Custom wallet management
691
+ const useCustomWalletAdapter = (): WalletAdapter => {
692
+ const { openWalletModal, connectedAddress, chainId } = useYourWalletManager();
693
+
694
+ return {
695
+ connect: async () => {
696
+ // Open your wallet selection modal, return selected address
697
+ const address = await openWalletModal();
698
+ return address;
699
+ },
700
+ disconnect: async () => {
701
+ await yourDisconnectLogic();
702
+ },
703
+ signMessage: async (message) => {
704
+ return await yourSignMessageLogic(message);
705
+ },
706
+ getAddress: () => connectedAddress,
707
+ getChainId: () => chainId,
708
+ switchNetwork: async (chainId) => {
709
+ await yourSwitchNetworkLogic(chainId);
710
+ },
711
+ };
712
+ };
713
+
714
+ const App = () => {
715
+ const evmAdapter = useCustomWalletAdapter();
716
+
717
+ return (
718
+ <TaskOnProvider
719
+ config={{
720
+ apiKey: "your-api-key",
721
+ walletConfig: { evmAdapter },
722
+ }}
723
+ >
724
+ <YourApp />
725
+ </TaskOnProvider>
726
+ );
727
+ };
728
+ ```
729
+
730
+ ### Priority Order
731
+
732
+ When multiple options are available:
733
+
734
+ 1. **Custom Adapter** - `walletConfig.evmAdapter` / `solanaAdapter` (highest)
735
+ 2. **Built-in Adapter** - window.ethereum adapter for EVM wallets (lowest)
736
+
737
+ ## Hooks
738
+
739
+ ### useTaskOnAuth
740
+
741
+ Access TaskOn authentication with unified login method.
742
+
743
+ ```tsx
744
+ import { useTaskOnAuth } from "@taskon/widget-react";
745
+
746
+ const Component = () => {
747
+ const {
748
+ // State
749
+ userId, // TaskOn user ID (number | null)
750
+ isLoggedIn, // Whether user is logged in
751
+ isInitializing, // Whether provider is initializing
752
+
753
+ // Unified login method
754
+ login, // (params: LoginParams) => Promise<void>
755
+
756
+ // Logout
757
+ logout, // () => void
758
+ } = useTaskOnAuth();
759
+
760
+ return <div>TaskOn ID: {userId}</div>;
761
+ };
762
+ ```
763
+
764
+ ### Login Function
765
+
766
+ ```typescript
767
+ // Unified login method
768
+ login: (params: LoginParams) => Promise<void>;
769
+
770
+ // LoginParams
771
+ interface LoginParams {
772
+ method: LoginMethod; // Login method type
773
+ value: string; // Address / email / OAuth token
774
+ sign: string; // Backend signature
775
+ timestamp: number; // Signature timestamp (seconds)
776
+ }
777
+
778
+ // Logout
779
+ logout: () => void;
780
+ ```
781
+
782
+ ### useTaskOnTheme
783
+
784
+ Access current theme. Must be used within ThemeProvider.
785
+
786
+ ```tsx
787
+ import { useTaskOnTheme } from "@taskon/widget-react";
788
+
789
+ const Component = () => {
790
+ const theme = useTaskOnTheme();
791
+
792
+ return (
793
+ <div
794
+ style={{
795
+ background: theme.tokens.colorBg,
796
+ color: theme.tokens.colorText,
797
+ padding: theme.tokens.spacingMd,
798
+ borderRadius: theme.tokens.borderRadius,
799
+ }}
800
+ >
801
+ Current mode: {theme.mode}
802
+ </div>
803
+ );
804
+ };
805
+ ```
806
+
807
+ ### Integration Examples
808
+
809
+ ```tsx
810
+ // Example 1: With EVM Wallet
811
+ const EVMWalletIntegration = () => {
812
+ const { evmAddress } = useWallet(); // TaskOn wallet hook
813
+ const { userId, login } = useTaskOnAuth();
814
+
815
+ const handleLogin = async () => {
816
+ if (evmAddress && !userId) {
817
+ // Get signature from your backend
818
+ const { sign, timestamp } = await fetchSignatureFromBackend(evmAddress);
819
+ await login({
820
+ method: "evm_wallet",
821
+ value: evmAddress,
822
+ sign,
823
+ timestamp,
824
+ });
825
+ }
826
+ };
827
+
828
+ useEffect(() => {
829
+ handleLogin();
830
+ }, [evmAddress, userId]);
831
+
832
+ return <div>{userId ? `TaskOn: ${userId}` : "Not logged in"}</div>;
833
+ };
834
+
835
+ // Example 2: With Custom Wallet Adapter
836
+ const CustomWalletIntegration = () => {
837
+ const { evmAddress, connectEvm } = useWallet();
838
+ const { userId, login } = useTaskOnAuth();
839
+
840
+ const handleLogin = async () => {
841
+ if (publicKey && !userId) {
842
+ const address = publicKey.toBase58();
843
+ const { sign, timestamp } = await fetchSignatureFromBackend(address);
844
+ await login({
845
+ method: "solana_wallet",
846
+ value: address,
847
+ sign,
848
+ timestamp,
849
+ });
850
+ }
851
+ };
852
+
853
+ useEffect(() => {
854
+ handleLogin();
855
+ }, [publicKey, userId]);
856
+
857
+ return <div>{userId ? `TaskOn: ${userId}` : "Not logged in"}</div>;
858
+ };
859
+
860
+ // Example 3: Email login
861
+ const EmailLogin = () => {
862
+ const { login } = useTaskOnAuth();
863
+
864
+ const handleEmailLogin = async (email: string) => {
865
+ const { sign, timestamp } = await fetchSignatureFromBackend(email);
866
+ await login({
867
+ method: "email",
868
+ value: email,
869
+ sign,
870
+ timestamp,
871
+ });
872
+ };
873
+
874
+ return (
875
+ <button onClick={() => handleEmailLogin("user@example.com")}>Login</button>
876
+ );
877
+ };
878
+
879
+ // Example 4: Discord OAuth callback
880
+ const DiscordCallback = () => {
881
+ const { login } = useTaskOnAuth();
882
+
883
+ useEffect(() => {
884
+ // After your Discord OAuth flow
885
+ const processOAuth = async () => {
886
+ const oauthToken = getDiscordTokenFromOAuth();
887
+ const { sign, timestamp } = await fetchSignatureFromBackend(oauthToken);
888
+ await login({
889
+ method: "discord",
890
+ value: oauthToken,
891
+ sign,
892
+ timestamp,
893
+ });
894
+ };
895
+ processOAuth();
896
+ }, []);
897
+
898
+ return <div>Processing...</div>;
899
+ };
900
+ ```
901
+
902
+ ## WalletAdapter Interface
903
+
904
+ For custom wallet integration:
905
+
906
+ ```typescript
907
+ interface WalletAdapter {
908
+ // Required
909
+ connect: () => Promise<string>;
910
+ disconnect: () => Promise<void>;
911
+ signMessage: (message: string) => Promise<string>;
912
+ getAddress: () => string | null;
913
+
914
+ // Optional (EVM only)
915
+ getChainId?: () => number | null;
916
+ switchNetwork?: (chainId: number) => Promise<void>;
917
+
918
+ // Optional (Event subscriptions)
919
+ onAccountChange?: (callback: (address: string | null) => void) => () => void;
920
+ onChainChange?: (callback: (chainId: number) => void) => () => void;
921
+ }
922
+ ```
923
+
924
+ ## Types
925
+
926
+ ### TaskOnAuthState
927
+
928
+ ```typescript
929
+ interface TaskOnAuthState {
930
+ userId: number | null; // TaskOn user ID (null if not logged in)
931
+ isLoggedIn: boolean; // Whether user is logged in
932
+ isInitializing: boolean; // Whether provider is initializing
933
+ }
934
+ ```
935
+
936
+ ### LoginParams
937
+
938
+ ```typescript
939
+ interface LoginParams {
940
+ method: LoginMethod; // Login method type
941
+ value: string; // Address / email / OAuth token
942
+ sign: string; // Backend signature
943
+ timestamp: number; // Signature timestamp (seconds)
944
+ }
945
+
946
+ type LoginMethod =
947
+ | "evm_wallet"
948
+ | "solana_wallet"
949
+ | "email"
950
+ | "discord"
951
+ | "twitter"
952
+ | "telegram";
953
+ ```
954
+
955
+ ### TaskOnTheme
956
+
957
+ The resolved theme object returned by `useTaskOnTheme()`:
958
+
959
+ ```typescript
960
+ interface TaskOnTheme {
961
+ mode: "light" | "dark";
962
+ compact: boolean;
963
+
964
+ // All tokens are resolved (seed + derived + overrides)
965
+ tokens: {
966
+ // Primary colors
967
+ colorPrimary: string;
968
+ colorPrimaryHover: string;
969
+ colorPrimaryActive: string;
970
+ colorPrimaryBg: string;
971
+
972
+ // Secondary colors
973
+ colorSecondary: string;
974
+
975
+ // Status colors
976
+ colorSuccess: string;
977
+ colorWarning: string;
978
+ colorError: string;
979
+
980
+ // Background
981
+ colorBg: string;
982
+ colorBgElevated: string;
983
+ colorBgSpotlight: string;
984
+
985
+ // Text
986
+ colorText: string;
987
+ colorTextSecondary: string;
988
+ colorTextTertiary: string;
989
+ colorTextDisabled: string;
990
+
991
+ // Border
992
+ colorBorder: string;
993
+ colorBorderSecondary: string;
994
+
995
+ // Layout
996
+ borderRadius: number;
997
+ borderRadiusSm: number;
998
+ borderRadiusLg: number;
999
+
1000
+ // Typography
1001
+ fontSize: number;
1002
+ fontSizeSm: number;
1003
+ fontSizeLg: number;
1004
+ fontFamily: string;
1005
+
1006
+ // Spacing
1007
+ spacing: number;
1008
+ spacingXs: number;
1009
+ spacingSm: number;
1010
+ spacingMd: number;
1011
+ spacingLg: number;
1012
+ spacingXl: number;
1013
+ };
1014
+ }
1015
+ ```
1016
+
1017
+ ## SSR Compatibility
1018
+
1019
+ This package is SSR-compatible:
1020
+
1021
+ - All components are marked with `'use client'`
1022
+ - Browser APIs are safely wrapped
1023
+ - No hydration mismatches
1024
+
1025
+ Works with:
1026
+
1027
+ - Next.js App Router
1028
+ - Next.js Pages Router
1029
+ - Remix
1030
+ - Other React SSR frameworks
1031
+
1032
+ ## Peer Dependencies
1033
+
1034
+ - `react` >= 18.0.0
1035
+ - `react-dom` >= 18.0.0
1036
+ - `@taskon/core` >= 0.0.0
1037
+
1038
+ ### Optional Peer Dependencies
1039
+
1040
+ For WalletConnect support in the built-in wallet binding dialog:
1041
+
1042
+ ```bash
1043
+ npm install @walletconnect/ethereum-provider @walletconnect/modal
1044
+ ```
1045
+
1046
+ Then configure your project ID:
1047
+
1048
+ ```tsx
1049
+ <TaskOnProvider
1050
+ config={{
1051
+ apiKey: "your-api-key",
1052
+ walletConnectProjectId: "your-walletconnect-project-id",
1053
+ }}
1054
+ >
1055
+ <App />
1056
+ </TaskOnProvider>
1057
+ ```
1058
+
1059
+ Get your WalletConnect Project ID at https://cloud.walletconnect.com
1060
+
1061
+ If not configured, the WalletConnect option will be disabled in the wallet binding dialog.
1062
+
1063
+ ## License
1064
+
1065
+ MIT