Fix login data structure and improve voice input UX
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

- Fix login endpoint to return families as array of objects instead of strings
- Update auth interface to match /auth/me endpoint structure
- Add silence detection to voice input (auto-stop after 1.5s)
- Add comprehensive status messages to voice modal (Listening, Understanding, Saving)
- Unify voice input flow to use MediaRecorder + backend for all platforms
- Add null checks to prevent tracking page crashes from invalid data
- Wait for auth completion before loading family data in HomePage

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-02 10:25:13 +00:00
parent 4b8828fdad
commit c60467b6f9
9 changed files with 231 additions and 120 deletions

View File

@@ -16,7 +16,7 @@ export async function POST(request: NextRequest) {
let transcribedText: string;
if (contentType.includes('application/json')) {
// Text input (already transcribed)
// Text input (already transcribed) - forward to backend for LLM classification
const body = await request.json();
transcribedText = body.text;
@@ -29,6 +29,41 @@ export async function POST(request: NextRequest) {
{ status: 400 }
);
}
// Forward text to backend for LLM-based classification
const backendUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3020';
const backendResponse = await fetch(`${backendUrl}/api/v1/voice/transcribe`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Forward auth token if present
...(request.headers.get('authorization') && {
authorization: request.headers.get('authorization')!,
}),
},
body: JSON.stringify({
text: transcribedText,
language: body.language || 'en',
childName: body.childName,
}),
});
if (!backendResponse.ok) {
const errorData = await backendResponse.json();
return NextResponse.json(errorData, { status: backendResponse.status });
}
const result = await backendResponse.json();
// Backend returns { success, transcript, classification }
return NextResponse.json(
{
success: true,
transcript: result.transcript,
classification: result.classification,
},
{ status: 200 }
);
} else if (contentType.includes('multipart/form-data')) {
// Audio file upload - forward to backend for Whisper transcription
const formData = await request.formData();

View File

@@ -25,7 +25,7 @@ import { childrenApi, Child } from '@/lib/api/children';
import { format } from 'date-fns';
export default function HomePage() {
const { user } = useAuth();
const { user, isLoading: authLoading } = useAuth();
const router = useRouter();
const [children, setChildren] = useState<Child[]>([]);
const [selectedChild, setSelectedChild] = useState<Child | null>(null);
@@ -33,17 +33,29 @@ export default function HomePage() {
const [loading, setLoading] = useState(true);
const familyId = user?.families?.[0]?.familyId;
// Load children and daily summary
useEffect(() => {
const loadData = async () => {
// Wait for auth to complete before trying to load data
if (authLoading) {
return;
}
if (!familyId) {
console.log('[HomePage] No familyId found');
console.log('[HomePage] User object:', JSON.stringify(user, null, 2));
console.log('[HomePage] User.families:', user?.families);
setLoading(false);
return;
}
console.log('[HomePage] Loading data for familyId:', familyId);
try {
// Load children
const childrenData = await childrenApi.getChildren(familyId);
console.log('[HomePage] Children loaded:', childrenData.length);
setChildren(childrenData);
if (childrenData.length > 0) {
@@ -56,14 +68,14 @@ export default function HomePage() {
setDailySummary(summary);
}
} catch (error) {
console.error('Failed to load data:', error);
console.error('[HomePage] Failed to load data:', error);
} finally {
setLoading(false);
}
};
loadData();
}, [familyId]);
}, [familyId, authLoading, user]);
const quickActions = [
{ icon: <Restaurant />, label: 'Feeding', color: '#FFB6C1', path: '/track/feeding' },

View File

@@ -619,6 +619,11 @@ export default function DiaperTrackPage() {
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{recentDiapers.map((activity, index) => {
const data = activity.data as DiaperData;
// Skip activities with invalid data structure
if (!data || !data.diaperType) {
console.warn('[Diaper] Activity missing diaperType:', activity);
return null;
}
return (
<motion.div
key={activity.id}

View File

@@ -601,6 +601,11 @@ function FeedingTrackPage() {
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{recentFeedings.map((activity, index) => {
const data = activity.data as FeedingData;
// Skip activities with invalid data structure
if (!data || !data.feedingType) {
console.warn('[Feeding] Activity missing feedingType:', activity);
return null;
}
return (
<motion.div
key={activity.id}

View File

@@ -557,6 +557,11 @@ export default function SleepTrackPage() {
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{recentSleeps.map((activity, index) => {
const data = activity.data as SleepData;
// Skip activities with invalid data structure
if (!data || !data.quality || !data.location) {
console.warn('[Sleep] Activity missing required fields:', activity);
return null;
}
return (
<motion.div
key={activity.id}