Details
+{{ JSON.stringify(entry.event_data, null, 2) }}+
From 6e56e164df45c555b7edbf36cc54074376871129 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:30:52 +0000 Subject: [PATCH 1/7] Fix: Prevent automatic logout when starting app offline Problem: The application would inadvertently log you out if it was started while offline. This occurred because the `fetchCurrentUser` action in the `authStore` would attempt to fetch your profile, and if this network request failed (as it does when offline), the catch block would unconditionally call `clearTokens()`. This removed the authentication token, effectively logging you out and preventing access to any cached data or offline functionality. Solution: I modified the `fetchCurrentUser` action in `fe/src/stores/auth.ts`: - The `catch` block now inspects the error. - `clearTokens()` is only called if the error is a specific HTTP authentication error from the server (401 Unauthorized or 403 Forbidden) when online. - For network errors (indicating offline status) or other non-auth HTTP errors, tokens are preserved. The user object (`user.value`) might remain null if no cached profile is available, but the authentication token itself is kept. This change allows the application to remain in a logged-in state when started offline. The service worker can then serve cached API responses, and you can view previously accessed data. Navigation guards rely on `isAuthenticated` (which now remains true offline as long as a token exists), so you are not incorrectly redirected to the login page. --- fe/src/stores/auth.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts index 1996562..7c6d386 100644 --- a/fe/src/stores/auth.ts +++ b/fe/src/stores/auth.ts @@ -57,6 +57,7 @@ export const useAuthStore = defineStore('auth', () => { const fetchCurrentUser = async () => { if (!accessToken.value) { + // No token, so definitely clear any residual state and return. clearTokens() return null } @@ -65,7 +66,28 @@ export const useAuthStore = defineStore('auth', () => { setUser(response.data) return response.data } catch (error: any) { - clearTokens() + // Check if the error is from an Axios request and has a response status + if (error.isAxiosError && error.response) { + const status = error.response.status + if (status === 401 || status === 403) { + // Authentication error from the server, clear tokens. + console.error('Authentication error fetching user, clearing tokens:', error) + clearTokens() + } else { + // Other HTTP error, log it but don't clear tokens. + // The user might be null, but the token remains for other cached calls. + console.error('HTTP error fetching user, token preserved:', error) + } + } else { + // Network error (offline) or other non-HTTP error. + // Log the error but preserve tokens. + // This allows the app to function with cached data if available. + console.error('Network or other error fetching user, token preserved:', error) + } + // In all error cases where tokens are not cleared, return null for the user object. + // The existing user object (if any) will remain until explicitly cleared or overwritten. + // If the intention is to clear the user object on any fetch error, uncomment the next line: + // setUser(null); return null } } From 198222c3ffe2a28fc1d96ac8c563cfffbf79ed28 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 20:40:49 +0000 Subject: [PATCH 2/7] feat: Add missing i18n translations for page components (partial) This commit introduces internationalization for several page components by identifying hardcoded strings, adding them to translation files, and updating the components to use translation keys. Processed pages: - fe/src/pages/AuthCallbackPage.vue: I internationalized an error message. - fe/src/pages/ChoresPage.vue: I internationalized console error messages and an input placeholder. - fe/src/pages/ErrorNotFound.vue: I found no missing translations. - fe/src/pages/GroupDetailPage.vue: I internationalized various UI elements (ARIA labels, button text, fallback user display names) and console/error messages. - fe/src/pages/GroupsPage.vue: I internationalized error messages and console logs. - fe/src/pages/IndexPage.vue: I found no missing user-facing translations. - fe/src/pages/ListDetailPage.vue: My analysis is complete, and I identified a console message and a fallback string for translation (implementation of changes for this page is pending). For each processed page where changes were needed: - I added new keys to `fe/src/i18n/en.json`. - I added corresponding placeholder keys `"[TRANSLATE] Original Text"` to `fe/src/i18n/de.json`, `fe/src/i18n/es.json`, and `fe/src/i18n/fr.json`. - I updated the Vue component to use the `t()` function with the new keys. Further pages in `fe/src/pages/` are pending analysis and internationalization as per our original plan. --- fe/src/i18n/de.json | 37 +++++++++++++++++++++++++++---- fe/src/i18n/en.json | 37 +++++++++++++++++++++++++++---- fe/src/i18n/es.json | 37 +++++++++++++++++++++++++++---- fe/src/i18n/fr.json | 37 +++++++++++++++++++++++++++---- fe/src/pages/AuthCallbackPage.vue | 2 +- fe/src/pages/ChoresPage.vue | 16 ++++++------- fe/src/pages/GroupDetailPage.vue | 26 ++++++++++------------ fe/src/pages/GroupsPage.vue | 6 ++--- 8 files changed, 156 insertions(+), 42 deletions(-) diff --git a/fe/src/i18n/de.json b/fe/src/i18n/de.json index ab6a3ff..49c77bf 100644 --- a/fe/src/i18n/de.json +++ b/fe/src/i18n/de.json @@ -73,7 +73,10 @@ "groupNameRequired": "DE: Group name is required", "createFailed": "DE: Failed to create group. Please try again.", "inviteCodeRequired": "DE: Invite code is required", - "joinFailed": "DE: Failed to join group. Please check the invite code and try again." + "joinFailed": "DE: Failed to join group. Please check the invite code and try again.", + "invalidDataFromServer": "[TRANSLATE] Invalid data received from server.", + "createFailedConsole": "[TRANSLATE] Error creating group:", + "joinFailedConsole": "[TRANSLATE] Error joining group:" }, "notifications": { "groupCreatedSuccess": "DE: Group '{groupName}' created successfully.", @@ -85,7 +88,8 @@ "authCallbackPage": { "redirecting": "DE: Redirecting...", "errors": { - "authenticationFailed": "DE: Authentication failed" + "authenticationFailed": "DE: Authentication failed", + "noTokenProvided": "[TRANSLATE] No token provided" } }, "choresPage": { @@ -165,7 +169,17 @@ "quickDueDateTomorrow": "DE: Tomorrow", "quickDueDateNextWeek": "DE: Next Week", "cancelButton": "DE: Cancel", - "saveButton": "DE: Save" + "saveButton": "DE: Save", + "intervalPlaceholder": "[TRANSLATE] e.g., 10" + }, + "consoleErrors": { + "loadFailed": "[TRANSLATE] Failed to load all chores:", + "loadGroupsFailed": "[TRANSLATE] Failed to load groups", + "createAssignmentForNewChoreFailed": "[TRANSLATE] Failed to create assignment for new chore:", + "saveFailed": "[TRANSLATE] Failed to save chore:", + "deleteFailed": "[TRANSLATE] Failed to delete chore:", + "createAssignmentFailed": "[TRANSLATE] Failed to create assignment:", + "updateCompletionStatusFailed": "[TRANSLATE] Failed to update chore completion status:" }, "deleteDialog": { "title": "DE: Delete Chore", @@ -228,10 +242,14 @@ "title": "DE: Group Members", "defaultRole": "DE: Member", "removeButton": "DE: Remove", - "emptyState": "DE: No members found." + "emptyState": "DE: No members found.", + "closeMenuLabel": "[TRANSLATE] Close menu" }, "invites": { "title": "DE: Invite Members", + "description": "[TRANSLATE] Invite new members by generating a shareable code.", + "addMemberButtonLabel": "[TRANSLATE] Add member", + "closeInviteLabel": "[TRANSLATE] Close invite", "regenerateButton": "DE: Regenerate Invite Code", "generateButton": "DE: Generate Invite Code", "activeCodeLabel": "DE: Current Active Invite Code:", @@ -242,6 +260,15 @@ "newDataInvalid": "DE: New invite code data is invalid." } }, + "errors": { + "failedToFetchActiveInvite": "[TRANSLATE] Failed to fetch active invite code.", + "failedToFetchGroupDetails": "[TRANSLATE] Failed to fetch group details.", + "failedToLoadUpcomingChores": "[TRANSLATE] Error loading upcoming chores:", + "failedToLoadRecentExpenses": "[TRANSLATE] Error loading recent expenses:" + }, + "console": { + "noActiveInvite": "[TRANSLATE] No active invite code found for this group." + }, "chores": { "title": "DE: Group Chores", "manageButton": "DE: Manage Chores", @@ -252,6 +279,8 @@ "title": "DE: Group Expenses", "manageButton": "DE: Manage Expenses", "emptyState": "DE: No expenses recorded. Click \"Manage Expenses\" to add some!", + "fallbackUserName": "[TRANSLATE] User ID: {userId}", + "activityByUserFallback": "[TRANSLATE] User {userId}", "splitTypes": { "equal": "DE: Equal", "exactAmounts": "DE: Exact Amounts", diff --git a/fe/src/i18n/en.json b/fe/src/i18n/en.json index 62afc82..57abdd0 100644 --- a/fe/src/i18n/en.json +++ b/fe/src/i18n/en.json @@ -73,7 +73,10 @@ "groupNameRequired": "Group name is required", "createFailed": "Failed to create group. Please try again.", "inviteCodeRequired": "Invite code is required", - "joinFailed": "Failed to join group. Please check the invite code and try again." + "joinFailed": "Failed to join group. Please check the invite code and try again.", + "invalidDataFromServer": "Invalid data received from server.", + "createFailedConsole": "Error creating group:", + "joinFailedConsole": "Error joining group:" }, "notifications": { "groupCreatedSuccess": "Group '{groupName}' created successfully.", @@ -85,7 +88,8 @@ "authCallbackPage": { "redirecting": "Redirecting...", "errors": { - "authenticationFailed": "Authentication failed" + "authenticationFailed": "Authentication failed", + "noTokenProvided": "No token provided" } }, "choresPage": { @@ -125,7 +129,17 @@ "save": "Save Changes", "create": "Create", "editChore": "Edit Chore", - "createChore": "Create Chore" + "createChore": "Create Chore", + "intervalPlaceholder": "e.g., 10" + }, + "consoleErrors": { + "loadFailed": "Failed to load all chores:", + "loadGroupsFailed": "Failed to load groups", + "createAssignmentForNewChoreFailed": "Failed to create assignment for new chore:", + "saveFailed": "Failed to save chore:", + "deleteFailed": "Failed to delete chore:", + "createAssignmentFailed": "Failed to create assignment:", + "updateCompletionStatusFailed": "Failed to update chore completion status:" }, "deleteConfirm": { "title": "Confirm Deletion", @@ -160,10 +174,14 @@ "title": "Group Members", "defaultRole": "Member", "removeButton": "Remove", - "emptyState": "No members found." + "emptyState": "No members found.", + "closeMenuLabel": "Close menu" }, "invites": { "title": "Invite Members", + "description": "Invite new members by generating a shareable code.", + "addMemberButtonLabel": "Add member", + "closeInviteLabel": "Close invite", "regenerateButton": "Regenerate Invite Code", "generateButton": "Generate Invite Code", "activeCodeLabel": "Current Active Invite Code:", @@ -174,6 +192,15 @@ "newDataInvalid": "New invite code data is invalid." } }, + "errors": { + "failedToFetchActiveInvite": "Failed to fetch active invite code.", + "failedToFetchGroupDetails": "Failed to fetch group details.", + "failedToLoadUpcomingChores": "Error loading upcoming chores:", + "failedToLoadRecentExpenses": "Error loading recent expenses:" + }, + "console": { + "noActiveInvite": "No active invite code found for this group." + }, "chores": { "title": "Group Chores", "manageButton": "Manage Chores", @@ -191,6 +218,8 @@ "settleShareButton": "Settle My Share", "activityLabel": "Activity:", "byUser": "by", + "fallbackUserName": "User ID: {userId}", + "activityByUserFallback": "User {userId}", "splitTypes": { "equal": "Equal", "exactAmounts": "Exact Amounts", diff --git a/fe/src/i18n/es.json b/fe/src/i18n/es.json index 665870e..5560da0 100644 --- a/fe/src/i18n/es.json +++ b/fe/src/i18n/es.json @@ -73,7 +73,10 @@ "groupNameRequired": "ES: Group name is required", "createFailed": "ES: Failed to create group. Please try again.", "inviteCodeRequired": "ES: Invite code is required", - "joinFailed": "ES: Failed to join group. Please check the invite code and try again." + "joinFailed": "ES: Failed to join group. Please check the invite code and try again.", + "invalidDataFromServer": "[TRANSLATE] Invalid data received from server.", + "createFailedConsole": "[TRANSLATE] Error creating group:", + "joinFailedConsole": "[TRANSLATE] Error joining group:" }, "notifications": { "groupCreatedSuccess": "ES: Group '{groupName}' created successfully.", @@ -85,7 +88,8 @@ "authCallbackPage": { "redirecting": "ES: Redirecting...", "errors": { - "authenticationFailed": "ES: Authentication failed" + "authenticationFailed": "ES: Authentication failed", + "noTokenProvided": "[TRANSLATE] No token provided" } }, "choresPage": { @@ -165,7 +169,17 @@ "quickDueDateTomorrow": "ES: Tomorrow", "quickDueDateNextWeek": "ES: Next Week", "cancelButton": "ES: Cancel", - "saveButton": "ES: Save" + "saveButton": "ES: Save", + "intervalPlaceholder": "[TRANSLATE] e.g., 10" + }, + "consoleErrors": { + "loadFailed": "[TRANSLATE] Failed to load all chores:", + "loadGroupsFailed": "[TRANSLATE] Failed to load groups", + "createAssignmentForNewChoreFailed": "[TRANSLATE] Failed to create assignment for new chore:", + "saveFailed": "[TRANSLATE] Failed to save chore:", + "deleteFailed": "[TRANSLATE] Failed to delete chore:", + "createAssignmentFailed": "[TRANSLATE] Failed to create assignment:", + "updateCompletionStatusFailed": "[TRANSLATE] Failed to update chore completion status:" }, "deleteDialog": { "title": "ES: Delete Chore", @@ -228,10 +242,14 @@ "title": "ES: Group Members", "defaultRole": "ES: Member", "removeButton": "ES: Remove", - "emptyState": "ES: No members found." + "emptyState": "ES: No members found.", + "closeMenuLabel": "[TRANSLATE] Close menu" }, "invites": { "title": "ES: Invite Members", + "description": "[TRANSLATE] Invite new members by generating a shareable code.", + "addMemberButtonLabel": "[TRANSLATE] Add member", + "closeInviteLabel": "[TRANSLATE] Close invite", "regenerateButton": "ES: Regenerate Invite Code", "generateButton": "ES: Generate Invite Code", "activeCodeLabel": "ES: Current Active Invite Code:", @@ -242,6 +260,15 @@ "newDataInvalid": "ES: New invite code data is invalid." } }, + "errors": { + "failedToFetchActiveInvite": "[TRANSLATE] Failed to fetch active invite code.", + "failedToFetchGroupDetails": "[TRANSLATE] Failed to fetch group details.", + "failedToLoadUpcomingChores": "[TRANSLATE] Error loading upcoming chores:", + "failedToLoadRecentExpenses": "[TRANSLATE] Error loading recent expenses:" + }, + "console": { + "noActiveInvite": "[TRANSLATE] No active invite code found for this group." + }, "chores": { "title": "ES: Group Chores", "manageButton": "ES: Manage Chores", @@ -252,6 +279,8 @@ "title": "ES: Group Expenses", "manageButton": "ES: Manage Expenses", "emptyState": "ES: No expenses recorded. Click \"Manage Expenses\" to add some!", + "fallbackUserName": "[TRANSLATE] User ID: {userId}", + "activityByUserFallback": "[TRANSLATE] User {userId}", "splitTypes": { "equal": "ES: Equal", "exactAmounts": "ES: Exact Amounts", diff --git a/fe/src/i18n/fr.json b/fe/src/i18n/fr.json index cf8c4fc..887eb41 100644 --- a/fe/src/i18n/fr.json +++ b/fe/src/i18n/fr.json @@ -73,7 +73,10 @@ "groupNameRequired": "FR: Group name is required", "createFailed": "FR: Failed to create group. Please try again.", "inviteCodeRequired": "FR: Invite code is required", - "joinFailed": "FR: Failed to join group. Please check the invite code and try again." + "joinFailed": "FR: Failed to join group. Please check the invite code and try again.", + "invalidDataFromServer": "[TRANSLATE] Invalid data received from server.", + "createFailedConsole": "[TRANSLATE] Error creating group:", + "joinFailedConsole": "[TRANSLATE] Error joining group:" }, "notifications": { "groupCreatedSuccess": "FR: Group '{groupName}' created successfully.", @@ -85,7 +88,8 @@ "authCallbackPage": { "redirecting": "FR: Redirecting...", "errors": { - "authenticationFailed": "FR: Authentication failed" + "authenticationFailed": "FR: Authentication failed", + "noTokenProvided": "[TRANSLATE] No token provided" } }, "choresPage": { @@ -165,7 +169,17 @@ "quickDueDateTomorrow": "FR: Tomorrow", "quickDueDateNextWeek": "FR: Next Week", "cancelButton": "FR: Cancel", - "saveButton": "FR: Save" + "saveButton": "FR: Save", + "intervalPlaceholder": "[TRANSLATE] e.g., 10" + }, + "consoleErrors": { + "loadFailed": "[TRANSLATE] Failed to load all chores:", + "loadGroupsFailed": "[TRANSLATE] Failed to load groups", + "createAssignmentForNewChoreFailed": "[TRANSLATE] Failed to create assignment for new chore:", + "saveFailed": "[TRANSLATE] Failed to save chore:", + "deleteFailed": "[TRANSLATE] Failed to delete chore:", + "createAssignmentFailed": "[TRANSLATE] Failed to create assignment:", + "updateCompletionStatusFailed": "[TRANSLATE] Failed to update chore completion status:" }, "deleteDialog": { "title": "FR: Delete Chore", @@ -228,10 +242,14 @@ "title": "FR: Group Members", "defaultRole": "FR: Member", "removeButton": "FR: Remove", - "emptyState": "FR: No members found." + "emptyState": "FR: No members found.", + "closeMenuLabel": "[TRANSLATE] Close menu" }, "invites": { "title": "FR: Invite Members", + "description": "[TRANSLATE] Invite new members by generating a shareable code.", + "addMemberButtonLabel": "[TRANSLATE] Add member", + "closeInviteLabel": "[TRANSLATE] Close invite", "regenerateButton": "FR: Regenerate Invite Code", "generateButton": "FR: Generate Invite Code", "activeCodeLabel": "FR: Current Active Invite Code:", @@ -242,6 +260,15 @@ "newDataInvalid": "FR: New invite code data is invalid." } }, + "errors": { + "failedToFetchActiveInvite": "[TRANSLATE] Failed to fetch active invite code.", + "failedToFetchGroupDetails": "[TRANSLATE] Failed to fetch group details.", + "failedToLoadUpcomingChores": "[TRANSLATE] Error loading upcoming chores:", + "failedToLoadRecentExpenses": "[TRANSLATE] Error loading recent expenses:" + }, + "console": { + "noActiveInvite": "[TRANSLATE] No active invite code found for this group." + }, "chores": { "title": "FR: Group Chores", "manageButton": "FR: Manage Chores", @@ -252,6 +279,8 @@ "title": "FR: Group Expenses", "manageButton": "FR: Manage Expenses", "emptyState": "FR: No expenses recorded. Click \"Manage Expenses\" to add some!", + "fallbackUserName": "[TRANSLATE] User ID: {userId}", + "activityByUserFallback": "[TRANSLATE] User {userId}", "splitTypes": { "equal": "FR: Equal", "exactAmounts": "FR: Exact Amounts", diff --git a/fe/src/pages/AuthCallbackPage.vue b/fe/src/pages/AuthCallbackPage.vue index 81ed4d7..d50a510 100644 --- a/fe/src/pages/AuthCallbackPage.vue +++ b/fe/src/pages/AuthCallbackPage.vue @@ -38,7 +38,7 @@ onMounted(async () => { const tokenToUse = accessToken || legacyToken; if (!tokenToUse) { - throw new Error('No token provided'); + throw new Error(t('authCallbackPage.errors.noTokenProvided')); } await authStore.setTokens({ access_token: tokenToUse, refresh_token: refreshToken }); diff --git a/fe/src/pages/ChoresPage.vue b/fe/src/pages/ChoresPage.vue index b4104b8..0ec5b6c 100644 --- a/fe/src/pages/ChoresPage.vue +++ b/fe/src/pages/ChoresPage.vue @@ -80,7 +80,7 @@ const loadChores = async () => { cachedChores.value = mappedChores; cachedTimestamp.value = Date.now() } catch (error) { - console.error('Failed to load all chores:', error) + console.error(t('choresPage.consoleErrors.loadFailed'), error) notificationStore.addNotification({ message: t('choresPage.notifications.loadFailed', 'Failed to load chores.'), type: 'error' }) } finally { isLoading.value = false @@ -91,7 +91,7 @@ const loadGroups = async () => { try { groups.value = await groupService.getUserGroups(); } catch (error) { - console.error("Failed to load groups", error); + console.error(t('choresPage.consoleErrors.loadGroupsFailed'), error); notificationStore.addNotification({ message: t('choresPage.notifications.loadGroupsFailed', 'Failed to load groups.'), type: 'error' }); } } @@ -227,7 +227,7 @@ const handleFormSubmit = async () => { due_date: createdChore.next_due_date }); } catch (assignmentError) { - console.error('Failed to create assignment for new chore:', assignmentError); + console.error(t('choresPage.consoleErrors.createAssignmentForNewChoreFailed'), assignmentError); // Continue anyway since the chore was created } } @@ -237,7 +237,7 @@ const handleFormSubmit = async () => { showChoreModal.value = false; await loadChores(); } catch (error) { - console.error('Failed to save chore:', error); + console.error(t('choresPage.consoleErrors.saveFailed'), error); notificationStore.addNotification({ message: t('choresPage.notifications.saveFailed', 'Failed to save the chore.'), type: 'error' }); } } @@ -255,7 +255,7 @@ const deleteChore = async () => { showDeleteDialog.value = false await loadChores() } catch (error) { - console.error('Failed to delete chore:', error) + console.error(t('choresPage.consoleErrors.deleteFailed'), error) notificationStore.addNotification({ message: t('choresPage.notifications.deleteFailed', 'Failed to delete chore.'), type: 'error' }) } } @@ -271,7 +271,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => { }); chore.current_assignment_id = assignment.id; } catch (error) { - console.error('Failed to create assignment:', error); + console.error(t('choresPage.consoleErrors.createAssignmentFailed'), error); notificationStore.addNotification({ message: t('choresPage.notifications.createAssignmentFailed', 'Failed to create assignment for chore.'), type: 'error' @@ -299,7 +299,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => { }); await loadChores(); } catch (error) { - console.error('Failed to update chore completion status:', error); + console.error(t('choresPage.consoleErrors.updateCompletionStatusFailed'), error); notificationStore.addNotification({ message: t('choresPage.notifications.updateFailed', 'Failed to update chore status.'), type: 'error' }); chore.is_completed = originalCompleted; } finally { @@ -403,7 +403,7 @@ const toggleCompletion = async (chore: ChoreWithCompletion) => { + class="form-input" :placeholder="t('choresPage.form.intervalPlaceholder')" min="1">
Invite new members by generating a shareable code.
+{{ t('groupDetailPage.invites.description') }}
{{ t('groupDetailPage.settleShareModal.settleAmountFor', { userName: selectedSplitForSettlement?.user?.name - || selectedSplitForSettlement?.user?.email || `User ID: ${selectedSplitForSettlement?.user_id}` + || selectedSplitForSettlement?.user?.email || t('groupDetailPage.expenses.fallbackUserName', { userId: selectedSplitForSettlement?.user_id }) }) }}
{{ t('groupDetailPage.activityLog.emptyState') }}
+{{ t('groupDetailPage.settleShareModal.settleAmountFor', { userName: selectedSplitForSettlement?.user?.name - || selectedSplitForSettlement?.user?.email || t('groupDetailPage.expenses.fallbackUserName', { userId: selectedSplitForSettlement?.user_id }) + || selectedSplitForSettlement?.user?.email || t('groupDetailPage.expenses.fallbackUserName', { + userId: + selectedSplitForSettlement?.user_id + }) }) }}
{{ t('groupDetailPage.choreDetailModal.noHistory') }}
+