@realtimex/email-automator 2.2.0 → 2.2.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/api/server.ts +0 -6
  2. package/api/src/config/index.ts +3 -0
  3. package/bin/email-automator-setup.js +2 -3
  4. package/bin/email-automator.js +23 -7
  5. package/package.json +1 -2
  6. package/src/App.tsx +0 -622
  7. package/src/components/AccountSettings.tsx +0 -310
  8. package/src/components/AccountSettingsPage.tsx +0 -390
  9. package/src/components/Configuration.tsx +0 -1345
  10. package/src/components/Dashboard.tsx +0 -940
  11. package/src/components/ErrorBoundary.tsx +0 -71
  12. package/src/components/LiveTerminal.tsx +0 -308
  13. package/src/components/LoadingSpinner.tsx +0 -39
  14. package/src/components/Login.tsx +0 -371
  15. package/src/components/Logo.tsx +0 -57
  16. package/src/components/SetupWizard.tsx +0 -388
  17. package/src/components/Toast.tsx +0 -109
  18. package/src/components/migration/MigrationBanner.tsx +0 -97
  19. package/src/components/migration/MigrationModal.tsx +0 -458
  20. package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
  21. package/src/components/mode-toggle.tsx +0 -24
  22. package/src/components/theme-provider.tsx +0 -72
  23. package/src/components/ui/alert.tsx +0 -66
  24. package/src/components/ui/button.tsx +0 -57
  25. package/src/components/ui/card.tsx +0 -75
  26. package/src/components/ui/dialog.tsx +0 -133
  27. package/src/components/ui/input.tsx +0 -22
  28. package/src/components/ui/label.tsx +0 -24
  29. package/src/components/ui/otp-input.tsx +0 -184
  30. package/src/context/AppContext.tsx +0 -422
  31. package/src/context/MigrationContext.tsx +0 -53
  32. package/src/context/TerminalContext.tsx +0 -31
  33. package/src/core/actions.ts +0 -76
  34. package/src/core/auth.ts +0 -108
  35. package/src/core/intelligence.ts +0 -76
  36. package/src/core/processor.ts +0 -112
  37. package/src/hooks/useRealtimeEmails.ts +0 -111
  38. package/src/index.css +0 -140
  39. package/src/lib/api-config.ts +0 -42
  40. package/src/lib/api-old.ts +0 -228
  41. package/src/lib/api.ts +0 -421
  42. package/src/lib/migration-check.ts +0 -264
  43. package/src/lib/sounds.ts +0 -120
  44. package/src/lib/supabase-config.ts +0 -117
  45. package/src/lib/supabase.ts +0 -28
  46. package/src/lib/types.ts +0 -166
  47. package/src/lib/utils.ts +0 -6
  48. package/src/main.tsx +0 -10
@@ -1,390 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { User, Shield, Database, Save, Loader2, LogOut, Trash2, Settings, CheckCircle, XCircle, ExternalLink, Key, Camera, Volume2, VolumeX } from 'lucide-react';
3
- import { Card, CardHeader, CardTitle, CardContent, CardDescription } from './ui/card';
4
- import { Button } from './ui/button';
5
- import { Input } from './ui/input';
6
- import { Label } from './ui/label';
7
- import { Alert, AlertDescription } from './ui/alert';
8
- import { useApp } from '../context/AppContext';
9
- import { supabase } from '../lib/supabase';
10
- import { toast } from './Toast';
11
- import { LoadingSpinner } from './LoadingSpinner';
12
- import { getSupabaseConfig, clearSupabaseConfig, getConfigSource } from '../lib/supabase-config';
13
- import { SetupWizard } from './SetupWizard';
14
- import { sounds } from '../lib/sounds';
15
-
16
- type SettingsTab = 'profile' | 'security' | 'database';
17
-
18
- export function AccountSettingsPage() {
19
- const [activeTab, setActiveTab] = useState<SettingsTab>('profile');
20
- const { state, actions } = useApp();
21
-
22
- useEffect(() => {
23
- actions.fetchProfile();
24
- }, []);
25
-
26
- const tabs = [
27
- { id: 'profile', label: 'Profile', icon: User },
28
- { id: 'security', label: 'Security', icon: Shield },
29
- { id: 'database', label: 'Supabase', icon: Database },
30
- ];
31
-
32
- return (
33
- <div className="max-w-4xl mx-auto space-y-6 animate-in fade-in duration-500">
34
- <div className="flex flex-col gap-2">
35
- <h1 className="text-3xl font-bold tracking-tight">Account Settings</h1>
36
- <p className="text-muted-foreground">Manage your profile, security, and connection preferences.</p>
37
- </div>
38
-
39
- <div className="flex flex-col md:flex-row gap-8">
40
- {/* Sidebar Tabs */}
41
- <aside className="w-full md:w-64 space-y-1">
42
- {tabs.map((tab) => {
43
- const Icon = tab.icon;
44
- const isActive = activeTab === tab.id;
45
- return (
46
- <button
47
- key={tab.id}
48
- onClick={() => setActiveTab(tab.id as SettingsTab)}
49
- className={`w-full flex items-center gap-3 px-4 py-2 text-sm font-medium rounded-lg transition-colors ${
50
- isActive
51
- ? 'bg-primary text-primary-foreground'
52
- : 'hover:bg-secondary text-muted-foreground'
53
- }`}
54
- >
55
- <Icon className="w-4 h-4" />
56
- {tab.label}
57
- </button>
58
- );
59
- })}
60
- </aside>
61
-
62
- {/* Content Area */}
63
- <div className="flex-1 space-y-6">
64
- {activeTab === 'profile' && <ProfileSection />}
65
- {activeTab === 'security' && <SecuritySection />}
66
- {activeTab === 'database' && <DatabaseSection />}
67
- </div>
68
- </div>
69
- </div>
70
- );
71
- }
72
-
73
- function ProfileSection() {
74
- const { state, actions } = useApp();
75
- const [firstName, setFirstName] = useState('');
76
- const [lastName, setLastName] = useState('');
77
- const [isSaving, setIsSaving] = useState(false);
78
- const [isUploading, setIsUploading] = useState(false);
79
- const [soundsEnabled, setSoundsEnabled] = useState(sounds.isEnabled());
80
-
81
- useEffect(() => {
82
- if (state.profile) {
83
- setFirstName(state.profile.first_name || '');
84
- setLastName(state.profile.last_name || '');
85
- }
86
- }, [state.profile]);
87
-
88
- const toggleSounds = () => {
89
- const next = !soundsEnabled;
90
- sounds.setEnabled(next);
91
- setSoundsEnabled(next);
92
- if (next) {
93
- sounds.playSuccess();
94
- toast.success('Sound effects enabled');
95
- } else {
96
- toast.info('Sound effects disabled');
97
- }
98
- };
99
-
100
- const handleSave = async () => {
101
- setIsSaving(true);
102
- const success = await actions.updateProfile({
103
- first_name: firstName,
104
- last_name: lastName
105
- });
106
- setIsSaving(false);
107
- if (success) {
108
- toast.success('Profile updated');
109
- }
110
- };
111
-
112
- const handleAvatarUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
113
- const file = e.target.files?.[0];
114
- if (!file || !state.user) return;
115
-
116
- setIsUploading(true);
117
- try {
118
- const fileExt = file.name.split('.').pop();
119
- const fileName = `${state.user.id}/avatar.${fileExt}`;
120
-
121
- // Upload to Supabase Storage
122
- const { data, error: uploadError } = await supabase.storage
123
- .from('avatars')
124
- .upload(fileName, file, { upsert: true });
125
-
126
- if (uploadError) throw uploadError;
127
-
128
- // Get public URL
129
- const { data: { publicUrl } } = supabase.storage
130
- .from('avatars')
131
- .getPublicUrl(fileName);
132
-
133
- // Update profile
134
- await actions.updateProfile({ avatar_url: publicUrl });
135
- toast.success('Avatar updated');
136
- } catch (error) {
137
- console.error('Avatar upload error:', error);
138
- toast.error('Failed to upload avatar');
139
- } finally {
140
- setIsUploading(false);
141
- }
142
- };
143
-
144
- return (
145
- <Card>
146
- <CardHeader>
147
- <CardTitle>Profile Information</CardTitle>
148
- <CardDescription>Update your personal details and avatar.</CardDescription>
149
- </CardHeader>
150
- <CardContent className="space-y-6">
151
- <div className="flex flex-col items-center sm:flex-row sm:items-start gap-6">
152
- <div className="relative group">
153
- <div className="w-24 h-24 rounded-full bg-secondary overflow-hidden border-2 border-border flex items-center justify-center">
154
- {state.profile?.avatar_url ? (
155
- <img src={state.profile.avatar_url} alt="Avatar" className="w-full h-full object-cover" />
156
- ) : (
157
- <User className="w-12 h-12 text-muted-foreground" />
158
- )}
159
- </div>
160
- <label className="absolute inset-0 flex items-center justify-center bg-black/40 rounded-full opacity-0 group-hover:opacity-100 cursor-pointer transition-opacity">
161
- <Camera className="w-6 h-6 text-white" />
162
- <input type="file" className="hidden" accept="image/*" onChange={handleAvatarUpload} disabled={isUploading} />
163
- </label>
164
- {isUploading && (
165
- <div className="absolute inset-0 flex items-center justify-center bg-background/60 rounded-full">
166
- <LoadingSpinner size="sm" />
167
- </div>
168
- )}
169
- </div>
170
-
171
- <div className="flex-1 space-y-4 w-full">
172
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
173
- <div className="space-y-2">
174
- <Label htmlFor="first-name">First Name</Label>
175
- <Input
176
- id="first-name"
177
- value={firstName}
178
- onChange={(e) => setFirstName(e.target.value)}
179
- placeholder="John"
180
- />
181
- </div>
182
- <div className="space-y-2">
183
- <Label htmlFor="last-name">Last Name</Label>
184
- <Input
185
- id="last-name"
186
- value={lastName}
187
- onChange={(e) => setLastName(e.target.value)}
188
- placeholder="Doe"
189
- />
190
- </div>
191
- </div>
192
- <div className="space-y-2">
193
- <Label htmlFor="email">Email Address</Label>
194
- <Input
195
- id="email"
196
- value={state.profile?.email || ''}
197
- disabled
198
- className="bg-secondary/50"
199
- />
200
- <p className="text-[10px] text-muted-foreground">Email cannot be changed directly.</p>
201
- </div>
202
- </div>
203
- </div>
204
-
205
- <div className="pt-6 border-t flex items-center justify-between">
206
- <div className="space-y-0.5">
207
- <label className="text-sm font-medium">Sound & Haptics</label>
208
- <p className="text-xs text-muted-foreground">Audio feedback when processing emails and completing tasks.</p>
209
- </div>
210
- <Button
211
- variant={soundsEnabled ? 'default' : 'outline'}
212
- size="sm"
213
- onClick={toggleSounds}
214
- className="gap-2"
215
- >
216
- {soundsEnabled ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
217
- {soundsEnabled ? 'Enabled' : 'Disabled'}
218
- </Button>
219
- </div>
220
-
221
- <div className="flex justify-end pt-4 border-t">
222
- <Button onClick={handleSave} disabled={isSaving}>
223
- {isSaving ? <LoadingSpinner size="sm" className="mr-2" /> : <Save className="w-4 h-4 mr-2" />}
224
- Save Changes
225
- </Button>
226
- </div>
227
- </CardContent>
228
- </Card>
229
- );
230
- }
231
-
232
- function SecuritySection() {
233
- const [password, setPassword] = useState('');
234
- const [confirmPassword, setConfirmPassword] = useState('');
235
- const [isSaving, setIsSaving] = useState(false);
236
-
237
- const handlePasswordChange = async () => {
238
- if (!password) {
239
- toast.error('Please enter a new password');
240
- return;
241
- }
242
- if (password !== confirmPassword) {
243
- toast.error('Passwords do not match');
244
- return;
245
- }
246
- if (password.length < 6) {
247
- toast.error('Password must be at least 6 characters');
248
- return;
249
- }
250
-
251
- setIsSaving(true);
252
- try {
253
- const { api } = await import('../lib/api');
254
- const result = await api.changePassword(password);
255
- if (result.success) {
256
- toast.success('Password changed successfully');
257
- setPassword('');
258
- setConfirmPassword('');
259
- } else {
260
- toast.error(result.error?.message || 'Failed to change password');
261
- }
262
- } catch (error) {
263
- toast.error('An error occurred');
264
- } finally {
265
- setIsSaving(false);
266
- }
267
- };
268
-
269
- return (
270
- <Card>
271
- <CardHeader>
272
- <CardTitle>Security</CardTitle>
273
- <CardDescription>Manage your password and account security.</CardDescription>
274
- </CardHeader>
275
- <CardContent className="space-y-4">
276
- <div className="space-y-2">
277
- <Label htmlFor="new-password">New Password</Label>
278
- <Input
279
- id="new-password"
280
- type="password"
281
- value={password}
282
- onChange={(e) => setPassword(e.target.value)}
283
- placeholder="••••••••"
284
- />
285
- </div>
286
- <div className="space-y-2">
287
- <Label htmlFor="confirm-password">Confirm New Password</Label>
288
- <Input
289
- id="confirm-password"
290
- type="password"
291
- value={confirmPassword}
292
- onChange={(e) => setConfirmPassword(e.target.value)}
293
- placeholder="••••••••"
294
- />
295
- </div>
296
-
297
- <div className="flex justify-end pt-4 border-t">
298
- <Button onClick={handlePasswordChange} disabled={isSaving}>
299
- {isSaving ? <LoadingSpinner size="sm" className="mr-2" /> : <Key className="w-4 h-4 mr-2" />}
300
- Update Password
301
- </Button>
302
- </div>
303
- </CardContent>
304
- </Card>
305
- );
306
- }
307
-
308
- function DatabaseSection() {
309
- const [showWizard, setShowWizard] = useState(false);
310
- const config = getSupabaseConfig();
311
- const source = getConfigSource();
312
-
313
- const handleClearConfig = () => {
314
- if (confirm('Are you sure you want to disconnect from this Supabase project? This will log you out and clear local configuration.')) {
315
- clearSupabaseConfig();
316
- window.location.reload();
317
- }
318
- };
319
-
320
- return (
321
- <>
322
- <Card>
323
- <CardHeader>
324
- <CardTitle className="flex items-center gap-2">
325
- <Database className="w-5 h-5 text-primary" />
326
- Supabase Connection
327
- </CardTitle>
328
- <CardDescription>Manage your database configuration (BYOK - Bring Your Own Keys).</CardDescription>
329
- </CardHeader>
330
- <CardContent className="space-y-6">
331
- {config ? (
332
- <>
333
- {/* Status Card */}
334
- <div className="flex items-start gap-4 p-4 border rounded-xl bg-emerald-500/5 border-emerald-500/20">
335
- <CheckCircle className="w-6 h-6 text-emerald-500 mt-0.5" />
336
- <div className="flex-1 space-y-1">
337
- <p className="font-semibold text-emerald-700 dark:text-emerald-400">Connected</p>
338
- <p className="text-sm font-mono break-all opacity-80">{config.url}</p>
339
- </div>
340
- </div>
341
-
342
- {source === 'env' && (
343
- <Alert className="bg-amber-500/5 border-amber-500/20">
344
- <Settings className="h-4 w-4 text-amber-500" />
345
- <AlertDescription className="text-amber-700 dark:text-amber-400 text-xs">
346
- Configuration is loaded from environment variables. Use the UI to override them.
347
- </AlertDescription>
348
- </Alert>
349
- )}
350
-
351
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
352
- <Button variant="outline" onClick={() => setShowWizard(true)} className="w-full">
353
- <Settings className="w-4 h-4 mr-2" />
354
- Change Connection
355
- </Button>
356
- {source === 'ui' && (
357
- <Button variant="outline" onClick={handleClearConfig} className="w-full text-destructive hover:bg-destructive/10">
358
- <Trash2 className="w-4 h-4 mr-2" />
359
- Clear Config
360
- </Button>
361
- )}
362
- </div>
363
-
364
- <div className="space-y-2 pt-4 border-t">
365
- <Label className="text-xs uppercase tracking-wider text-muted-foreground">Anon Public Key</Label>
366
- <div className="p-2 bg-secondary/50 rounded-lg font-mono text-xs break-all">
367
- {config.anonKey.substring(0, 20)}...{config.anonKey.substring(config.anonKey.length - 10)}
368
- </div>
369
- </div>
370
- </>
371
- ) : (
372
- <div className="flex flex-col items-center justify-center py-8 text-center space-y-4">
373
- <XCircle className="w-12 h-12 text-destructive opacity-20" />
374
- <div>
375
- <p className="font-medium">No Connection Detected</p>
376
- <p className="text-sm text-muted-foreground">Configure a Supabase project to get started.</p>
377
- </div>
378
- <Button onClick={() => setShowWizard(true)}>
379
- <Database className="w-4 h-4 mr-2" />
380
- Setup Supabase
381
- </Button>
382
- </div>
383
- )}
384
- </CardContent>
385
- </Card>
386
-
387
- <SetupWizard open={showWizard} onComplete={() => setShowWizard(false)} canClose={true} />
388
- </>
389
- );
390
- }