fix: Fix 3 critical bugs - voice tracking, session persistence, and status updates
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

BUG-1: Voice tracking not saving activities
- Fix activity data format to match backend CreateActivityDto
- Change 'timestamp' to 'startedAt' and 'data' to 'metadata'
- Remove duplicate voice button from mobile TabBar

BUG-2: Session persistence after revocation
- Add logout() call when revoking all sessions
- Add logout() call when removing all devices
- Ensures user is logged out after session/device revocation
- Clears tokens and redirects to login

BUG-3: Voice modal status not updating
- Set identifiedActivity before saving to show tracker name
- Display "Adding to [tracker] tracker..." during save
- Improves UX by showing which tracker is being updated

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 06:19:09 +00:00
parent e2ca04c98f
commit 2ab98746da
5 changed files with 22 additions and 36 deletions

View File

@@ -110,34 +110,7 @@ export const TabBar = () => {
</BottomNavigation> </BottomNavigation>
</Paper> </Paper>
{/* Voice Command Floating Button - Mobile Only */} {/* Voice Command Floating Button is now centralized in layout.tsx - removed duplicate */}
<Fab
color="secondary"
aria-label="voice command"
onClick={() => {
// Trigger voice command - will integrate with existing VoiceFloatingButton
const voiceButton = document.querySelector('[aria-label="voice input"]') as HTMLButtonElement;
if (voiceButton) {
voiceButton.click();
}
}}
sx={{
display: { xs: 'flex', md: 'none' },
position: 'fixed',
bottom: 40,
left: '50%',
transform: 'translateX(-50%)',
zIndex: 1100,
width: 56,
height: 56,
bgcolor: '#FF69B4',
'&:hover': {
bgcolor: '#FF1493',
},
}}
>
<Mic />
</Fab>
</> </>
); );
}; };

View File

@@ -36,8 +36,10 @@ import {
import { devicesApi, type DeviceInfo } from '@/lib/api/devices'; import { devicesApi, type DeviceInfo } from '@/lib/api/devices';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useLocalizedDate } from '@/hooks/useLocalizedDate';
import { useAuth } from '@/lib/auth/AuthContext';
export function DeviceTrustManagement() { export function DeviceTrustManagement() {
const { logout } = useAuth();
const { formatDistanceToNow } = useLocalizedDate(); const { formatDistanceToNow } = useLocalizedDate();
const [devices, setDevices] = useState<DeviceInfo[]>([]); const [devices, setDevices] = useState<DeviceInfo[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
@@ -112,13 +114,16 @@ export function DeviceTrustManagement() {
const response = await devicesApi.removeAllDevices(); const response = await devicesApi.removeAllDevices();
setSuccessMessage(`${response.removedCount} device(s) removed successfully`); setSuccessMessage(`${response.removedCount} device(s) removed successfully`);
setRemoveAllDialogOpen(false); setRemoveAllDialogOpen(false);
await loadDevices();
// Logout the current user since all devices are removed
// This terminates all sessions and redirects to login
await logout();
} catch (err: any) { } catch (err: any) {
console.error('Failed to remove all devices:', err); console.error('Failed to remove all devices:', err);
setError(err.response?.data?.message || 'Failed to remove devices'); setError(err.response?.data?.message || 'Failed to remove devices');
} finally {
setIsRemovingAll(false); setIsRemovingAll(false);
} }
// Note: Don't set isRemovingAll to false here, as we're logging out
}; };
const getPlatformIcon = (platform?: string) => { const getPlatformIcon = (platform?: string) => {

View File

@@ -32,9 +32,11 @@ import {
import { sessionsApi, type SessionInfo } from '@/lib/api/sessions'; import { sessionsApi, type SessionInfo } from '@/lib/api/sessions';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useLocalizedDate } from '@/hooks/useLocalizedDate';
import { useAuth } from '@/lib/auth/AuthContext';
export function SessionsManagement() { export function SessionsManagement() {
const { formatDistanceToNow } = useLocalizedDate(); const { formatDistanceToNow } = useLocalizedDate();
const { logout } = useAuth();
const [sessions, setSessions] = useState<SessionInfo[]>([]); const [sessions, setSessions] = useState<SessionInfo[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -91,13 +93,16 @@ export function SessionsManagement() {
const response = await sessionsApi.revokeAllSessions(); const response = await sessionsApi.revokeAllSessions();
setSuccessMessage(`${response.revokedCount} session(s) revoked successfully`); setSuccessMessage(`${response.revokedCount} session(s) revoked successfully`);
setRevokeAllDialogOpen(false); setRevokeAllDialogOpen(false);
await loadSessions();
// Logout the current user since all sessions are revoked
// This clears tokens and redirects to login
await logout();
} catch (err: any) { } catch (err: any) {
console.error('Failed to revoke all sessions:', err); console.error('Failed to revoke all sessions:', err);
setError(err.response?.data?.message || 'Failed to revoke sessions'); setError(err.response?.data?.message || 'Failed to revoke sessions');
} finally {
setIsRevokingAll(false); setIsRevokingAll(false);
} }
// Note: Don't set isRevokingAll to false here, as we're logging out
}; };
const getPlatformIcon = (platform?: string) => { const getPlatformIcon = (platform?: string) => {

View File

@@ -173,11 +173,12 @@ export function VoiceFloatingButton() {
const childId = children[0].id; const childId = children[0].id;
console.log('[Voice] Using child ID:', childId); console.log('[Voice] Using child ID:', childId);
// Create the activity // Create the activity - match backend CreateActivityDto format
const activityData = { const activityData = {
type: activityType, type: activityType,
timestamp: activityTimestamp || new Date().toISOString(), startedAt: activityTimestamp ? new Date(activityTimestamp).toISOString() : new Date().toISOString(),
data: activityDetails, endedAt: activityDetails.endedAt || undefined,
metadata: activityDetails,
notes: activityDetails.notes || undefined, notes: activityDetails.notes || undefined,
}; };
@@ -213,6 +214,7 @@ export function VoiceFloatingButton() {
const handleApprove = async (data: any) => { const handleApprove = async (data: any) => {
try { try {
setIsProcessing(true); setIsProcessing(true);
setIdentifiedActivity(data.type); // Set the activity type for display
setProcessingStatus('saving'); setProcessingStatus('saving');
setShowReview(false); setShowReview(false);
@@ -249,6 +251,7 @@ export function VoiceFloatingButton() {
const handleEdit = async (editedData: any) => { const handleEdit = async (editedData: any) => {
try { try {
setIsProcessing(true); setIsProcessing(true);
setIdentifiedActivity(editedData.type); // Set the activity type for display
setProcessingStatus('saving'); setProcessingStatus('saving');
setShowReview(false); setShowReview(false);

File diff suppressed because one or more lines are too long