primus-saas-react 1.0.3 → 1.0.5

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 (45) hide show
  1. package/dist/styles.css +568 -0
  2. package/package.json +15 -9
  3. package/DEMO.md +0 -68
  4. package/INTEGRATION.md +0 -702
  5. package/build_log.txt +0 -0
  6. package/postcss.config.js +0 -6
  7. package/src/components/ai/AICopilot.tsx +0 -88
  8. package/src/components/auth/PrimusLogin.tsx +0 -298
  9. package/src/components/auth/UserProfile.tsx +0 -26
  10. package/src/components/banking/accounts/AccountDashboard.tsx +0 -67
  11. package/src/components/banking/cards/CreditCardVisual.tsx +0 -67
  12. package/src/components/banking/credit/CreditScoreCard.tsx +0 -80
  13. package/src/components/banking/kyc/KYCVerification.tsx +0 -76
  14. package/src/components/banking/loans/LoanCalculator.tsx +0 -106
  15. package/src/components/banking/transactions/TransactionHistory.tsx +0 -74
  16. package/src/components/crud/PrimusDataTable.tsx +0 -220
  17. package/src/components/crud/PrimusModal.tsx +0 -68
  18. package/src/components/dashboard/PrimusDashboard.tsx +0 -145
  19. package/src/components/documents/DocumentViewer.tsx +0 -107
  20. package/src/components/featureflags/FeatureFlagToggle.tsx +0 -64
  21. package/src/components/insurance/agents/AgentDirectory.tsx +0 -72
  22. package/src/components/insurance/claims/ClaimStatusTracker.tsx +0 -78
  23. package/src/components/insurance/fraud/FraudDetectionDashboard.tsx +0 -68
  24. package/src/components/insurance/policies/PolicyCard.tsx +0 -77
  25. package/src/components/insurance/premium/PremiumCalculator.tsx +0 -104
  26. package/src/components/insurance/quotes/QuoteComparison.tsx +0 -75
  27. package/src/components/layout/PrimusHeader.tsx +0 -75
  28. package/src/components/layout/PrimusLayout.tsx +0 -47
  29. package/src/components/layout/PrimusSidebar.tsx +0 -102
  30. package/src/components/logging/LogViewer.tsx +0 -90
  31. package/src/components/notifications/NotificationFeed.tsx +0 -106
  32. package/src/components/notifications/PrimusNotificationCenter.tsx +0 -282
  33. package/src/components/payments/CheckoutForm.tsx +0 -167
  34. package/src/components/security/SecurityDashboard.tsx +0 -83
  35. package/src/components/shared/Button.tsx +0 -36
  36. package/src/components/shared/Input.tsx +0 -36
  37. package/src/components/storage/FileUploader.tsx +0 -79
  38. package/src/context/PrimusProvider.tsx +0 -156
  39. package/src/context/PrimusThemeProvider.tsx +0 -160
  40. package/src/hooks/useNotifications.ts +0 -58
  41. package/src/hooks/usePrimusAuth.ts +0 -3
  42. package/src/hooks/useRealtimeNotifications.ts +0 -114
  43. package/src/index.ts +0 -42
  44. package/tailwind.config.js +0 -18
  45. package/tsconfig.json +0 -28
@@ -1,88 +0,0 @@
1
- import React, { useState } from 'react';
2
-
3
- export const AICopilot: React.FC<{ apiUrl?: string }> = ({
4
- apiUrl = 'http://localhost:5221'
5
- }) => {
6
- const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([]);
7
- const [input, setInput] = useState('');
8
- const [loading, setLoading] = useState(false);
9
-
10
- const sendMessage = async () => {
11
- if (!input.trim()) return;
12
-
13
- const userMessage = { role: 'user', content: input };
14
- setMessages(prev => [...prev, userMessage]);
15
- setInput('');
16
- setLoading(true);
17
-
18
- try {
19
- const response = await fetch(`${apiUrl}/api/ai/chat`, {
20
- method: 'POST',
21
- headers: { 'Content-Type': 'application/json' },
22
- body: JSON.stringify({ message: input })
23
- });
24
-
25
- if (response.ok) {
26
- const data = await response.json();
27
- setMessages(prev => [...prev, { role: 'assistant', content: data.message }]);
28
- }
29
- } catch (error) {
30
- console.error('Failed to send message:', error);
31
- } finally {
32
- setLoading(false);
33
- }
34
- };
35
-
36
- return (
37
- <div className="bg-white rounded-lg border border-gray-200 shadow-sm flex flex-col h-[600px]">
38
- <div className="p-4 border-b border-gray-200">
39
- <h3 className="text-lg font-medium text-gray-900">AI Copilot</h3>
40
- </div>
41
-
42
- <div className="flex-1 overflow-y-auto p-4 space-y-4">
43
- {messages.map((msg, index) => (
44
- <div key={index} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
45
- <div className={`max-w-[70%] p-3 rounded-lg ${msg.role === 'user'
46
- ? 'bg-primus-600 text-white'
47
- : 'bg-gray-100 text-gray-900'
48
- }`}>
49
- {msg.content}
50
- </div>
51
- </div>
52
- ))}
53
- {loading && (
54
- <div className="flex justify-start">
55
- <div className="bg-gray-100 p-3 rounded-lg">
56
- <div className="flex gap-1">
57
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
58
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
59
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
60
- </div>
61
- </div>
62
- </div>
63
- )}
64
- </div>
65
-
66
- <div className="p-4 border-t border-gray-200">
67
- <div className="flex gap-2">
68
- <input
69
- type="text"
70
- value={input}
71
- onChange={(e) => setInput(e.target.value)}
72
- onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
73
- placeholder="Ask me anything..."
74
- className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primus-500"
75
- disabled={loading}
76
- />
77
- <button
78
- onClick={sendMessage}
79
- disabled={loading || !input.trim()}
80
- className="px-4 py-2 bg-primus-600 text-white rounded-lg hover:bg-primus-700 disabled:opacity-50"
81
- >
82
- Send
83
- </button>
84
- </div>
85
- </div>
86
- </div>
87
- );
88
- };
@@ -1,298 +0,0 @@
1
- import { useState } from 'react';
2
- import { usePrimusTheme } from '../../context/PrimusThemeProvider';
3
-
4
- // ============================================================
5
- // TYPES
6
- // ============================================================
7
-
8
- export type SocialProvider = 'google' | 'azure' | 'github' | 'apple' | 'microsoft' | 'facebook';
9
-
10
- export interface PrimusLoginProps {
11
- /** Callback when login is successful */
12
- onLogin?: (credentials: { username: string; password: string }) => void;
13
-
14
- /**
15
- * Social login providers to display
16
- * @example socialProviders={['google', 'azure']}
17
- */
18
- socialProviders?: SocialProvider[];
19
-
20
- /** Callback when social provider is clicked */
21
- onSocialLogin?: (provider: SocialProvider) => void;
22
-
23
- /** OAuth endpoint base URL (default: /api/auth) */
24
- authEndpoint?: string;
25
-
26
- /** Show email/password form (default: true) */
27
- showEmailLogin?: boolean;
28
-
29
- /** App title displayed above form */
30
- title?: string;
31
-
32
- /** Subtitle/description text */
33
- subtitle?: string;
34
-
35
- /** Logo element or URL */
36
- logo?: React.ReactNode | string;
37
-
38
- /** Force specific theme */
39
- theme?: 'light' | 'dark';
40
- }
41
-
42
- // ============================================================
43
- // PROVIDER CONFIGS (internal)
44
- // ============================================================
45
-
46
- const providerConfigs: Record<SocialProvider, { name: string; icon: JSX.Element; colors: { light: string; dark: string } }> = {
47
- google: {
48
- name: 'Google',
49
- icon: (
50
- <svg className="h-5 w-5" viewBox="0 0 24 24">
51
- <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
52
- <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
53
- <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
54
- <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
55
- </svg>
56
- ),
57
- colors: { light: 'bg-white hover:bg-gray-50 text-gray-700 border-gray-300', dark: 'bg-white hover:bg-gray-100 text-gray-700 border-gray-300' }
58
- },
59
- azure: {
60
- name: 'Microsoft',
61
- icon: (
62
- <svg className="h-5 w-5" viewBox="0 0 23 23">
63
- <path fill="#f35325" d="M1 1h10v10H1z" />
64
- <path fill="#81bc06" d="M12 1h10v10H12z" />
65
- <path fill="#05a6f0" d="M1 12h10v10H1z" />
66
- <path fill="#ffba08" d="M12 12h10v10H12z" />
67
- </svg>
68
- ),
69
- colors: { light: 'bg-white hover:bg-gray-50 text-gray-700 border-gray-300', dark: 'bg-white hover:bg-gray-100 text-gray-700 border-gray-300' }
70
- },
71
- github: {
72
- name: 'GitHub',
73
- icon: (
74
- <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
75
- <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
76
- </svg>
77
- ),
78
- colors: { light: 'bg-gray-900 hover:bg-gray-800 text-white border-gray-900', dark: 'bg-white hover:bg-gray-100 text-gray-900 border-gray-300' }
79
- },
80
- microsoft: {
81
- name: 'Microsoft',
82
- icon: (
83
- <svg className="h-5 w-5" viewBox="0 0 24 24">
84
- <path fill="#F25022" d="M1 1h10v10H1z" />
85
- <path fill="#00A4EF" d="M1 13h10v10H1z" />
86
- <path fill="#7FBA00" d="M13 1h10v10H13z" />
87
- <path fill="#FFB900" d="M13 13h10v10H13z" />
88
- </svg>
89
- ),
90
- colors: { light: 'bg-white hover:bg-gray-50 text-gray-700 border-gray-300', dark: 'bg-white hover:bg-gray-100 text-gray-700 border-gray-300' }
91
- },
92
- apple: {
93
- name: 'Apple',
94
- icon: (
95
- <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
96
- <path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
97
- </svg>
98
- ),
99
- colors: { light: 'bg-black hover:bg-gray-900 text-white border-black', dark: 'bg-white hover:bg-gray-100 text-black border-gray-300' }
100
- },
101
- facebook: {
102
- name: 'Facebook',
103
- icon: (
104
- <svg className="h-5 w-5" fill="white" viewBox="0 0 24 24">
105
- <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
106
- </svg>
107
- ),
108
- colors: { light: 'bg-[#1877F2] hover:bg-[#166FE5] text-white border-[#1877F2]', dark: 'bg-[#1877F2] hover:bg-[#166FE5] text-white border-[#1877F2]' }
109
- },
110
- };
111
-
112
- // ============================================================
113
- // COMPONENT
114
- // ============================================================
115
-
116
- /**
117
- * PrimusLogin - Complete Login Component
118
- *
119
- * A single component that handles all login methods. Enable features via props.
120
- *
121
- * @example
122
- * ```tsx
123
- * // Basic email/password only
124
- * <PrimusLogin onLogin={(creds) => console.log(creds)} />
125
- *
126
- * // With social providers
127
- * <PrimusLogin
128
- * socialProviders={['google', 'azure']}
129
- * onSocialLogin={(provider) => redirectToOAuth(provider)}
130
- * />
131
- *
132
- * // Social only (no email form)
133
- * <PrimusLogin
134
- * showEmailLogin={false}
135
- * socialProviders={['google', 'github', 'azure']}
136
- * />
137
- * ```
138
- */
139
- export function PrimusLogin({
140
- onLogin,
141
- socialProviders = [],
142
- onSocialLogin,
143
- authEndpoint = '/api/auth',
144
- showEmailLogin = true,
145
- title = 'Welcome Back',
146
- subtitle = 'Enter your credentials to continue',
147
- logo,
148
- theme: themeOverride,
149
- }: PrimusLoginProps) {
150
- const themeContext = usePrimusTheme();
151
- const theme = themeOverride || themeContext.theme;
152
- const isLight = theme === 'light';
153
-
154
- const [username, setUsername] = useState('');
155
- const [password, setPassword] = useState('');
156
- const [loading, setLoading] = useState(false);
157
-
158
- const handleSubmit = async (e: React.FormEvent) => {
159
- e.preventDefault();
160
- if (!username || !password) return;
161
-
162
- setLoading(true);
163
- try {
164
- onLogin?.({ username, password });
165
- } finally {
166
- setLoading(false);
167
- }
168
- };
169
-
170
- const handleSocialClick = (provider: SocialProvider) => {
171
- if (onSocialLogin) {
172
- onSocialLogin(provider);
173
- } else {
174
- window.location.href = `${authEndpoint}/${provider}`;
175
- }
176
- };
177
-
178
- // Theme colors
179
- const bgGradient = isLight
180
- ? 'bg-gradient-to-br from-gray-50 via-white to-violet-50'
181
- : 'bg-gray-950';
182
- const cardBg = isLight
183
- ? 'bg-white border-gray-200 shadow-xl'
184
- : 'bg-gray-900/50 border-white/10';
185
- const headingColor = isLight ? 'text-gray-900' : 'text-white';
186
- const subtextColor = isLight ? 'text-gray-500' : 'text-slate-400';
187
- const labelColor = isLight ? 'text-gray-600' : 'text-slate-300';
188
- const inputClasses = isLight
189
- ? 'bg-gray-50 border-gray-300 text-gray-900 placeholder:text-gray-400 focus:border-violet-500 focus:ring-violet-500'
190
- : 'bg-gray-950/50 border-slate-700 text-white placeholder:text-slate-600 focus:border-blue-500 focus:ring-blue-500';
191
- const buttonGradient = isLight
192
- ? 'bg-gradient-to-r from-violet-600 to-purple-600'
193
- : 'bg-gradient-to-r from-blue-600 to-indigo-600';
194
- const logoBg = isLight ? 'bg-violet-600 shadow-violet-500/20' : 'bg-blue-600 shadow-blue-500/20';
195
- const dividerColor = isLight ? 'border-gray-200' : 'border-slate-700';
196
-
197
- const hasSocial = socialProviders.length > 0;
198
-
199
- return (
200
- <div className={`min-h-screen ${bgGradient} flex items-center justify-center p-4`}>
201
- {!isLight && (
202
- <div className="absolute inset-0 overflow-hidden">
203
- <div className="absolute -top-[20%] -left-[10%] w-[50%] h-[50%] bg-blue-600/20 rounded-full blur-[120px]" />
204
- <div className="absolute -bottom-[20%] -right-[10%] w-[50%] h-[50%] bg-purple-600/20 rounded-full blur-[120px]" />
205
- </div>
206
- )}
207
-
208
- <div className={`w-full max-w-md ${cardBg} p-8 rounded-2xl border backdrop-blur-xl relative z-10`}>
209
- {/* Logo & Title */}
210
- <div className="text-center mb-8">
211
- {logo ? (
212
- typeof logo === 'string'
213
- ? <img src={logo} alt="Logo" className="h-12 w-12 mx-auto mb-4 rounded-xl" />
214
- : <div className="mb-4">{logo}</div>
215
- ) : (
216
- <div className={`h-12 w-12 ${logoBg} rounded-xl mx-auto flex items-center justify-center mb-4 shadow-lg`}>
217
- <span className="font-bold text-xl text-white">P</span>
218
- </div>
219
- )}
220
- <h1 className={`text-2xl font-bold ${headingColor} tracking-tight`}>{title}</h1>
221
- <p className={`${subtextColor} text-sm mt-2`}>{subtitle}</p>
222
- </div>
223
-
224
- {/* Social Login Buttons */}
225
- {hasSocial && (
226
- <div className="space-y-3 mb-6">
227
- {socialProviders.map((provider) => {
228
- const config = providerConfigs[provider];
229
- if (!config) return null;
230
-
231
- return (
232
- <button
233
- key={provider}
234
- onClick={() => handleSocialClick(provider)}
235
- className={`w-full flex items-center justify-center gap-3 px-4 py-2.5 border rounded-lg font-medium text-sm transition-all duration-200 ${isLight ? config.colors.light : config.colors.dark
236
- }`}
237
- >
238
- {config.icon}
239
- <span>Continue with {config.name}</span>
240
- </button>
241
- );
242
- })}
243
- </div>
244
- )}
245
-
246
- {/* Divider */}
247
- {hasSocial && showEmailLogin && (
248
- <div className="relative my-6">
249
- <div className="absolute inset-0 flex items-center">
250
- <div className={`w-full border-t ${dividerColor}`}></div>
251
- </div>
252
- <div className="relative flex justify-center text-xs uppercase">
253
- <span className={`${isLight ? 'bg-white' : 'bg-gray-900/50'} px-2 ${subtextColor}`}>
254
- Or continue with email
255
- </span>
256
- </div>
257
- </div>
258
- )}
259
-
260
- {/* Email/Password Form */}
261
- {showEmailLogin && (
262
- <form onSubmit={handleSubmit} className="space-y-4">
263
- <div className="space-y-2">
264
- <label className={`text-xs font-medium ${labelColor} ml-1`}>EMAIL</label>
265
- <input
266
- type="text"
267
- value={username}
268
- onChange={e => setUsername(e.target.value)}
269
- className={`w-full px-4 py-2.5 ${inputClasses} border rounded-lg focus:outline-none focus:ring-1 transition-all text-sm`}
270
- placeholder="you@example.com"
271
- />
272
- </div>
273
- <div className="space-y-2">
274
- <label className={`text-xs font-medium ${labelColor} ml-1`}>PASSWORD</label>
275
- <input
276
- type="password"
277
- value={password}
278
- onChange={e => setPassword(e.target.value)}
279
- className={`w-full px-4 py-2.5 ${inputClasses} border rounded-lg focus:outline-none focus:ring-1 transition-all text-sm`}
280
- placeholder="••••••••"
281
- />
282
- </div>
283
- <button
284
- type="submit"
285
- disabled={loading}
286
- className={`w-full mt-4 py-2.5 ${buttonGradient} text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50`}
287
- >
288
- {loading ? 'Signing in...' : 'Sign In'}
289
- </button>
290
- </form>
291
- )}
292
- </div>
293
- </div>
294
- );
295
- }
296
-
297
- // Export alias
298
- export const LoginPage = PrimusLogin;
@@ -1,26 +0,0 @@
1
- import React from 'react';
2
- import { usePrimusAuth } from '../../hooks/usePrimusAuth';
3
- import { Button } from '../shared/Button';
4
-
5
- export const UserProfile: React.FC = () => {
6
- const { user, logout, isAuthenticated } = usePrimusAuth();
7
-
8
- if (!isAuthenticated || !user) {
9
- return null;
10
- }
11
-
12
- return (
13
- <div className="flex items-center gap-4 p-4 rounded-lg bg-gray-50 border border-gray-200">
14
- <div className="h-10 w-10 rounded-full bg-primus-100 flex items-center justify-center text-primus-600 font-bold">
15
- {user.name?.charAt(0) || 'U'}
16
- </div>
17
- <div className="flex-1">
18
- <p className="text-sm font-medium text-gray-900">{user.name}</p>
19
- <p className="text-xs text-gray-500">{user.email}</p>
20
- </div>
21
- <Button variant="outline" size="sm" onClick={() => logout()}>
22
- Sign out
23
- </Button>
24
- </div>
25
- );
26
- };
@@ -1,67 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
-
3
- interface Account {
4
- id: string;
5
- accountNumber: string;
6
- type: string;
7
- balance: number;
8
- currency: string;
9
- status: string;
10
- }
11
-
12
- export const AccountDashboard: React.FC<{ apiUrl?: string }> = ({
13
- apiUrl = 'http://localhost:5221'
14
- }) => {
15
- const [accounts, setAccounts] = useState<Account[]>([]);
16
- const [loading, setLoading] = useState(true);
17
-
18
- useEffect(() => {
19
- fetchAccounts();
20
- }, []);
21
-
22
- const fetchAccounts = async () => {
23
- try {
24
- const response = await fetch(`${apiUrl}/api/banking/accounts`);
25
- if (response.ok) {
26
- const data = await response.json();
27
- setAccounts(data);
28
- }
29
- } catch (error) {
30
- console.error('Failed to fetch accounts:', error);
31
- } finally {
32
- setLoading(false);
33
- }
34
- };
35
-
36
- if (loading) return <div className="p-4">Loading accounts...</div>;
37
-
38
- return (
39
- <div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
40
- <h3 className="text-lg font-medium text-gray-900 mb-4">Bank Accounts</h3>
41
- <div className="space-y-4">
42
- {accounts.map((account) => (
43
- <div key={account.id} className="p-4 bg-gray-50 rounded-lg border border-gray-200">
44
- <div className="flex justify-between items-start">
45
- <div>
46
- <h4 className="font-medium text-gray-900">{account.type}</h4>
47
- <p className="text-sm text-gray-500">{account.accountNumber}</p>
48
- </div>
49
- <div className="text-right">
50
- <p className="text-2xl font-bold text-gray-900">
51
- ${account.balance.toLocaleString()}
52
- </p>
53
- <p className="text-sm text-gray-500">{account.currency}</p>
54
- </div>
55
- </div>
56
- <div className="mt-2">
57
- <span className={`px-2 py-1 rounded text-xs font-medium ${account.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
58
- }`}>
59
- {account.status}
60
- </span>
61
- </div>
62
- </div>
63
- ))}
64
- </div>
65
- </div>
66
- );
67
- };
@@ -1,67 +0,0 @@
1
- import React from 'react';
2
- import { clsx } from 'clsx';
3
- import { WifiIcon } from '@heroicons/react/24/outline'; // Approximate for contactless
4
-
5
- export interface CreditCardProps {
6
- cardHolder: string;
7
- last4: string;
8
- expiry: string;
9
- brand?: 'visa' | 'mastercard' | 'amex';
10
- variant?: 'black' | 'blue' | 'gold' | 'platinum';
11
- }
12
-
13
- export const CreditCardVisual: React.FC<CreditCardProps> = ({
14
- cardHolder,
15
- last4,
16
- expiry,
17
- brand = 'visa',
18
- variant = 'black'
19
- }) => {
20
- const getBackground = () => {
21
- switch (variant) {
22
- case 'blue': return 'bg-gradient-to-br from-blue-600 to-blue-800';
23
- case 'gold': return 'bg-gradient-to-br from-yellow-500 to-yellow-700';
24
- case 'platinum': return 'bg-gradient-to-br from-gray-300 to-gray-500';
25
- default: return 'bg-gradient-to-br from-gray-900 to-gray-800';
26
- }
27
- };
28
-
29
- return (
30
- <div className={clsx(
31
- "w-80 h-48 rounded-2xl p-6 text-white shadow-xl relative overflow-hidden transition-transform hover:scale-105 duration-300",
32
- getBackground()
33
- )}>
34
- {/* Decorative circles */}
35
- <div className="absolute top-0 right-0 -mr-10 -mt-10 w-40 h-40 bg-white opacity-5 rounded-full blur-xl" />
36
- <div className="absolute bottom-0 left-0 -ml-10 -mb-10 w-40 h-40 bg-white opacity-5 rounded-full blur-xl" />
37
-
38
- <div className="flex justify-between items-start mb-8">
39
- {/* Chip */}
40
- <div className="w-12 h-9 bg-yellow-400 rounded-md opacity-80 flex items-center justify-center">
41
- <div className="w-8 h-6 border border-yellow-600 rounded opacity-50" />
42
- </div>
43
- <WifiIcon className="h-6 w-6 rotate-90 opacity-70" />
44
- </div>
45
-
46
- <div className="mb-6">
47
- <p className="font-mono text-xl tracking-widest text-shadow">
48
- •••• •••• •••• {last4}
49
- </p>
50
- </div>
51
-
52
- <div className="flex justify-between items-end">
53
- <div>
54
- <p className="text-[10px] uppercase text-gray-300 tracking-wider mb-1">Card Holder</p>
55
- <p className="font-medium tracking-wide uppercase text-sm truncate max-w-[160px]">{cardHolder}</p>
56
- </div>
57
- <div>
58
- <p className="text-[10px] uppercase text-gray-300 tracking-wider mb-1">Expires</p>
59
- <p className="font-medium tracking-wide text-sm">{expiry}</p>
60
- </div>
61
- <div className="font-bold text-lg italic opacity-90">
62
- {brand.toUpperCase()}
63
- </div>
64
- </div>
65
- </div>
66
- );
67
- };
@@ -1,80 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
-
3
- interface CreditScore {
4
- score: number;
5
- rating: string;
6
- lastUpdated: string;
7
- factors: Array<{ factor: string; impact: string; weight: number }>;
8
- }
9
-
10
- export const CreditScoreCard: React.FC<{ userId: string; apiUrl?: string }> = ({
11
- userId,
12
- apiUrl = 'http://localhost:5221'
13
- }) => {
14
- const [score, setScore] = useState<CreditScore | null>(null);
15
- const [loading, setLoading] = useState(true);
16
-
17
- useEffect(() => {
18
- fetchScore();
19
- }, [userId]);
20
-
21
- const fetchScore = async () => {
22
- try {
23
- const response = await fetch(`${apiUrl}/api/banking/credit-score/${userId}`);
24
- if (response.ok) {
25
- const data = await response.json();
26
- setScore(data);
27
- }
28
- } catch (error) {
29
- console.error('Failed to fetch credit score:', error);
30
- } finally {
31
- setLoading(false);
32
- }
33
- };
34
-
35
- if (loading) return <div className="p-4">Loading credit score...</div>;
36
- if (!score) return <div className="p-4">No credit score data</div>;
37
-
38
- const getScoreColor = (score: number) => {
39
- if (score >= 740) return 'text-green-600';
40
- if (score >= 670) return 'text-blue-600';
41
- if (score >= 580) return 'text-yellow-600';
42
- return 'text-red-600';
43
- };
44
-
45
- return (
46
- <div className="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
47
- <h3 className="text-lg font-medium text-gray-900 mb-4">Credit Score</h3>
48
-
49
- <div className="text-center mb-6">
50
- <div className={`text-6xl font-bold ${getScoreColor(score.score)}`}>
51
- {score.score}
52
- </div>
53
- <div className="text-lg text-gray-600 mt-2">{score.rating}</div>
54
- <div className="text-sm text-gray-400 mt-1">
55
- Updated: {new Date(score.lastUpdated).toLocaleDateString()}
56
- </div>
57
- </div>
58
-
59
- <div>
60
- <h4 className="font-medium text-gray-900 mb-3">Score Factors</h4>
61
- <div className="space-y-2">
62
- {score.factors.map((factor, index) => (
63
- <div key={index} className="flex justify-between items-center p-2 bg-gray-50 rounded">
64
- <span className="text-sm text-gray-900">{factor.factor}</span>
65
- <div className="flex items-center gap-2">
66
- <span className="text-xs text-gray-500">{factor.weight}%</span>
67
- <span className={`px-2 py-0.5 rounded text-xs font-medium ${factor.impact === 'positive' ? 'bg-green-100 text-green-800' :
68
- factor.impact === 'negative' ? 'bg-red-100 text-red-800' :
69
- 'bg-gray-100 text-gray-800'
70
- }`}>
71
- {factor.impact}
72
- </span>
73
- </div>
74
- </div>
75
- ))}
76
- </div>
77
- </div>
78
- </div>
79
- );
80
- };