@realtimex/email-automator 2.2.0 → 2.3.0

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 (74) hide show
  1. package/api/server.ts +4 -8
  2. package/api/src/config/index.ts +6 -3
  3. package/bin/email-automator-setup.js +2 -3
  4. package/bin/email-automator.js +7 -11
  5. package/dist/api/server.js +109 -0
  6. package/dist/api/src/config/index.js +88 -0
  7. package/dist/api/src/middleware/auth.js +119 -0
  8. package/dist/api/src/middleware/errorHandler.js +78 -0
  9. package/dist/api/src/middleware/index.js +4 -0
  10. package/dist/api/src/middleware/rateLimit.js +57 -0
  11. package/dist/api/src/middleware/validation.js +111 -0
  12. package/dist/api/src/routes/actions.js +173 -0
  13. package/dist/api/src/routes/auth.js +106 -0
  14. package/dist/api/src/routes/emails.js +100 -0
  15. package/dist/api/src/routes/health.js +33 -0
  16. package/dist/api/src/routes/index.js +19 -0
  17. package/dist/api/src/routes/migrate.js +61 -0
  18. package/dist/api/src/routes/rules.js +104 -0
  19. package/dist/api/src/routes/settings.js +178 -0
  20. package/dist/api/src/routes/sync.js +118 -0
  21. package/dist/api/src/services/eventLogger.js +41 -0
  22. package/dist/api/src/services/gmail.js +350 -0
  23. package/dist/api/src/services/intelligence.js +243 -0
  24. package/dist/api/src/services/microsoft.js +256 -0
  25. package/dist/api/src/services/processor.js +503 -0
  26. package/dist/api/src/services/scheduler.js +210 -0
  27. package/dist/api/src/services/supabase.js +59 -0
  28. package/dist/api/src/utils/contentCleaner.js +94 -0
  29. package/dist/api/src/utils/crypto.js +68 -0
  30. package/dist/api/src/utils/logger.js +119 -0
  31. package/package.json +5 -5
  32. package/src/App.tsx +0 -622
  33. package/src/components/AccountSettings.tsx +0 -310
  34. package/src/components/AccountSettingsPage.tsx +0 -390
  35. package/src/components/Configuration.tsx +0 -1345
  36. package/src/components/Dashboard.tsx +0 -940
  37. package/src/components/ErrorBoundary.tsx +0 -71
  38. package/src/components/LiveTerminal.tsx +0 -308
  39. package/src/components/LoadingSpinner.tsx +0 -39
  40. package/src/components/Login.tsx +0 -371
  41. package/src/components/Logo.tsx +0 -57
  42. package/src/components/SetupWizard.tsx +0 -388
  43. package/src/components/Toast.tsx +0 -109
  44. package/src/components/migration/MigrationBanner.tsx +0 -97
  45. package/src/components/migration/MigrationModal.tsx +0 -458
  46. package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
  47. package/src/components/mode-toggle.tsx +0 -24
  48. package/src/components/theme-provider.tsx +0 -72
  49. package/src/components/ui/alert.tsx +0 -66
  50. package/src/components/ui/button.tsx +0 -57
  51. package/src/components/ui/card.tsx +0 -75
  52. package/src/components/ui/dialog.tsx +0 -133
  53. package/src/components/ui/input.tsx +0 -22
  54. package/src/components/ui/label.tsx +0 -24
  55. package/src/components/ui/otp-input.tsx +0 -184
  56. package/src/context/AppContext.tsx +0 -422
  57. package/src/context/MigrationContext.tsx +0 -53
  58. package/src/context/TerminalContext.tsx +0 -31
  59. package/src/core/actions.ts +0 -76
  60. package/src/core/auth.ts +0 -108
  61. package/src/core/intelligence.ts +0 -76
  62. package/src/core/processor.ts +0 -112
  63. package/src/hooks/useRealtimeEmails.ts +0 -111
  64. package/src/index.css +0 -140
  65. package/src/lib/api-config.ts +0 -42
  66. package/src/lib/api-old.ts +0 -228
  67. package/src/lib/api.ts +0 -421
  68. package/src/lib/migration-check.ts +0 -264
  69. package/src/lib/sounds.ts +0 -120
  70. package/src/lib/supabase-config.ts +0 -117
  71. package/src/lib/supabase.ts +0 -28
  72. package/src/lib/types.ts +0 -166
  73. package/src/lib/utils.ts +0 -6
  74. package/src/main.tsx +0 -10
@@ -1,228 +0,0 @@
1
- import { getSupabaseConfig } from './supabase-config';
2
-
3
- const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3002';
4
-
5
- interface ApiOptions extends RequestInit {
6
- auth?: boolean;
7
- }
8
-
9
- interface ApiResponse<T> {
10
- data?: T;
11
- error?: { code: string; message: string };
12
- }
13
-
14
- class ApiClient {
15
- private baseUrl: string;
16
- private token: string | null = null;
17
-
18
- constructor(baseUrl: string) {
19
- this.baseUrl = baseUrl;
20
- }
21
-
22
- setToken(token: string | null) {
23
- this.token = token;
24
- }
25
-
26
- private async request<T>(
27
- endpoint: string,
28
- options: ApiOptions = {}
29
- ): Promise<ApiResponse<T>> {
30
- const { auth = true, ...fetchOptions } = options;
31
-
32
- const headers: HeadersInit = {
33
- 'Content-Type': 'application/json',
34
- ...(options.headers || {}),
35
- };
36
-
37
- if (auth && this.token) {
38
- (headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
39
- }
40
-
41
- try {
42
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
43
- ...fetchOptions,
44
- headers,
45
- });
46
-
47
- if (!response.ok) {
48
- const errorData = await response.json().catch(() => ({}));
49
- return {
50
- error: {
51
- code: errorData.error?.code || 'API_ERROR',
52
- message: errorData.error?.message || `Request failed: ${response.status}`,
53
- },
54
- };
55
- }
56
-
57
- const data = await response.json();
58
- return { data };
59
- } catch (error) {
60
- return {
61
- error: {
62
- code: 'NETWORK_ERROR',
63
- message: error instanceof Error ? error.message : 'Network error',
64
- },
65
- };
66
- }
67
- }
68
-
69
- // Auth endpoints
70
- async getGmailAuthUrl() {
71
- return this.request<{ url: string }>('/api/auth/gmail/url', { method: 'GET' });
72
- }
73
-
74
- async connectGmail(code: string) {
75
- return this.request<{ success: boolean; account: any }>('/api/auth/gmail/callback', {
76
- method: 'POST',
77
- body: JSON.stringify({ code }),
78
- });
79
- }
80
-
81
- async startMicrosoftDeviceFlow() {
82
- return this.request<{ userCode: string; verificationUri: string; message: string }>(
83
- '/api/auth/microsoft/device-flow',
84
- { method: 'POST' }
85
- );
86
- }
87
-
88
- async getAccounts() {
89
- return this.request<{ accounts: any[] }>('/api/auth/accounts');
90
- }
91
-
92
- async disconnectAccount(accountId: string) {
93
- return this.request<{ success: boolean }>(`/api/auth/accounts/${accountId}`, {
94
- method: 'DELETE',
95
- });
96
- }
97
-
98
- // Sync endpoints
99
- async triggerSync(accountId: string) {
100
- return this.request<{ message: string }>('/api/sync', {
101
- method: 'POST',
102
- body: JSON.stringify({ accountId }),
103
- });
104
- }
105
-
106
- async syncAll() {
107
- return this.request<{ message: string; accountCount: number }>('/api/sync/all', {
108
- method: 'POST',
109
- });
110
- }
111
-
112
- async getSyncLogs(limit = 10) {
113
- return this.request<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
114
- }
115
-
116
- // Email endpoints
117
- async getEmails(params: {
118
- limit?: number;
119
- offset?: number;
120
- category?: string;
121
- search?: string;
122
- } = {}) {
123
- const query = new URLSearchParams();
124
- if (params.limit) query.set('limit', params.limit.toString());
125
- if (params.offset) query.set('offset', params.offset.toString());
126
- if (params.category) query.set('category', params.category);
127
- if (params.search) query.set('search', params.search);
128
-
129
- return this.request<{ emails: any[]; total: number }>(`/api/emails?${query}`);
130
- }
131
-
132
- async getEmail(emailId: string) {
133
- return this.request<{ email: any }>(`/api/emails/${emailId}`);
134
- }
135
-
136
- async getCategorySummary() {
137
- return this.request<{ categories: Record<string, number> }>('/api/emails/summary/categories');
138
- }
139
-
140
- // Action endpoints
141
- async executeAction(emailId: string, action: string, draftContent?: string) {
142
- return this.request<{ success: boolean; details?: string }>('/api/actions/execute', {
143
- method: 'POST',
144
- body: JSON.stringify({ emailId, action, draftContent }),
145
- });
146
- }
147
-
148
- async generateDraft(emailId: string, instructions?: string) {
149
- return this.request<{ draft: string }>(`/api/actions/draft/${emailId}`, {
150
- method: 'POST',
151
- body: JSON.stringify({ instructions }),
152
- });
153
- }
154
-
155
- async bulkAction(emailIds: string[], action: string) {
156
- return this.request<{ success: number; failed: number }>('/api/actions/bulk', {
157
- method: 'POST',
158
- body: JSON.stringify({ emailIds, action }),
159
- });
160
- }
161
-
162
- // Rules endpoints
163
- async getRules() {
164
- return this.request<{ rules: any[] }>('/api/rules');
165
- }
166
-
167
- async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean }) {
168
- return this.request<{ rule: any }>('/api/rules', {
169
- method: 'POST',
170
- body: JSON.stringify(rule),
171
- });
172
- }
173
-
174
- async updateRule(ruleId: string, updates: any) {
175
- return this.request<{ rule: any }>(`/api/rules/${ruleId}`, {
176
- method: 'PATCH',
177
- body: JSON.stringify(updates),
178
- });
179
- }
180
-
181
- async deleteRule(ruleId: string) {
182
- return this.request<{ success: boolean }>(`/api/rules/${ruleId}`, {
183
- method: 'DELETE',
184
- });
185
- }
186
-
187
- async toggleRule(ruleId: string) {
188
- return this.request<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
189
- method: 'POST',
190
- });
191
- }
192
-
193
- // Settings endpoints
194
- async getSettings() {
195
- return this.request<{ settings: any }>('/api/settings');
196
- }
197
-
198
- async updateSettings(settings: any) {
199
- return this.request<{ settings: any }>('/api/settings', {
200
- method: 'PATCH',
201
- body: JSON.stringify(settings),
202
- });
203
- }
204
-
205
- async getStats() {
206
- return this.request<{ stats: any }>('/api/settings/stats');
207
- }
208
-
209
- // Health check
210
- async healthCheck() {
211
- return this.request<{ status: string; services: any }>('/api/health', { auth: false });
212
- }
213
- }
214
-
215
- export const api = new ApiClient(API_BASE_URL);
216
-
217
- // Helper to initialize API with auth token from Supabase session
218
- export async function initializeApi(supabase: any) {
219
- const { data: { session } } = await supabase.auth.getSession();
220
- if (session?.access_token) {
221
- api.setToken(session.access_token);
222
- }
223
-
224
- // Listen for auth changes
225
- supabase.auth.onAuthStateChange((_event: string, session: any) => {
226
- api.setToken(session?.access_token || null);
227
- });
228
- }
package/src/lib/api.ts DELETED
@@ -1,421 +0,0 @@
1
- /**
2
- * Hybrid API Client for Email Automator
3
- *
4
- * Architecture:
5
- * - Edge Functions: Auth, OAuth, Database operations (via Supabase)
6
- * - Express API (Local App): Email sync, AI processing
7
- */
8
-
9
- import { getApiConfig } from './api-config';
10
- import { EmailAccount } from './types';
11
-
12
- interface ApiOptions extends RequestInit {
13
- auth?: boolean;
14
- }
15
-
16
- interface ApiResponse<T> {
17
- data?: T;
18
- error?: { code?: string; message: string } | string;
19
- }
20
-
21
- class HybridApiClient {
22
- private edgeFunctionsUrl: string;
23
- private expressApiUrl: string;
24
- private anonKey: string;
25
- private token: string | null = null;
26
- private supabaseClient: any = null;
27
-
28
- constructor() {
29
- const config = getApiConfig();
30
- this.edgeFunctionsUrl = config.edgeFunctionsUrl;
31
- this.expressApiUrl = config.expressApiUrl;
32
- this.anonKey = config.anonKey;
33
- }
34
-
35
- setSupabaseClient(client: any) {
36
- this.supabaseClient = client;
37
- }
38
-
39
- setToken(token: string | null) {
40
- if (token) {
41
- console.debug('[HybridApiClient] Token updated');
42
- } else {
43
- console.debug('[HybridApiClient] Token cleared');
44
- }
45
- this.token = token;
46
- }
47
-
48
- private async request<T>(
49
- baseUrl: string,
50
- endpoint: string,
51
- options: ApiOptions = {}
52
- ): Promise<ApiResponse<T>> {
53
- const { auth = true, ...fetchOptions } = options;
54
-
55
- const headers: HeadersInit = {
56
- 'Content-Type': 'application/json',
57
- ...(options.headers || {}),
58
- };
59
-
60
- // Fallback: try to get token from supabaseClient if missing
61
- if (auth && !this.token && this.supabaseClient) {
62
- const { data: { session } } = await this.supabaseClient.auth.getSession();
63
- if (session?.access_token) {
64
- this.token = session.access_token;
65
- console.debug('[HybridApiClient] Recovered token from session');
66
- }
67
- }
68
-
69
- if (auth && !this.token) {
70
- console.warn(`[HybridApiClient] Attempted protected request to ${endpoint} without token`);
71
- return {
72
- error: {
73
- code: 'AUTH_REQUIRED',
74
- message: 'You must be logged in to perform this action',
75
- },
76
- };
77
- }
78
-
79
- if (auth && this.token) {
80
- (headers as Record<string, string>)['Authorization'] = `Bearer ${this.token}`;
81
- }
82
-
83
- try {
84
- console.debug(`[HybridApiClient] ${options.method || 'GET'} ${baseUrl}${endpoint}`);
85
- const response = await fetch(`${baseUrl}${endpoint}`, {
86
- ...fetchOptions,
87
- headers,
88
- });
89
-
90
- if (!response.ok) {
91
- const errorData = await response.json().catch(() => ({}));
92
- const errorMessage = typeof errorData.error === 'string'
93
- ? errorData.error
94
- : errorData.error?.message || `Request failed: ${response.status}`;
95
-
96
- return {
97
- error: {
98
- code: errorData.error?.code || 'API_ERROR',
99
- message: errorMessage,
100
- },
101
- };
102
- }
103
-
104
- const data = await response.json();
105
- return { data };
106
- } catch (error) {
107
- return {
108
- error: {
109
- code: 'NETWORK_ERROR',
110
- message: error instanceof Error ? error.message : 'Network error',
111
- },
112
- };
113
- }
114
- }
115
-
116
- // Edge Functions requests
117
- private edgeRequest<T>(endpoint: string, options?: ApiOptions) {
118
- // Supabase Gateway requires 'apikey' header for all requests
119
- const headers = {
120
- ...(options?.headers || {}),
121
- apikey: this.anonKey,
122
- };
123
-
124
- return this.request<T>(this.edgeFunctionsUrl, endpoint, {
125
- ...options,
126
- headers,
127
- });
128
- }
129
-
130
- // Express API requests - include Supabase config for backend to use
131
- private expressRequest<T>(endpoint: string, options?: ApiOptions) {
132
- const config = getApiConfig();
133
- const headers = {
134
- ...(options?.headers || {}),
135
- // Pass Supabase config so backend can connect dynamically
136
- 'X-Supabase-Url': config.edgeFunctionsUrl.replace('/functions/v1', ''),
137
- 'X-Supabase-Anon-Key': config.anonKey,
138
- };
139
-
140
- return this.request<T>(this.expressApiUrl, endpoint, {
141
- ...options,
142
- headers,
143
- });
144
- }
145
-
146
- // ============================================================================
147
- // AUTH & OAUTH ENDPOINTS (Edge Functions)
148
- // ============================================================================
149
-
150
- async getGmailAuthUrl() {
151
- return this.edgeRequest<{ url: string }>('/auth-gmail?action=url', {
152
- method: 'GET',
153
- });
154
- }
155
-
156
- async connectGmail(code: string) {
157
- return this.edgeRequest<{ success: boolean; account: any }>('/auth-gmail', {
158
- method: 'POST',
159
- body: JSON.stringify({ code }),
160
- });
161
- }
162
-
163
- async startMicrosoftDeviceFlow() {
164
- return this.edgeRequest<{
165
- userCode: string;
166
- verificationUri: string;
167
- message: string;
168
- deviceCode: string;
169
- expiresIn: number;
170
- interval: number;
171
- }>('/auth-microsoft?action=device-flow', {
172
- method: 'POST',
173
- });
174
- }
175
-
176
- async pollMicrosoftDeviceCode(deviceCode: string) {
177
- return this.edgeRequest<{
178
- status: 'pending' | 'completed';
179
- account?: any
180
- }>('/auth-microsoft?action=poll', {
181
- method: 'POST',
182
- body: JSON.stringify({ deviceCode }),
183
- });
184
- }
185
-
186
- // ============================================================================
187
- // ACCOUNTS ENDPOINTS (Edge Functions)
188
- // ============================================================================
189
-
190
- async getAccounts() {
191
- return this.edgeRequest<{ accounts: any[] }>('/api-v1-accounts');
192
- }
193
-
194
- async disconnectAccount(accountId: string) {
195
- return this.edgeRequest<{ success: boolean }>(`/api-v1-accounts/${accountId}`, {
196
- method: 'DELETE',
197
- });
198
- }
199
-
200
- async updateAccount(accountId: string, updates: Partial<EmailAccount>) {
201
- return this.edgeRequest<{ account: EmailAccount }>(`/api-v1-accounts/${accountId}`, {
202
- method: 'PATCH',
203
- body: JSON.stringify(updates),
204
- });
205
- }
206
-
207
- // ============================================================================
208
- // EMAILS ENDPOINTS (Edge Functions)
209
- // ============================================================================
210
-
211
- async getEmails(params: {
212
- limit?: number;
213
- offset?: number;
214
- category?: string;
215
- search?: string;
216
- } = {}) {
217
- const query = new URLSearchParams();
218
- if (params.limit) query.set('limit', params.limit.toString());
219
- if (params.offset) query.set('offset', params.offset.toString());
220
- if (params.category) query.set('category', params.category);
221
- if (params.search) query.set('search', params.search);
222
-
223
- return this.edgeRequest<{ emails: any[]; total: number }>(`/api-v1-emails?${query}`);
224
- }
225
-
226
- async getEmail(emailId: string) {
227
- return this.edgeRequest<{ email: any }>(`/api-v1-emails/${emailId}`);
228
- }
229
-
230
- async getEmailEvents(emailId: string) {
231
- return this.edgeRequest<{ events: any[] }>(`/api-v1-emails/${emailId}/events`);
232
- }
233
-
234
- async deleteEmail(emailId: string) {
235
- return this.edgeRequest<{ success: boolean }>(`/api-v1-emails/${emailId}`, {
236
- method: 'DELETE',
237
- });
238
- }
239
-
240
- async getCategorySummary() {
241
- return this.edgeRequest<{ categories: Record<string, number> }>('/api-v1-emails/summary/categories');
242
- }
243
-
244
- // ============================================================================
245
- // RULES ENDPOINTS (Express API - Local App)
246
- // ============================================================================
247
-
248
- async getRules() {
249
- return this.expressRequest<{ rules: any[] }>('/api/rules');
250
- }
251
-
252
- async createRule(rule: { name: string; condition: any; action: string; is_enabled?: boolean, instructions?: string, attachments?: any[] }) {
253
- return this.expressRequest<{ rule: any }>('/api/rules', {
254
- method: 'POST',
255
- body: JSON.stringify(rule),
256
- });
257
- }
258
-
259
- async updateRule(ruleId: string, updates: any) {
260
- return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}`, {
261
- method: 'PATCH',
262
- body: JSON.stringify(updates),
263
- });
264
- }
265
-
266
- async deleteRule(ruleId: string) {
267
- return this.expressRequest<{ success: boolean }>(`/api/rules/${ruleId}`, {
268
- method: 'DELETE',
269
- });
270
- }
271
-
272
- async toggleRule(ruleId: string) {
273
- return this.expressRequest<{ rule: any }>(`/api/rules/${ruleId}/toggle`, {
274
- method: 'POST',
275
- });
276
- }
277
-
278
- // ============================================================================
279
- // SETTINGS ENDPOINTS (Edge Functions)
280
- // ============================================================================
281
-
282
- async getSettings() {
283
- return this.edgeRequest<{ settings: any }>('/api-v1-settings');
284
- }
285
-
286
- async getRunEvents(runId: string) {
287
- return this.edgeRequest<{ events: any[] }>(`/api-v1-settings/logs/${runId}/events`);
288
- }
289
-
290
- async updateSettings(settings: any) {
291
- return this.edgeRequest<{ settings: any }>('/api-v1-settings', {
292
- method: 'PATCH',
293
- body: JSON.stringify(settings),
294
- });
295
- }
296
-
297
- async getStats() {
298
- return this.edgeRequest<{ stats: any }>('/api-v1-settings/stats');
299
- }
300
-
301
- async testLlm(config: { llm_model: string | null; llm_base_url: string | null; llm_api_key: string | null }) {
302
- return this.expressRequest<{ success: boolean; message: string }>('/api/settings/test-llm', {
303
- method: 'POST',
304
- body: JSON.stringify(config),
305
- });
306
- }
307
-
308
- // ============================================================================
309
- // SYNC ENDPOINTS (Express API - Local App)
310
- // ============================================================================
311
-
312
- async triggerSync(accountId: string) {
313
- return this.expressRequest<{ message: string }>('/api/sync', {
314
- method: 'POST',
315
- body: JSON.stringify({ accountId }),
316
- });
317
- }
318
-
319
- async syncAll() {
320
- return this.expressRequest<{ message: string; accountCount: number }>('/api/sync/all', {
321
- method: 'POST',
322
- });
323
- }
324
-
325
- async getSyncLogs(limit = 10) {
326
- return this.expressRequest<{ logs: any[] }>(`/api/sync/logs?limit=${limit}`);
327
- }
328
-
329
- // ============================================================================
330
- // ACTION ENDPOINTS (Express API - Local App)
331
- // ============================================================================
332
-
333
- async executeAction(emailId: string, action: string, draftContent?: string) {
334
- return this.expressRequest<{ success: boolean; details?: string }>('/api/actions/execute', {
335
- method: 'POST',
336
- body: JSON.stringify({ emailId, action, draftContent }),
337
- });
338
- }
339
-
340
- async generateDraft(emailId: string, instructions?: string) {
341
- return this.expressRequest<{ draft: string }>(`/api/actions/draft/${emailId}`, {
342
- method: 'POST',
343
- body: JSON.stringify({ instructions }),
344
- });
345
- }
346
-
347
- async bulkAction(emailIds: string[], action: string) {
348
- return this.expressRequest<{ success: number; failed: number }>('/api/actions/bulk', {
349
- method: 'POST',
350
- body: JSON.stringify({ emailIds, action }),
351
- });
352
- }
353
-
354
- // ============================================================================
355
- // HEALTH CHECK (Express API)
356
- // ============================================================================
357
-
358
- async healthCheck() {
359
- return this.expressRequest<{ status: string; services: any }>('/api/health', {
360
- auth: false
361
- });
362
- }
363
-
364
- // ============================================================================
365
- // PROFILE & SECURITY (Edge Functions / Supabase Auth)
366
- // ============================================================================
367
-
368
- async getProfile() {
369
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
370
- const { data: { user } } = await this.supabaseClient.auth.getUser();
371
- if (!user) return { error: 'Not authenticated' };
372
-
373
- const { data, error } = await this.supabaseClient
374
- .from('profiles')
375
- .select('*')
376
- .eq('id', user.id)
377
- .single();
378
-
379
- return { data, error };
380
- }
381
-
382
- async updateProfile(updates: { first_name?: string; last_name?: string; avatar_url?: string }) {
383
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
384
- const { data: { user } } = await this.supabaseClient.auth.getUser();
385
- if (!user) return { error: 'Not authenticated' };
386
-
387
- const { data, error } = await this.supabaseClient
388
- .from('profiles')
389
- .update({
390
- ...updates,
391
- updated_at: new Date().toISOString()
392
- })
393
- .eq('id', user.id)
394
- .select()
395
- .single();
396
-
397
- return { data, error };
398
- }
399
-
400
- async changePassword(password: string) {
401
- if (!this.supabaseClient) return { error: 'Supabase client not initialized' };
402
- const { error } = await this.supabaseClient.auth.updateUser({ password });
403
- return { success: !error, error };
404
- }
405
- }
406
-
407
- export const api = new HybridApiClient();
408
-
409
- // Helper to initialize API with auth token from Supabase session
410
- export async function initializeApi(supabase: any) {
411
- api.setSupabaseClient(supabase);
412
- const { data: { session } } = await supabase.auth.getSession();
413
- if (session?.access_token) {
414
- api.setToken(session.access_token);
415
- }
416
-
417
- // Listen for auth changes
418
- supabase.auth.onAuthStateChange((_event: string, session: any) => {
419
- api.setToken(session?.access_token || null);
420
- });
421
- }