logging etc - testing signup flow still
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 20s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 20s
This commit is contained in:
36
App.tsx
36
App.tsx
@@ -5,6 +5,7 @@ import { AnalysisPanel } from './components/AnalysisPanel';
|
||||
import { PriceChart } from './components/PriceChart';
|
||||
import { ErrorDisplay } from './components/ErrorDisplay';
|
||||
import { Header } from './components/Header';
|
||||
import { logger } from './services/logger';
|
||||
import { isImageAFlyer, extractCoreDataFromImage, extractAddressFromImage, extractLogoFromImage } from './services/geminiService';
|
||||
import type { FlyerItem, Flyer, MasterGroceryItem, DealItem, ProcessingStage, StageStatus, Store, Profile, ShoppingList, ShoppingListItem } from './types';
|
||||
import type { Database } from './types/supabase'; // Correctly import the Database type
|
||||
@@ -185,11 +186,13 @@ function App() {
|
||||
// It fetches user-specific data when a session is established.
|
||||
const fetchRealUserSessionData = async (session: Session | null) => {
|
||||
setSession(session);
|
||||
logger.info('Auth state change detected.', { hasSession: !!session });
|
||||
if (session) {
|
||||
const userProfile = await getUserProfile(session.user.id);
|
||||
setProfile(userProfile);
|
||||
fetchWatchedItems(session.user.id);
|
||||
fetchShoppingLists(session.user.id);
|
||||
logger.info('User session active. Fetched profile and user data.', { userId: session.user.id });
|
||||
} else {
|
||||
setProfile(null);
|
||||
setWatchedItems([]);
|
||||
@@ -203,13 +206,7 @@ function App() {
|
||||
|
||||
// Add a separate listener for specific auth events to provide user feedback.
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
// When a user signs up, they are immediately signed in, but their profile
|
||||
// is not yet complete. This is a good time to open the profile manager.
|
||||
if (event === "SIGNED_IN" && session && !profile) {
|
||||
console.log("New user signed in, opening profile manager.");
|
||||
setIsProfileManagerOpen(true);
|
||||
}
|
||||
|
||||
logger.info(`Supabase auth event: ${event}`);
|
||||
if (event === "SIGNED_OUT") {
|
||||
setIsProfileManagerOpen(false);
|
||||
}
|
||||
@@ -219,6 +216,17 @@ function App() {
|
||||
return () => subscription.unsubscribe();
|
||||
}, [isDbConnected, fetchWatchedItems, fetchShoppingLists]);
|
||||
|
||||
// Effect to handle the post-signup redirect.
|
||||
// This is more reliable than checking inside onAuthStateChange, which can re-run.
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash;
|
||||
// If the URL contains '#...' and 'type=signup', it's a confirmation link click.
|
||||
if (hash && hash.includes('type=signup')) {
|
||||
logger.info("New user confirmed email, opening profile manager.");
|
||||
setIsProfileManagerOpen(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (isReady && isDbConnected) {
|
||||
@@ -281,7 +289,7 @@ function App() {
|
||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||
return today >= from && today <= to;
|
||||
} catch (e) {
|
||||
console.error("Error parsing flyer date", e);
|
||||
logger.error("Error parsing flyer date", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -340,7 +348,7 @@ function App() {
|
||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||
return today >= from && today <= to;
|
||||
} catch (e) {
|
||||
console.error("Error parsing flyer date", e);
|
||||
logger.error("Error parsing flyer date", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -354,7 +362,7 @@ function App() {
|
||||
const totalCount = await countFlyerItemsForFlyers(validFlyerIds);
|
||||
setTotalActiveItems(totalCount);
|
||||
} catch (e: any) {
|
||||
console.error("Failed to calculate total active items:", e.message);
|
||||
logger.error("Failed to calculate total active items:", { error: e.message });
|
||||
setTotalActiveItems(0);
|
||||
}
|
||||
};
|
||||
@@ -424,7 +432,7 @@ function App() {
|
||||
storeAddress = await withTimeout(extractAddressFromImage(files[0]), nonCriticalTimeout);
|
||||
updateStage?.(stageIndex++, { status: 'completed' }); // stageIndex is now 4
|
||||
} catch (e: any) {
|
||||
console.warn("Non-critical step failed: Address extraction.", e.message);
|
||||
logger.warn("Non-critical step failed: Address extraction.", { error: e.message });
|
||||
updateStage?.(stageIndex++, { status: 'error', detail: '(Skipped)' }); // stageIndex is now 4
|
||||
}
|
||||
|
||||
@@ -436,7 +444,7 @@ function App() {
|
||||
storeLogoBase64 = logoData.store_logo_base_64;
|
||||
updateStage?.(stageIndex++, { status: 'completed' }); // stageIndex is now 5
|
||||
} catch (e: any) {
|
||||
console.warn("Non-critical step failed: Logo extraction.", e.message);
|
||||
logger.warn("Non-critical step failed: Logo extraction.", { error: e.message });
|
||||
updateStage?.(stageIndex++, { status: 'error', detail: '(Skipped)' }); // stageIndex is now 5
|
||||
}
|
||||
|
||||
@@ -563,7 +571,7 @@ function App() {
|
||||
checksum = await generateFileChecksum(originalFile);
|
||||
const existing = await findFlyerByChecksum(checksum);
|
||||
if (existing) {
|
||||
console.log(`Skipping duplicate file: ${originalFile.name}`);
|
||||
logger.info(`Skipping duplicate file: ${originalFile.name}`);
|
||||
summary.skipped.push(originalFile.name);
|
||||
updateStage(currentStageIndex, { status: 'completed', detail: '(Duplicate)' });
|
||||
setProcessingProgress(((i + 1) / files.length) * 100);
|
||||
@@ -577,7 +585,7 @@ function App() {
|
||||
await processFiles(filesToProcess, checksum, originalFile.name, processFilesUpdateStage);
|
||||
summary.processed.push(originalFile.name);
|
||||
} catch (e: any) {
|
||||
console.error(`Failed to process ${originalFile.name}:`, e);
|
||||
logger.error(`Failed to process ${originalFile.name}:`, { error: e });
|
||||
summary.errors.push({ fileName: originalFile.name, message: e.message });
|
||||
setProcessingStages(prev => prev.map(stage => {
|
||||
if (stage.status === 'in-progress' && (stage.critical ?? true)) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
|
||||
import { Session } from '@supabase/supabase-js';
|
||||
import type { Profile } from '../types';
|
||||
import { supabase, updateUserProfile, updateUserPassword, exportUserData, deleteUserAccount } from '../services/supabaseClient';
|
||||
import { logger } from '../services/logger';
|
||||
import { LoadingSpinner } from './LoadingSpinner';
|
||||
import { XMarkIcon } from './icons/XMarkIcon';
|
||||
import { GoogleIcon } from './icons/GoogleIcon';
|
||||
@@ -63,8 +64,10 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
avatar_url: avatarUrl
|
||||
});
|
||||
onProfileUpdate(updatedProfile);
|
||||
logger.info('User profile updated successfully.', { userId: session.user.id });
|
||||
setProfileMessage('Profile updated successfully!');
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to update user profile.', { userId: session.user.id, error: error.message });
|
||||
setProfileMessage(error.message);
|
||||
} finally {
|
||||
setProfileLoading(false);
|
||||
@@ -83,6 +86,7 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
});
|
||||
if (error) {
|
||||
// This error will be shown if the user cancels or if there's a config issue.
|
||||
logger.error(`Could not link ${provider} account.`, { userId: session.user.id, error: error.message });
|
||||
setPasswordError(`Could not link ${provider} account: ${error.message}`);
|
||||
}
|
||||
};
|
||||
@@ -102,10 +106,12 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
setPasswordMessage('');
|
||||
try {
|
||||
await updateUserPassword(password);
|
||||
logger.info('User password updated successfully.', { userId: session.user.id });
|
||||
setPasswordMessage("Password updated successfully!");
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to update user password.', { userId: session.user.id, error: error.message });
|
||||
setPasswordError(error.message);
|
||||
} finally {
|
||||
setPasswordLoading(false);
|
||||
@@ -119,6 +125,7 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
const handleExportData = async () => {
|
||||
setExportLoading(true);
|
||||
try {
|
||||
logger.info('User initiated data export.', { userId: session.user.id });
|
||||
const userData = await exportUserData(session.user.id);
|
||||
const jsonString = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(userData, null, 2))}`;
|
||||
const link = document.createElement("a");
|
||||
@@ -126,7 +133,7 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
link.download = `flyer-crawler-data-export-${new Date().toISOString().split('T')[0]}.json`;
|
||||
link.click();
|
||||
} catch (error: any) {
|
||||
console.error("Failed to export data:", error);
|
||||
logger.error("Failed to export user data:", { userId: session.user.id, error });
|
||||
alert(`Error exporting data: ${error.message}`);
|
||||
} finally {
|
||||
setExportLoading(false);
|
||||
@@ -138,11 +145,13 @@ export const ProfileManager: React.FC<ProfileManagerProps> = ({ isOpen, onClose,
|
||||
setDeleteLoading(true);
|
||||
setDeleteError('');
|
||||
try {
|
||||
logger.warn('User initiated account deletion.', { userId: session.user.id });
|
||||
await deleteUserAccount(passwordForDelete);
|
||||
alert("Your account and all associated data have been permanently deleted.");
|
||||
// The onAuthStateChange listener in App.tsx will handle the UI update
|
||||
await supabase.auth.signOut();
|
||||
onClose();
|
||||
logger.warn('User account deleted successfully.', { userId: session.user.id });
|
||||
} catch (error: any) {
|
||||
setDeleteError(error.message);
|
||||
} finally {
|
||||
|
||||
@@ -15,6 +15,7 @@ interface SignUpModalProps {
|
||||
export const SignUpModal: React.FC<SignUpModalProps> = ({ isOpen, onClose, onSwitchToSignIn }) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
@@ -22,6 +23,13 @@ export const SignUpModal: React.FC<SignUpModalProps> = ({ isOpen, onClose, onSwi
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
// Add password confirmation check
|
||||
if (password !== confirmPassword) {
|
||||
setError("Passwords do not match.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
setMessage(null);
|
||||
|
||||
@@ -111,6 +119,10 @@ export const SignUpModal: React.FC<SignUpModalProps> = ({ isOpen, onClose, onSwi
|
||||
<input id="password-signup" type="password" value={password} onChange={e => setPassword(e.target.value)} required className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-brand-primary focus:border-brand-primary" placeholder="••••••••" />
|
||||
{password.length > 0 && <PasswordStrength password={password} />}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirm-password-signup" className="block text-sm font-medium text-gray-700 dark:text-gray-300">Confirm Password</label>
|
||||
<input id="confirm-password-signup" type="password" value={confirmPassword} onChange={e => setConfirmPassword(e.target.value)} required className="mt-1 block w-full px-3 py-2 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-brand-primary focus:border-brand-primary" placeholder="••••••••" />
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-600 dark:text-red-400 text-center">{error}</p>}
|
||||
{message && <p className="text-sm text-green-600 dark:text-green-400 text-center">{message}</p>}
|
||||
<button type="submit" disabled={loading || !!message} className="w-full bg-brand-secondary hover:bg-brand-dark disabled:bg-gray-400 disabled:cursor-not-allowed text-white font-bold py-2.5 px-4 rounded-lg transition-colors duration-300 flex items-center justify-center">
|
||||
|
||||
39
services/logger.ts
Normal file
39
services/logger.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* A simple logger service that wraps the console.
|
||||
* This provides a centralized place to manage logging behavior,
|
||||
* such as adding timestamps, log levels, or sending logs to a remote service.
|
||||
*/
|
||||
|
||||
const getTimestamp = () => new Date().toISOString();
|
||||
|
||||
type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG';
|
||||
|
||||
const log = (level: LogLevel, message: string, ...args: any[]) => {
|
||||
const timestamp = getTimestamp();
|
||||
// We construct the log message with a timestamp and level for better context.
|
||||
const logMessage = `[${timestamp}] [${level}] ${message}`;
|
||||
|
||||
switch (level) {
|
||||
case 'INFO':
|
||||
console.log(logMessage, ...args);
|
||||
break;
|
||||
case 'WARN':
|
||||
console.warn(logMessage, ...args);
|
||||
break;
|
||||
case 'ERROR':
|
||||
console.error(logMessage, ...args);
|
||||
break;
|
||||
case 'DEBUG':
|
||||
// For now, we can show debug logs in development. This could be controlled by an environment variable.
|
||||
console.debug(logMessage, ...args);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Export the logger object for use throughout the application.
|
||||
export const logger = {
|
||||
info: (message: string, ...args: any[]) => log('INFO', message, ...args),
|
||||
warn: (message: string, ...args: any[]) => log('WARN', message, ...args),
|
||||
error: (message: string, ...args: any[]) => log('ERROR', message, ...args),
|
||||
debug: (message: string, ...args: any[]) => log('DEBUG', message, ...args),
|
||||
};
|
||||
Reference in New Issue
Block a user