@mantle-rwa/react 0.1.1 → 0.1.3

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 (73) hide show
  1. package/dist/cjs/components/ConnectWalletPrompt.js +13 -0
  2. package/dist/cjs/components/ConnectWalletPrompt.js.map +1 -0
  3. package/dist/cjs/components/ErrorDisplay.js +42 -0
  4. package/dist/cjs/components/ErrorDisplay.js.map +1 -0
  5. package/dist/cjs/components/InvestorDashboard.js +156 -0
  6. package/dist/cjs/components/InvestorDashboard.js.map +1 -0
  7. package/dist/cjs/components/KYCFlow.js +146 -0
  8. package/dist/cjs/components/KYCFlow.js.map +1 -0
  9. package/dist/cjs/components/LoadingSpinner.js +18 -0
  10. package/dist/cjs/components/LoadingSpinner.js.map +1 -0
  11. package/dist/cjs/components/TokenMintForm.js +163 -0
  12. package/dist/cjs/components/TokenMintForm.js.map +1 -0
  13. package/dist/cjs/components/YieldCalculator.js +97 -0
  14. package/dist/cjs/components/YieldCalculator.js.map +1 -0
  15. package/dist/cjs/hooks/useRWA.js +87 -40
  16. package/dist/cjs/hooks/useRWA.js.map +1 -1
  17. package/dist/cjs/index.js +11 -1
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/types/index.js +2 -2
  20. package/dist/cjs/types/index.js.map +1 -1
  21. package/dist/esm/components/ConnectWalletPrompt.js +10 -0
  22. package/dist/esm/components/ConnectWalletPrompt.js.map +1 -0
  23. package/dist/esm/components/ErrorDisplay.js +38 -0
  24. package/dist/esm/components/ErrorDisplay.js.map +1 -0
  25. package/dist/esm/components/InvestorDashboard.js +153 -0
  26. package/dist/esm/components/InvestorDashboard.js.map +1 -0
  27. package/dist/esm/components/KYCFlow.js +143 -0
  28. package/dist/esm/components/KYCFlow.js.map +1 -0
  29. package/dist/esm/components/LoadingSpinner.js +15 -0
  30. package/dist/esm/components/LoadingSpinner.js.map +1 -0
  31. package/dist/esm/components/TokenMintForm.js +158 -0
  32. package/dist/esm/components/TokenMintForm.js.map +1 -0
  33. package/dist/esm/components/YieldCalculator.js +94 -0
  34. package/dist/esm/components/YieldCalculator.js.map +1 -0
  35. package/dist/esm/hooks/useRWA.js +86 -39
  36. package/dist/esm/hooks/useRWA.js.map +1 -1
  37. package/dist/esm/index.js +4 -0
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/esm/types/index.js +3 -3
  40. package/dist/esm/types/index.js.map +1 -1
  41. package/dist/styles.css +3 -1
  42. package/dist/types/components/ConnectWalletPrompt.d.ts +15 -0
  43. package/dist/types/components/ConnectWalletPrompt.d.ts.map +1 -0
  44. package/dist/types/components/ErrorDisplay.d.ts +16 -0
  45. package/dist/types/components/ErrorDisplay.d.ts.map +1 -0
  46. package/dist/types/components/InvestorDashboard.d.ts +7 -0
  47. package/dist/types/components/InvestorDashboard.d.ts.map +1 -0
  48. package/dist/types/components/KYCFlow.d.ts +7 -0
  49. package/dist/types/components/KYCFlow.d.ts.map +1 -0
  50. package/dist/types/components/LoadingSpinner.d.ts +10 -0
  51. package/dist/types/components/LoadingSpinner.d.ts.map +1 -0
  52. package/dist/types/components/TokenMintForm.d.ts +15 -0
  53. package/dist/types/components/TokenMintForm.d.ts.map +1 -0
  54. package/dist/types/components/YieldCalculator.d.ts +7 -0
  55. package/dist/types/components/YieldCalculator.d.ts.map +1 -0
  56. package/dist/types/hooks/useRWA.d.ts +7 -19
  57. package/dist/types/hooks/useRWA.d.ts.map +1 -1
  58. package/dist/types/index.d.ts +5 -1
  59. package/dist/types/index.d.ts.map +1 -1
  60. package/dist/types/types/index.d.ts +113 -131
  61. package/dist/types/types/index.d.ts.map +1 -1
  62. package/package.json +5 -3
  63. package/src/components/ConnectWalletPrompt.tsx +47 -0
  64. package/src/components/ErrorDisplay.tsx +90 -0
  65. package/src/components/InvestorDashboard.tsx +315 -0
  66. package/src/components/KYCFlow.tsx +267 -0
  67. package/src/components/LoadingSpinner.tsx +33 -0
  68. package/src/components/TokenMintForm.tsx +291 -0
  69. package/src/components/YieldCalculator.tsx +250 -0
  70. package/src/hooks/useRWA.ts +110 -0
  71. package/src/index.ts +4 -0
  72. package/src/styles/index.css +68 -14
  73. package/src/types/index.ts +200 -0
@@ -0,0 +1,267 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * KYCFlow - Multi-step KYC verification flow component
5
+ *
6
+ * Guides users through the KYC verification process and displays
7
+ * their current verification status and accreditation tier.
8
+ */
9
+
10
+ import { useState, useEffect, useCallback } from 'react';
11
+ import { useAccount } from 'wagmi';
12
+ import { useRWA } from '../hooks/useRWA';
13
+ import type { KYCFlowProps, VerificationStatus } from '../types';
14
+ import { AccreditationTier, type InvestorData } from '@mantle-rwa/sdk';
15
+
16
+ /**
17
+ * Get display name for accreditation tier
18
+ */
19
+ function getTierName(tier: AccreditationTier): string {
20
+ switch (tier) {
21
+ case AccreditationTier.None:
22
+ return 'None';
23
+ case AccreditationTier.Retail:
24
+ return 'Retail';
25
+ case AccreditationTier.Accredited:
26
+ return 'Accredited';
27
+ case AccreditationTier.Institutional:
28
+ return 'Institutional';
29
+ default:
30
+ return 'Unknown';
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Format date for display
36
+ */
37
+ function formatDate(date: Date): string {
38
+ return date.toLocaleDateString('en-US', {
39
+ year: 'numeric',
40
+ month: 'long',
41
+ day: 'numeric',
42
+ });
43
+ }
44
+
45
+ /**
46
+ * KYCFlow component for guiding users through KYC verification
47
+ */
48
+ export function KYCFlow({
49
+ registryAddress,
50
+ investorAddress,
51
+ onStatusChange,
52
+ onComplete,
53
+ onError,
54
+ className = '',
55
+ }: KYCFlowProps): JSX.Element {
56
+ const { client, isInitialized, hasSigner } = useRWA();
57
+ const { address: connectedAddress } = useAccount();
58
+
59
+ const targetAddress = investorAddress || connectedAddress;
60
+
61
+ const [status, setStatus] = useState<VerificationStatus>('idle');
62
+ const [investorInfo, setInvestorInfo] = useState<InvestorData | null>(null);
63
+ const [isLoading, setIsLoading] = useState(true);
64
+ const [error, setError] = useState<Error | null>(null);
65
+
66
+ // Update status and notify callback
67
+ const updateStatus = useCallback((newStatus: VerificationStatus) => {
68
+ setStatus(newStatus);
69
+ onStatusChange?.(newStatus);
70
+ }, [onStatusChange]);
71
+
72
+ // Fetch investor KYC data
73
+ const fetchKYCData = useCallback(async () => {
74
+ if (!client || !isInitialized || !registryAddress || !targetAddress) {
75
+ setIsLoading(false);
76
+ return;
77
+ }
78
+
79
+ setIsLoading(true);
80
+ setError(null);
81
+
82
+ try {
83
+ const registry = client.kyc.connect(registryAddress);
84
+ const info = await registry.getInvestorInfo(targetAddress);
85
+ setInvestorInfo(info);
86
+
87
+ // Determine status based on investor info
88
+ if (info.verified) {
89
+ updateStatus('completed');
90
+ onComplete?.(info.tier);
91
+ } else if (info.tier !== AccreditationTier.None) {
92
+ updateStatus('in_progress');
93
+ } else {
94
+ updateStatus('pending');
95
+ }
96
+ } catch (err) {
97
+ const errorObj = err instanceof Error ? err : new Error('Failed to fetch KYC data');
98
+ setError(errorObj);
99
+ updateStatus('failed');
100
+ onError?.(errorObj);
101
+ } finally {
102
+ setIsLoading(false);
103
+ }
104
+ }, [client, isInitialized, registryAddress, targetAddress, updateStatus, onComplete, onError]);
105
+
106
+ // Fetch data on mount and when dependencies change
107
+ useEffect(() => {
108
+ fetchKYCData();
109
+ }, [fetchKYCData]);
110
+
111
+ // Handle start verification
112
+ const handleStartVerification = useCallback(async () => {
113
+ if (!client || !targetAddress) return;
114
+
115
+ updateStatus('in_progress');
116
+
117
+ try {
118
+ // Initiate verification through the KYC provider
119
+ const session = await client.kyc.verifyInvestor(targetAddress);
120
+
121
+ // If there's a redirect URL, open it
122
+ if (session.redirectUrl) {
123
+ window.open(session.redirectUrl, '_blank');
124
+ }
125
+ } catch (err) {
126
+ const errorObj = err instanceof Error ? err : new Error('Failed to start verification');
127
+ setError(errorObj);
128
+ updateStatus('failed');
129
+ onError?.(errorObj);
130
+ }
131
+ }, [client, targetAddress, updateStatus, onError]);
132
+
133
+ // Handle retry
134
+ const handleRetry = useCallback(() => {
135
+ setError(null);
136
+ fetchKYCData();
137
+ }, [fetchKYCData]);
138
+
139
+ // Render loading state
140
+ if (isLoading) {
141
+ return (
142
+ <div className={`rwa-kyc-flow rwa-kyc-flow--loading ${className}`}>
143
+ <div className="flex items-center justify-center p-8">
144
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
145
+ <span className="ml-3 text-gray-600 dark:text-gray-300">Loading KYC status...</span>
146
+ </div>
147
+ </div>
148
+ );
149
+ }
150
+
151
+ // Render no wallet connected state
152
+ if (!targetAddress) {
153
+ return (
154
+ <div className={`rwa-kyc-flow rwa-kyc-flow--no-wallet ${className}`}>
155
+ <div className="p-6 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
156
+ <h3 className="text-lg font-semibold text-yellow-800 dark:text-yellow-200">
157
+ Wallet Not Connected
158
+ </h3>
159
+ <p className="mt-2 text-yellow-700 dark:text-yellow-300">
160
+ Please connect your wallet to view your KYC status.
161
+ </p>
162
+ </div>
163
+ </div>
164
+ );
165
+ }
166
+
167
+ // Render error state
168
+ if (status === 'failed' && error) {
169
+ return (
170
+ <div className={`rwa-kyc-flow rwa-kyc-flow--error ${className}`}>
171
+ <div className="p-6 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
172
+ <h3 className="text-lg font-semibold text-red-800 dark:text-red-200">
173
+ Verification Failed
174
+ </h3>
175
+ <p className="mt-2 text-red-700 dark:text-red-300">{error.message}</p>
176
+ <button
177
+ onClick={handleRetry}
178
+ className="mt-4 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors"
179
+ >
180
+ Retry
181
+ </button>
182
+ </div>
183
+ </div>
184
+ );
185
+ }
186
+
187
+ // Render completed state
188
+ if (status === 'completed' && investorInfo) {
189
+ return (
190
+ <div className={`rwa-kyc-flow rwa-kyc-flow--completed ${className}`}>
191
+ <div className="p-6 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
192
+ <div className="flex items-center">
193
+ <svg className="w-8 h-8 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
195
+ </svg>
196
+ <h3 className="ml-3 text-lg font-semibold text-green-800 dark:text-green-200">
197
+ Verification Complete
198
+ </h3>
199
+ </div>
200
+ <div className="mt-4 space-y-2">
201
+ <p className="text-green-700 dark:text-green-300">
202
+ <span className="font-medium">Accreditation Tier:</span> {getTierName(investorInfo.tier)}
203
+ </p>
204
+ {investorInfo.expiry && (
205
+ <p className="text-green-700 dark:text-green-300">
206
+ <span className="font-medium">Valid Until:</span> {formatDate(investorInfo.expiry)}
207
+ </p>
208
+ )}
209
+ </div>
210
+ </div>
211
+ </div>
212
+ );
213
+ }
214
+
215
+ // Render in progress state
216
+ if (status === 'in_progress') {
217
+ return (
218
+ <div className={`rwa-kyc-flow rwa-kyc-flow--in-progress ${className}`}>
219
+ <div className="p-6 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
220
+ <div className="flex items-center">
221
+ <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" />
222
+ <h3 className="ml-3 text-lg font-semibold text-blue-800 dark:text-blue-200">
223
+ Verification In Progress
224
+ </h3>
225
+ </div>
226
+ <p className="mt-4 text-blue-700 dark:text-blue-300">
227
+ Your identity verification is being processed. This may take a few minutes.
228
+ </p>
229
+ <button
230
+ onClick={fetchKYCData}
231
+ className="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors"
232
+ >
233
+ Check Status
234
+ </button>
235
+ </div>
236
+ </div>
237
+ );
238
+ }
239
+
240
+ // Render pending state (default)
241
+ return (
242
+ <div className={`rwa-kyc-flow rwa-kyc-flow--pending ${className}`}>
243
+ <div className="p-6 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
244
+ <h3 className="text-lg font-semibold text-gray-800 dark:text-gray-200">
245
+ KYC Verification Required
246
+ </h3>
247
+ <p className="mt-2 text-gray-600 dark:text-gray-400">
248
+ Complete identity verification to participate in RWA token offerings.
249
+ </p>
250
+ {!hasSigner ? (
251
+ <p className="mt-4 text-yellow-600 dark:text-yellow-400 text-sm">
252
+ Connect your wallet to start verification.
253
+ </p>
254
+ ) : (
255
+ <button
256
+ onClick={handleStartVerification}
257
+ className="mt-4 px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors"
258
+ >
259
+ Start Verification
260
+ </button>
261
+ )}
262
+ </div>
263
+ </div>
264
+ );
265
+ }
266
+
267
+ export default KYCFlow;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * LoadingSpinner - Shared loading indicator component
5
+ */
6
+
7
+ import type { LoadingSpinnerProps } from '../types';
8
+
9
+ const sizeClasses = {
10
+ sm: 'h-4 w-4',
11
+ md: 'h-6 w-6',
12
+ lg: 'h-8 w-8',
13
+ };
14
+
15
+ /**
16
+ * LoadingSpinner component
17
+ */
18
+ export function LoadingSpinner({
19
+ size = 'md',
20
+ className = '',
21
+ }: LoadingSpinnerProps): JSX.Element {
22
+ return (
23
+ <div className={`rwa-loading-spinner ${className}`}>
24
+ <div
25
+ className={`animate-spin rounded-full border-b-2 border-blue-600 dark:border-blue-400 ${sizeClasses[size]}`}
26
+ role="status"
27
+ aria-label="Loading"
28
+ />
29
+ </div>
30
+ );
31
+ }
32
+
33
+ export default LoadingSpinner;
@@ -0,0 +1,291 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * TokenMintForm - Form for minting RWA tokens to verified investors
5
+ *
6
+ * Validates recipient address and KYC status before allowing mint.
7
+ */
8
+
9
+ import React, { useState, useCallback } from 'react';
10
+ import { useRWA } from '../hooks/useRWA';
11
+ import type { TokenMintFormProps } from '../types';
12
+ import { AccreditationTier, type TransactionResult } from '@mantle-rwa/sdk';
13
+
14
+ /**
15
+ * Validate Ethereum address format
16
+ */
17
+ export function isValidAddress(address: string): boolean {
18
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
19
+ }
20
+
21
+ /**
22
+ * Validate amount is positive
23
+ */
24
+ export function isValidAmount(amount: string): boolean {
25
+ const num = parseFloat(amount);
26
+ return !isNaN(num) && num > 0 && isFinite(num);
27
+ }
28
+
29
+ /**
30
+ * Get display name for accreditation tier
31
+ */
32
+ function getTierName(tier: AccreditationTier): string {
33
+ switch (tier) {
34
+ case AccreditationTier.None:
35
+ return 'None';
36
+ case AccreditationTier.Retail:
37
+ return 'Retail';
38
+ case AccreditationTier.Accredited:
39
+ return 'Accredited';
40
+ case AccreditationTier.Institutional:
41
+ return 'Institutional';
42
+ default:
43
+ return 'Unknown';
44
+ }
45
+ }
46
+
47
+ interface FormState {
48
+ recipient: string;
49
+ amount: string;
50
+ }
51
+
52
+ interface ValidationState {
53
+ recipientError: string | null;
54
+ amountError: string | null;
55
+ }
56
+
57
+ interface KYCCheckState {
58
+ isChecking: boolean;
59
+ isVerified: boolean | null;
60
+ tier: AccreditationTier | null;
61
+ }
62
+
63
+ /**
64
+ * TokenMintForm component
65
+ */
66
+ export function TokenMintForm({
67
+ tokenAddress,
68
+ kycRegistryAddress,
69
+ onSuccess,
70
+ onError,
71
+ className = '',
72
+ }: TokenMintFormProps): JSX.Element {
73
+ const { client, isInitialized, hasSigner } = useRWA();
74
+
75
+ const [form, setForm] = useState<FormState>({ recipient: '', amount: '' });
76
+ const [validation, setValidation] = useState<ValidationState>({ recipientError: null, amountError: null });
77
+ const [kycCheck, setKycCheck] = useState<KYCCheckState>({ isChecking: false, isVerified: null, tier: null });
78
+ const [isPending, setIsPending] = useState(false);
79
+ const [error, setError] = useState<Error | null>(null);
80
+ const [txResult, setTxResult] = useState<TransactionResult | null>(null);
81
+
82
+ // Validate recipient address
83
+ const validateRecipient = useCallback((address: string): string | null => {
84
+ if (!address) return 'Recipient address is required';
85
+ if (!isValidAddress(address)) return 'Invalid Ethereum address format';
86
+ return null;
87
+ }, []);
88
+
89
+ // Validate amount
90
+ const validateAmount = useCallback((amount: string): string | null => {
91
+ if (!amount) return 'Amount is required';
92
+ if (!isValidAmount(amount)) return 'Amount must be a positive number';
93
+ return null;
94
+ }, []);
95
+
96
+ // Handle recipient change
97
+ const handleRecipientChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
98
+ const value = e.target.value;
99
+ setForm(prev => ({ ...prev, recipient: value }));
100
+ setValidation(prev => ({ ...prev, recipientError: validateRecipient(value) }));
101
+ setKycCheck({ isChecking: false, isVerified: null, tier: null });
102
+ setTxResult(null);
103
+ }, [validateRecipient]);
104
+
105
+ // Handle amount change
106
+ const handleAmountChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
107
+ const value = e.target.value;
108
+ setForm(prev => ({ ...prev, amount: value }));
109
+ setValidation(prev => ({ ...prev, amountError: validateAmount(value) }));
110
+ setTxResult(null);
111
+ }, [validateAmount]);
112
+
113
+ // Check KYC status
114
+ const checkKYC = useCallback(async () => {
115
+ if (!client || !isInitialized || !form.recipient || !isValidAddress(form.recipient)) {
116
+ return;
117
+ }
118
+
119
+ setKycCheck({ isChecking: true, isVerified: null, tier: null });
120
+
121
+ try {
122
+ const registry = client.kyc.connect(kycRegistryAddress);
123
+ const info = await registry.getInvestorInfo(form.recipient);
124
+ setKycCheck({
125
+ isChecking: false,
126
+ isVerified: info.verified,
127
+ tier: info.tier,
128
+ });
129
+ } catch (err) {
130
+ setKycCheck({ isChecking: false, isVerified: false, tier: null });
131
+ }
132
+ }, [client, isInitialized, form.recipient, kycRegistryAddress]);
133
+
134
+ // Handle form submission
135
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
136
+ e.preventDefault();
137
+
138
+ // Validate all fields
139
+ const recipientError = validateRecipient(form.recipient);
140
+ const amountError = validateAmount(form.amount);
141
+
142
+ setValidation({ recipientError, amountError });
143
+
144
+ if (recipientError || amountError) {
145
+ return;
146
+ }
147
+
148
+ // Check KYC if not already checked
149
+ if (kycCheck.isVerified === null) {
150
+ await checkKYC();
151
+ return; // User needs to submit again after KYC check
152
+ }
153
+
154
+ // Block if not verified
155
+ if (!kycCheck.isVerified) {
156
+ setError(new Error('Recipient is not KYC verified. Cannot mint tokens to unverified addresses.'));
157
+ return;
158
+ }
159
+
160
+ if (!client || !hasSigner) {
161
+ setError(new Error('Wallet not connected'));
162
+ return;
163
+ }
164
+
165
+ setIsPending(true);
166
+ setError(null);
167
+ setTxResult(null);
168
+
169
+ try {
170
+ const token = client.token.connect(tokenAddress);
171
+ const result = await token.mint(form.recipient, form.amount);
172
+ setTxResult(result);
173
+ onSuccess?.(result);
174
+ // Clear form on success
175
+ setForm({ recipient: '', amount: '' });
176
+ setKycCheck({ isChecking: false, isVerified: null, tier: null });
177
+ } catch (err) {
178
+ const errorObj = err instanceof Error ? err : new Error('Mint failed');
179
+ setError(errorObj);
180
+ onError?.(errorObj);
181
+ } finally {
182
+ setIsPending(false);
183
+ }
184
+ }, [form, validation, kycCheck, client, hasSigner, tokenAddress, validateRecipient, validateAmount, checkKYC, onSuccess, onError]);
185
+
186
+ const isFormValid = !validation.recipientError && !validation.amountError && form.recipient && form.amount;
187
+ const canSubmit = isFormValid && hasSigner && !isPending && (kycCheck.isVerified === null || kycCheck.isVerified);
188
+
189
+ return (
190
+ <div className={`rwa-token-mint-form ${className}`}>
191
+ <form onSubmit={handleSubmit} className="space-y-6">
192
+ {/* Recipient Address Field */}
193
+ <div>
194
+ <label htmlFor="recipient" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
195
+ Recipient Address
196
+ </label>
197
+ <input
198
+ type="text"
199
+ id="recipient"
200
+ value={form.recipient}
201
+ onChange={handleRecipientChange}
202
+ onBlur={checkKYC}
203
+ placeholder="0x..."
204
+ className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white ${validation.recipientError
205
+ ? 'border-red-500 focus:border-red-500'
206
+ : 'border-gray-300 dark:border-gray-600 focus:border-blue-500'
207
+ }`}
208
+ />
209
+ {validation.recipientError && (
210
+ <p className="mt-1 text-sm text-red-600 dark:text-red-400">{validation.recipientError}</p>
211
+ )}
212
+
213
+ {/* KYC Status Display */}
214
+ {kycCheck.isChecking && (
215
+ <p className="mt-2 text-sm text-gray-500 dark:text-gray-400">Checking KYC status...</p>
216
+ )}
217
+ {kycCheck.isVerified === true && (
218
+ <p className="mt-2 text-sm text-green-600 dark:text-green-400">
219
+ ✓ Verified ({getTierName(kycCheck.tier!)})
220
+ </p>
221
+ )}
222
+ {kycCheck.isVerified === false && (
223
+ <p className="mt-2 text-sm text-red-600 dark:text-red-400">
224
+ ✗ Not KYC verified - cannot mint to this address
225
+ </p>
226
+ )}
227
+ </div>
228
+
229
+ {/* Amount Field */}
230
+ <div>
231
+ <label htmlFor="amount" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
232
+ Amount
233
+ </label>
234
+ <input
235
+ type="text"
236
+ id="amount"
237
+ value={form.amount}
238
+ onChange={handleAmountChange}
239
+ placeholder="0.00"
240
+ className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-800 dark:text-white ${validation.amountError
241
+ ? 'border-red-500 focus:border-red-500'
242
+ : 'border-gray-300 dark:border-gray-600 focus:border-blue-500'
243
+ }`}
244
+ />
245
+ {validation.amountError && (
246
+ <p className="mt-1 text-sm text-red-600 dark:text-red-400">{validation.amountError}</p>
247
+ )}
248
+ </div>
249
+
250
+ {/* Wallet Connection Warning */}
251
+ {!hasSigner && (
252
+ <div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-md border border-yellow-200 dark:border-yellow-800">
253
+ <p className="text-sm text-yellow-700 dark:text-yellow-300">
254
+ Connect your wallet to mint tokens.
255
+ </p>
256
+ </div>
257
+ )}
258
+
259
+ {/* Error Display */}
260
+ {error && (
261
+ <div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-md border border-red-200 dark:border-red-800">
262
+ <p className="text-sm text-red-700 dark:text-red-300">{error.message}</p>
263
+ </div>
264
+ )}
265
+
266
+ {/* Success Display */}
267
+ {txResult && (
268
+ <div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-md border border-green-200 dark:border-green-800">
269
+ <p className="text-sm text-green-700 dark:text-green-300">
270
+ ✓ Mint successful! Transaction: {txResult.hash.slice(0, 10)}...
271
+ </p>
272
+ </div>
273
+ )}
274
+
275
+ {/* Submit Button */}
276
+ <button
277
+ type="submit"
278
+ disabled={!canSubmit}
279
+ className={`w-full py-2 px-4 rounded-md font-medium transition-colors ${canSubmit
280
+ ? 'bg-blue-600 hover:bg-blue-700 text-white'
281
+ : 'bg-gray-300 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed'
282
+ }`}
283
+ >
284
+ {isPending ? 'Minting...' : kycCheck.isVerified === null && form.recipient ? 'Check KYC & Mint' : 'Mint Tokens'}
285
+ </button>
286
+ </form>
287
+ </div>
288
+ );
289
+ }
290
+
291
+ export default TokenMintForm;