@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,371 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { Mail, Loader2, LogIn, UserPlus, KeyRound, ArrowLeft } from 'lucide-react';
3
- import { supabase } from '../lib/supabase';
4
- import { Button } from './ui/button';
5
- import { Input } from './ui/input';
6
- import { OtpInput } from './ui/otp-input';
7
- import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './ui/card';
8
- import { ModeToggle } from './mode-toggle';
9
- import { toast } from './Toast';
10
- import { Logo } from './Logo';
11
-
12
- interface LoginProps {
13
- onSuccess?: () => void;
14
- onConfigure?: () => void;
15
- }
16
-
17
- export function Login({ onSuccess, onConfigure }: LoginProps) {
18
- const [email, setEmail] = useState('');
19
- const [password, setPassword] = useState('');
20
- const [firstName, setFirstName] = useState('');
21
- const [lastName, setLastName] = useState('');
22
-
23
- // UI State
24
- const [isLoading, setIsLoading] = useState(false);
25
- const [isCheckingInit, setIsCheckingInit] = useState(true);
26
- const [isInitialized, setIsInitialized] = useState(false);
27
- const [error, setError] = useState('');
28
-
29
- // Login Mode
30
- const [loginMode, setLoginMode] = useState<'password' | 'otp'>('password');
31
- const [otpStep, setOtpStep] = useState<'email' | 'verify'>('email');
32
- const [otp, setOtp] = useState('');
33
-
34
-
35
- // Check initialization status on mount
36
- useEffect(() => {
37
- checkInitialization();
38
- }, []);
39
-
40
- const checkInitialization = async () => {
41
- try {
42
- const { data, error } = await supabase.from('init_state').select('is_initialized');
43
- if (error) {
44
- // atomic-crm behavior: If check fails, assume initialized (Show Login)
45
- // This covers cases where publishable keys block REST access but Auth works.
46
- console.warn('[Login] Init check failed, defaulting to initialized (Login):', error);
47
- // We do NOT show a blocking error, just log it.
48
- // This allows the user to attempt login.
49
- setIsInitialized(true);
50
- return;
51
- }
52
-
53
- // The view returns { is_initialized: 1 } if initialized, or 0/null if not
54
- const initialized = data?.[0]?.is_initialized > 0;
55
- setIsInitialized(initialized);
56
- } catch (err: any) {
57
- console.warn('[Login] Init check exception, defaulting to initialized:', err);
58
-
59
- // On any exception (network, auth, etc), we default to showing the Login screen.
60
- // This prevents getting stuck in Signup mode if the DB is actually initialized but unreachable.
61
- // The "Update Connection" button is still available if they need to change config.
62
-
63
- // Clear blocking error to allow UI to render Login form
64
- if (err.message?.includes('Invalid API key') || err.status === 401) {
65
- console.warn('[Login] API Key invalid for REST check (likely Publishable Key). defaulting to Setup Mode (isInitialized=false).');
66
- setIsInitialized(false);
67
- return;
68
- }
69
- setIsInitialized(true);
70
- } finally {
71
- setIsCheckingInit(false);
72
- }
73
- };
74
-
75
- const handleSubmit = async (e: React.FormEvent) => {
76
- e.preventDefault();
77
- setIsLoading(true);
78
- setError('');
79
-
80
- try {
81
- if (!isInitialized) {
82
- // Admin Signup Flow
83
- const { data, error } = await supabase.functions.invoke('setup', {
84
- body: {
85
- email,
86
- password,
87
- first_name: firstName,
88
- last_name: lastName
89
- }
90
- });
91
-
92
- if (error || !data) {
93
- if (error?.message?.includes('First user already exists')) {
94
- toast.info('System already initialized. Please log in.');
95
- setIsInitialized(true);
96
- return;
97
- }
98
- throw new Error(error?.message || 'Failed to create admin account');
99
- }
100
-
101
- toast.success('Admin account created! Signing you in...');
102
-
103
- // Auto login after creation
104
- const { error: signInError } = await supabase.auth.signInWithPassword({
105
- email,
106
- password,
107
- });
108
-
109
- if (signInError) throw signInError;
110
-
111
- // Force re-check of initialization status
112
- setIsInitialized(true);
113
- onSuccess?.();
114
- } else if (loginMode === 'password') {
115
- // Regular Login Flow
116
- const { error } = await supabase.auth.signInWithPassword({
117
- email,
118
- password,
119
- });
120
- if (error) throw error;
121
- toast.success('Logged in successfully');
122
- onSuccess?.();
123
- } else {
124
- // OTP Flow - Step 1: Send Code
125
- if (otpStep === 'email') {
126
- const { error } = await supabase.auth.signInWithOtp({
127
- email,
128
- options: { shouldCreateUser: false } // Only allow existing users to login this way
129
- });
130
- if (error) throw error;
131
- setOtpStep('verify');
132
- toast.success('Validation code sent to your email');
133
- }
134
- }
135
- } catch (err: any) {
136
- // Show full error message to user (e.g. "Invalid login credentials")
137
- setError(err?.message || 'Authentication failed');
138
- console.error('[Login] Error:', err);
139
- } finally {
140
- setIsLoading(false);
141
- }
142
- };
143
-
144
- const handleVerifyOtp = async () => {
145
- setIsLoading(true);
146
- setError('');
147
- try {
148
- const { data, error } = await supabase.auth.verifyOtp({
149
- email,
150
- token: otp,
151
- type: 'magiclink'
152
- });
153
- if (error) throw error;
154
- if (!data.session) throw new Error('Failed to create session');
155
-
156
- toast.success('Logged in successfully');
157
- onSuccess?.();
158
- } catch (err: any) {
159
- setError(err?.message || 'Invalid code');
160
- } finally {
161
- setIsLoading(false);
162
- }
163
- };
164
-
165
- if (isCheckingInit) {
166
- return (
167
- <div className="min-h-screen bg-background flex items-center justify-center">
168
- <Loader2 className="w-8 h-8 animate-spin text-primary" />
169
- </div>
170
- );
171
- }
172
-
173
- return (
174
- <div className="min-h-screen bg-background flex items-center justify-center p-8 relative">
175
- <div className="absolute top-4 right-4">
176
- <ModeToggle />
177
- </div>
178
- <Card className="w-full max-w-md shadow-2xl">
179
- <CardHeader className="text-center">
180
- <div className="mx-auto bg-primary/10 p-4 rounded-full w-fit mb-4">
181
- <Logo className="w-12 h-12" />
182
- </div>
183
- <CardTitle className="text-2xl">
184
- {!isInitialized ? 'Welcome to Email Automator' : 'Welcome Back'}
185
- </CardTitle>
186
- <CardDescription>
187
- {!isInitialized
188
- ? 'Create the first admin account to get started'
189
- : (loginMode === 'password'
190
- ? 'Sign in to access your email automation'
191
- : (otpStep === 'email' ? 'Receive a login code via email' : `Enter code sent to ${email}`)
192
- )
193
- }
194
- </CardDescription>
195
- </CardHeader>
196
- <form onSubmit={handleSubmit}>
197
- <CardContent className="space-y-4">
198
- {!isInitialized && (
199
- <div className="grid grid-cols-2 gap-4">
200
- <div className="space-y-2">
201
- <label className="text-sm font-medium">First Name</label>
202
- <Input
203
- value={firstName}
204
- onChange={(e) => setFirstName(e.target.value)}
205
- required={!isInitialized}
206
- placeholder="John"
207
- />
208
- </div>
209
- <div className="space-y-2">
210
- <label className="text-sm font-medium">Last Name</label>
211
- <Input
212
- value={lastName}
213
- onChange={(e) => setLastName(e.target.value)}
214
- required={!isInitialized}
215
- placeholder="Doe"
216
- />
217
- </div>
218
- </div>
219
- )}
220
-
221
- {loginMode === 'password' || otpStep === 'email' ? (
222
- <div className="space-y-2">
223
- <label className="text-sm font-medium">Email</label>
224
- <Input
225
- type="email"
226
- placeholder="admin@example.com"
227
- value={email}
228
- onChange={(e) => setEmail(e.target.value)}
229
- required
230
- autoComplete="email"
231
- disabled={otpStep === 'verify'}
232
- />
233
- </div>
234
- ) : null}
235
-
236
- {loginMode === 'password' && (
237
- <div className="space-y-2">
238
- <label className="text-sm font-medium">Password</label>
239
- <Input
240
- type="password"
241
- placeholder="••••••••"
242
- value={password}
243
- onChange={(e) => setPassword(e.target.value)}
244
- required
245
- minLength={6}
246
- autoComplete={!isInitialized ? 'new-password' : 'current-password'}
247
- />
248
- </div>
249
- )}
250
-
251
- {loginMode === 'otp' && otpStep === 'verify' && (
252
- <div className="space-y-4 py-2">
253
- <div className="flex justify-center">
254
- <OtpInput
255
- value={otp}
256
- onChange={setOtp}
257
- length={6}
258
- onComplete={() => { }}
259
- />
260
- </div>
261
- <Button
262
- type="button"
263
- variant="link"
264
- className="w-full text-xs text-muted-foreground"
265
- onClick={() => setOtpStep('email')}
266
- >
267
- Change email address
268
- </Button>
269
- </div>
270
- )}
271
-
272
- <div className="bg-destructive/10 text-destructive text-sm p-3 rounded-md flex flex-col gap-2">
273
- <p>{error}</p>
274
- {error.includes('configuration') || error.includes('Invalid API key') || error.includes('API key') && onConfigure && (
275
- <Button
276
- type="button"
277
- variant="outline"
278
- className="w-full border-destructive/50 hover:bg-destructive/20 text-destructive"
279
- onClick={onConfigure}
280
- >
281
- Update Connection Settings
282
- </Button>
283
- )}
284
- </div>
285
- </CardContent>
286
-
287
- <CardFooter className="flex flex-col gap-3">
288
- {loginMode === 'otp' && otpStep === 'verify' ? (
289
- <Button
290
- type="button"
291
- onClick={handleVerifyOtp}
292
- disabled={isLoading || otp.length !== 6}
293
- className="w-full"
294
- >
295
- {isLoading ? (
296
- <>
297
- <Loader2 className="w-4 h-4 mr-2 animate-spin" />
298
- Verifying...
299
- </>
300
- ) : (
301
- 'Verify Code'
302
- )}
303
- </Button>
304
- ) : (
305
- <Button
306
- type="submit"
307
- disabled={isLoading || !email || (loginMode === 'password' && !password)}
308
- className="w-full"
309
- >
310
- {isLoading ? (
311
- <>
312
- <Loader2 className="w-4 h-4 mr-2 animate-spin" />
313
- {!isInitialized ? 'Creating Account...' : (loginMode === 'otp' ? 'Send Code' : 'Signing in...')}
314
- </>
315
- ) : (
316
- <>
317
- {!isInitialized ? (
318
- <UserPlus className="w-4 h-4 mr-2" />
319
- ) : (
320
- loginMode === 'otp' ? <Mail className="w-4 h-4 mr-2" /> : <LogIn className="w-4 h-4 mr-2" />
321
- )}
322
- {!isInitialized ? 'Create Admin Account' : (loginMode === 'otp' ? 'Send Login Code' : 'Sign In')}
323
- </>
324
- )}
325
- </Button>
326
- )}
327
-
328
- {isInitialized && (
329
- <div className="flex flex-col gap-2 w-full">
330
- {loginMode === 'password' ? (
331
- <Button
332
- type="button"
333
- variant="ghost"
334
- className="w-full text-sm font-normal text-muted-foreground hover:text-primary"
335
- onClick={() => {
336
- setLoginMode('otp');
337
- setOtpStep('email');
338
- setError('');
339
- }}
340
- >
341
- <KeyRound className="w-4 h-4 mr-2" />
342
- Sign in with Code
343
- </Button>
344
- ) : (
345
- <Button
346
- type="button"
347
- variant="ghost"
348
- className="w-full text-sm font-normal text-muted-foreground hover:text-primary"
349
- onClick={() => {
350
- setLoginMode('password');
351
- setError('');
352
- }}
353
- >
354
- <ArrowLeft className="w-4 h-4 mr-2" />
355
- Sign in with Password
356
- </Button>
357
- )}
358
- </div>
359
- )}
360
-
361
- {isInitialized && (
362
- <p className="text-xs text-center text-muted-foreground mt-2">
363
- New users must be invited by an admin.
364
- </p>
365
- )}
366
- </CardFooter>
367
- </form>
368
- </Card>
369
- </div>
370
- );
371
- }
@@ -1,57 +0,0 @@
1
- import { cn } from '../lib/utils';
2
-
3
- interface LogoProps {
4
- className?: string;
5
- }
6
-
7
- export function Logo({ className }: LogoProps) {
8
- return (
9
- <svg
10
- width="512"
11
- height="512"
12
- viewBox="0 0 512 512"
13
- fill="none"
14
- xmlns="http://www.w3.org/2000/svg"
15
- className={cn("w-9 h-9", className)}
16
- >
17
- {/* Main Envelope Shape */}
18
- <path
19
- d="M112 112H400C426.51 112 448 133.49 448 160V352C448 378.51 426.51 400 400 400H112C85.4903 400 64 378.51 64 352V160C64 133.49 85.4903 112 112 112Z"
20
- className="stroke-foreground"
21
- strokeWidth="32"
22
- strokeLinecap="round"
23
- strokeLinejoin="round"
24
- />
25
-
26
- {/* The Flap (Open) */}
27
- <path
28
- d="M64 160 L200 270"
29
- className="stroke-foreground"
30
- strokeWidth="32"
31
- strokeLinecap="round"
32
- strokeLinejoin="round"
33
- />
34
- <path
35
- d="M448 160 L312 270"
36
- className="stroke-foreground"
37
- strokeWidth="32"
38
- strokeLinecap="round"
39
- strokeLinejoin="round"
40
- />
41
-
42
- {/* The AI Spark (Purple) */}
43
- <path
44
- d="M256 128 C256 128 276 170 306 178 C276 186 256 228 256 228 C256 228 236 186 206 178 C236 170 256 128 256 128 Z"
45
- fill="#9333ea"
46
- >
47
- <animateTransform
48
- attributeName="transform"
49
- type="translate"
50
- values="0 0; 0 -10; 0 0"
51
- dur="3s"
52
- repeatCount="indefinite"
53
- />
54
- </path>
55
- </svg>
56
- );
57
- }