Skip to content

[No QA] GPS expense page UI#77865

Merged
AndrewGable merged 15 commits intoExpensify:mainfrom
software-mansion-labs:@GCyganek/gps/gps-screen-ui
Jan 5, 2026
Merged

[No QA] GPS expense page UI#77865
AndrewGable merged 15 commits intoExpensify:mainfrom
software-mansion-labs:@GCyganek/gps/gps-screen-ui

Conversation

@GCyganek
Copy link
Contributor

@GCyganek GCyganek commented Dec 17, 2025

Explanation of Change

Adding UI to the GPS distance request screen with mocked Start/Stop functions to showcase all UI elements added in this PR.

This PR focuses on the UI and location tracking is not added here yet, there is also no location permissions related code which will be added in the next PR

Fixed Issues

$ #77214

Tests

Play with the GPS screen controls and make sure that everything looks like on the design

  • Verify that no errors appear in the JS console

Offline tests

QA Steps

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Screen.Recording.2025-12-19.at.10.56.47.mov
iOS: Native
Screen.Recording.2025-12-19.at.11.02.32.mov

@melvin-bot
Copy link

melvin-bot bot commented Dec 17, 2025

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx ts-node ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx ts-node ./scripts/generateTranslations.ts --compare-ref main

@codecov
Copy link

codecov bot commented Dec 17, 2025

@OSBotify
Copy link
Contributor

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 56e2de4a..1abda644 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1028,15 +1028,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Abonnement',
         domains: 'Domänen',
     },
-    tabSelector: {
-        chat: 'Chat',
-        room: 'Raum',
-        distance: 'Entfernung',
-        manual: 'Manuell',
-        scan: 'Scannen',
-        map: 'Karte',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Chat', room: 'Raum', distance: 'Entfernung', manual: 'Manuell', scan: 'Scannen', map: 'Karte', gps: 'GPS'},
     spreadsheet: {
         upload: 'Eine Tabellenkalkulation hochladen',
         import: 'Tabellenkalkulation importieren',
@@ -2250,7 +2242,7 @@ ${amount} für ${merchant} – ${date}`,
         addApprovalTip: 'Dieser Standard-Workflow gilt für alle Mitglieder, sofern kein spezifischerer Workflow vorhanden ist.',
         approver: 'Genehmiger',
         addApprovalsDescription: 'Zusätzliche Genehmigung anfordern, bevor eine Zahlung autorisiert wird.',
-        makeOrTrackPaymentsTitle: 'Zahlungen erstellen oder verfolgen',
+        makeOrTrackPaymentsTitle: 'Zahlungen erstellen oder nachverfolgen',
         makeOrTrackPaymentsDescription:
             'Fügen Sie eine autorisierte zahlende Person für Zahlungen hinzu, die in Expensify getätigt werden, oder verfolgen Sie Zahlungen, die andernorts vorgenommen wurden.',
         customApprovalWorkflowEnabled:
@@ -7971,6 +7963,30 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
         },
         admins: {title: 'Admins', findAdmin: 'Admin finden'},
     },
+    gps: {
+        tooltip: 'Keine Sorge, du kannst in der Zwischenzeit andere Dinge erledigen. Beende die Verfolgung, sobald du angekommen bist.',
+        disclaimer: 'Nutzen Sie GPS, um eine Ausgabe aus Ihrer Reise zu erstellen. Tippen Sie unten auf „Start“, um mit der Aufzeichnung zu beginnen.',
+        error: {failedToStart: 'Standortverfolgung konnte nicht gestartet werden.', failedToGetPermissions: 'Erforderliche Standortberechtigungen konnten nicht abgerufen werden.'},
+        trackingDistance: 'Entfernung wird erfasst…',
+        stopped: 'Angehalten',
+        start: 'Start',
+        stop: 'Stopp',
+        discard: 'Verwerfen',
+        stopGpsTracking: 'GPS-Tracking stoppen',
+        resumeTracking: 'Fortsetzung der Nachverfolgung',
+        stopGpsTrackingModal: {
+            title: 'GPS-Tracking stoppen?',
+            prompt: 'Möchtest du die Aufzeichnung deines GPS-Standorts wirklich beenden?',
+            cancel: 'Fortsetzung der Nachverfolgung',
+            confirm: 'GPS-Tracking stoppen',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Entfernungserfassung verwerfen?',
+            prompt: 'Möchten Sie diese Entfernungserfassung wirklich verwerfen? Diese Aktion kann nicht rückgängig gemacht werden.',
+            confirm: 'Entfernungserfassung verwerfen',
+        },
+        zeroDistanceTripModal: {title: 'Ausgabe kann nicht erstellt werden', prompt: 'Sie können keine Ausgabe mit demselben Start- und Zielort erstellen.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index a83a8099..d9c92ed5 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1030,15 +1030,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Abonnement',
         domains: 'Domaines',
     },
-    tabSelector: {
-        chat: 'Discussion',
-        room: 'Salle',
-        distance: 'Distance',
-        manual: 'Manuel',
-        scan: 'Scanner',
-        map: 'Carte',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Discussion', room: 'Salle', distance: 'Distance', manual: 'Manuel', scan: 'Scanner', map: 'Carte', gps: 'GPS'},
     spreadsheet: {
         upload: 'Téléverser une feuille de calcul',
         import: 'Importer une feuille de calcul',
@@ -2249,7 +2241,7 @@ ${amount} pour ${merchant} - ${date}`,
         submissionFrequencyDescription: 'Choisissez un planning personnalisé pour soumettre les dépenses.',
         submissionFrequencyDateOfMonth: 'Jour du mois',
         disableApprovalPromptDescription: 'La désactivation des approbations effacera tous les workflows d’approbation existants.',
-        addApprovalsTitle: 'Ajouter des approbations',
+        addApprovalsTitle: 'Ajouter des validations',
         addApprovalButton: 'Ajouter un circuit de validation',
         addApprovalTip: 'Ce flux de travail par défaut s’applique à tous les membres, sauf si un flux de travail plus spécifique existe.',
         approver: 'Approbateur',
@@ -7976,6 +7968,30 @@ Voici un *reçu test* pour vous montrer comment cela fonctionne :`,
         },
         admins: {title: 'Admins', findAdmin: 'Trouver un admin'},
     },
+    gps: {
+        tooltip: 'Ne vous inquiétez pas, vous pouvez faire autre chose en toute sécurité. Arrêtez le suivi une fois arrivé.',
+        disclaimer: 'Utilisez le GPS pour créer une dépense à partir de votre trajet. Touchez Démarrer ci-dessous pour commencer le suivi.',
+        error: {failedToStart: 'Échec du démarrage du suivi de localisation.', failedToGetPermissions: 'Échec de l’obtention des autorisations de localisation requises.'},
+        trackingDistance: 'Suivi de la distance…',
+        stopped: 'Arrêté',
+        start: 'Commencer',
+        stop: 'Arrêter',
+        discard: 'Ignorer',
+        stopGpsTracking: 'Arrêter le suivi GPS',
+        resumeTracking: 'Reprendre le suivi',
+        stopGpsTrackingModal: {
+            title: 'Arrêter le suivi GPS ?',
+            prompt: 'Voulez-vous vraiment arrêter d’enregistrer votre position GPS ?',
+            cancel: 'Reprendre le suivi',
+            confirm: 'Arrêter le suivi GPS',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Abandonner le suivi de distance ?',
+            prompt: 'Voulez-vous vraiment abandonner ce suivi de distance ? Cette action est irréversible.',
+            confirm: 'Ignorer le suivi des distances',
+        },
+        zeroDistanceTripModal: {title: 'Impossible de créer la dépense', prompt: 'Vous ne pouvez pas créer une dépense avec le même lieu de départ et d’arrivée.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/it.ts b/src/languages/it.ts
index c024fff6..8892a62b 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1026,15 +1026,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Abbonamento',
         domains: 'Domini',
     },
-    tabSelector: {
-        chat: 'Chat',
-        room: 'Stanza',
-        distance: 'Distanza',
-        manual: 'Manuale',
-        scan: 'Scannerizza',
-        map: 'Mappa',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Chat', room: 'Stanza', distance: 'Distanza', manual: 'Manuale', scan: 'Scannerizza', map: 'Mappa', gps: 'GPS'},
     spreadsheet: {
         upload: 'Carica un foglio di calcolo',
         import: 'Importa foglio di calcolo',
@@ -2243,7 +2235,7 @@ ${amount} per ${merchant} - ${date}`,
         addApprovalTip: 'Questo flusso di lavoro predefinito si applica a tutti i membri, a meno che non esista un flusso di lavoro più specifico.',
         approver: 'Approvatore',
         addApprovalsDescription: "Richiedi un'approvazione aggiuntiva prima di autorizzare un pagamento.",
-        makeOrTrackPaymentsTitle: 'Effettua o monitora pagamenti',
+        makeOrTrackPaymentsTitle: 'Effettua o tieni traccia dei pagamenti',
         makeOrTrackPaymentsDescription: 'Aggiungi un pagatore autorizzato per i pagamenti effettuati in Expensify o tieni traccia dei pagamenti effettuati altrove.',
         customApprovalWorkflowEnabled:
             '<muted-text-label>Un flusso di approvazione personalizzato è abilitato in questo workspace. Per rivedere o modificare questo flusso di lavoro, contatta il tuo <account-manager-link>Account Manager</account-manager-link> o <concierge-link>Concierge</concierge-link>.</muted-text-label>',
@@ -7952,6 +7944,30 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
         },
         admins: {title: 'Amministratori', findAdmin: 'Trova amministratore'},
     },
+    gps: {
+        tooltip: 'Non preoccuparti, puoi tranquillamente fare altro. Interrompi il tracciamento quando sei arrivato.',
+        disclaimer: 'Usa il GPS per creare una spesa dal tuo viaggio. Tocca Avvia qui sotto per iniziare il tracciamento.',
+        error: {failedToStart: 'Impossibile avviare il tracciamento della posizione.', failedToGetPermissions: 'Impossibile ottenere le autorizzazioni di posizione richieste.'},
+        trackingDistance: 'Tracciamento della distanza...',
+        stopped: 'Interrotto',
+        start: 'Avvia',
+        stop: 'Interrompi',
+        discard: 'Scarta',
+        stopGpsTracking: 'Interrompi tracciamento GPS',
+        resumeTracking: 'Riprendi tracciamento',
+        stopGpsTrackingModal: {
+            title: 'Interrompere il tracciamento GPS?',
+            prompt: 'Sei sicuro di voler interrompere la registrazione della tua posizione GPS?',
+            cancel: 'Riprendi tracciamento',
+            confirm: 'Interrompi tracciamento GPS',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Scartare il tracciamento della distanza?',
+            prompt: 'Sei sicuro di voler eliminare questo tracciamento della distanza? Questa azione non può essere annullata.',
+            confirm: 'Scarta rilevamento distanza',
+        },
+        zeroDistanceTripModal: {title: 'Impossibile creare la spesa', prompt: 'Non puoi creare una spesa con la stessa località di partenza e di arrivo.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index be896219..89416be3 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1026,15 +1026,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'サブスクリプション',
         domains: 'ドメイン',
     },
-    tabSelector: {
-        chat: 'チャット',
-        room: '部屋',
-        distance: '距離',
-        manual: '手動',
-        scan: 'スキャン',
-        map: '地図',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'チャット', room: '部屋', distance: '距離', manual: '手動', scan: 'スキャン', map: '地図', gps: 'GPS'},
     spreadsheet: {
         upload: 'スプレッドシートをアップロード',
         import: 'スプレッドシートをインポート',
@@ -2236,7 +2228,7 @@ ${merchant} への ${amount}(${date})`,
         addApprovalTip: 'より詳細なワークフローが存在しない限り、このデフォルトのワークフローはすべてのメンバーに適用されます。',
         approver: '承認者',
         addApprovalsDescription: '支払いを承認する前に、追加の承認を要求する。',
-        makeOrTrackPaymentsTitle: '支払いの作成や追跡を行う',
+        makeOrTrackPaymentsTitle: '支払いを行う・追跡する',
         makeOrTrackPaymentsDescription: 'Expensify で行われる支払い用の承認済み支払者を追加するか、他で行われた支払いを追跡します。',
         customApprovalWorkflowEnabled:
             '<muted-text-label>このワークスペースではカスタム承認ワークフローが有効になっています。このワークフローを確認または変更するには、<account-manager-link>アカウントマネージャー</account-manager-link>または<concierge-link>Concierge</concierge-link>までご連絡ください。</muted-text-label>',
@@ -7891,6 +7883,25 @@ Expensify の使い方をお見せするための*テストレシート*がこ
         },
         admins: {title: '管理者', findAdmin: '管理者を検索'},
     },
+    gps: {
+        tooltip: '心配いりません、他の作業をしていても問題ありません。到着したら追跡を停止してください。',
+        disclaimer: '移動中の位置情報を使用して経費を作成しましょう。追跡を開始するには、下の「開始」をタップしてください。',
+        error: {failedToStart: '位置情報の追跡を開始できませんでした。', failedToGetPermissions: '必要な位置情報の権限を取得できませんでした。'},
+        trackingDistance: '距離を追跡中...',
+        stopped: '停止',
+        start: '開始',
+        stop: '停止',
+        discard: '破棄',
+        stopGpsTracking: 'GPS追跡を停止',
+        resumeTracking: '追跡を再開',
+        stopGpsTrackingModal: {title: 'GPS追跡を停止しますか?', prompt: 'GPS位置情報の記録を停止しますか?', cancel: '追跡を再開', confirm: 'GPS追跡を停止'},
+        discardDistanceTrackingModal: {
+            title: '距離の追跡を破棄しますか?',
+            prompt: 'この距離トラッキングを破棄してもよろしいですか?この操作は元に戻せません。',
+            confirm: '距離の追跡を破棄',
+        },
+        zeroDistanceTripModal: {title: '経費を作成できません', prompt: '開始地点と終了地点が同じ経路では経費を作成できません。'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 6b7ad0ba..3a3bf2e1 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1026,15 +1026,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Abonnement',
         domains: 'Domeinen',
     },
-    tabSelector: {
-        chat: 'Chat',
-        room: 'Kamer',
-        distance: 'Afstand',
-        manual: 'Handmatig',
-        scan: 'Scannen',
-        map: 'Kaart',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Chat', room: 'Kamer', distance: 'Afstand', manual: 'Handmatig', scan: 'Scannen', map: 'Kaart', gps: 'GPS'},
     spreadsheet: {
         upload: 'Een spreadsheet uploaden',
         import: 'Spreadsheet importeren',
@@ -2241,7 +2233,7 @@ ${amount} voor ${merchant} - ${date}`,
         addApprovalTip: 'Deze standaardworkflow is van toepassing op alle leden, tenzij er een specifiekere workflow bestaat.',
         approver: 'Fiatteur',
         addApprovalsDescription: 'Extra goedkeuring vereisen voordat een betaling wordt geautoriseerd.',
-        makeOrTrackPaymentsTitle: 'Betalingen uitvoeren of volgen',
+        makeOrTrackPaymentsTitle: 'Betalingen doen of volgen',
         makeOrTrackPaymentsDescription: 'Voeg een gemachtigde betaler toe voor betalingen die in Expensify worden gedaan of houd betalingen bij die elders worden gedaan.',
         customApprovalWorkflowEnabled:
             '<muted-text-label>Er is een aangepast goedkeuringsworkflow ingeschakeld voor deze workspace. Neem contact op met uw <account-manager-link>Account Manager</account-manager-link> of <concierge-link>Concierge</concierge-link> om deze workflow te bekijken of te wijzigen.</muted-text-label>',
@@ -7938,6 +7930,30 @@ Hier is een *testbon* om je te laten zien hoe het werkt:`,
         },
         admins: {title: 'Beheerders', findAdmin: 'Beheerder zoeken'},
     },
+    gps: {
+        tooltip: 'Maak je geen zorgen, je kunt gerust andere dingen doen. Stop de tracking zodra je bent aangekomen.',
+        disclaimer: 'Gebruik GPS om een uitgave van je reis aan te maken. Tik hieronder op Start om met het bijhouden te beginnen.',
+        error: {failedToStart: 'Locatiebepaling starten is mislukt.', failedToGetPermissions: 'Het verkrijgen van de vereiste locatierechten is mislukt.'},
+        trackingDistance: 'Afstand bijhouden...',
+        stopped: 'Gestopt',
+        start: 'Start',
+        stop: 'Stop',
+        discard: 'Verwerpen',
+        stopGpsTracking: 'GPS-tracking stoppen',
+        resumeTracking: 'Tracking hervatten',
+        stopGpsTrackingModal: {
+            title: 'GPS-tracking stoppen?',
+            prompt: 'Weet je zeker dat je wilt stoppen met het opnemen van je gps-locatie?',
+            cancel: 'Tracking hervatten',
+            confirm: 'GPS-tracking stoppen',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Afstandstracking negeren?',
+            prompt: 'Weet je zeker dat je deze afstandsregistratie wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.',
+            confirm: 'Afstandsregistratie negeren',
+        },
+        zeroDistanceTripModal: {title: 'Kan onkost niet maken', prompt: 'Je kunt geen uitgave maken met dezelfde begin- en eindlocatie.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 8e97e27c..370b35ef 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1026,15 +1026,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Subskrypcja',
         domains: 'Domeny',
     },
-    tabSelector: {
-        chat: 'Czat',
-        room: 'Pokój',
-        distance: 'Dystans',
-        manual: 'Ręczny',
-        scan: 'Skanuj',
-        map: 'Mapa',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Czat', room: 'Pokój', distance: 'Dystans', manual: 'Ręczny', scan: 'Skanuj', map: 'Mapa', gps: 'GPS'},
     spreadsheet: {
         upload: 'Prześlij arkusz kalkulacyjny',
         import: 'Importuj arkusz kalkulacyjny',
@@ -2229,7 +2221,7 @@ ${amount} dla ${merchant} - ${date}`,
     workflowsPage: {
         workflowTitle: 'Wydatki',
         workflowDescription: 'Skonfiguruj proces od momentu poniesienia wydatku, obejmujący akceptację i płatność.',
-        submissionFrequency: 'Częstotliwość zgłoszeń',
+        submissionFrequency: 'Częstotliwość przesyłania',
         submissionFrequencyDescription: 'Wybierz niestandardowy harmonogram przesyłania wydatków.',
         submissionFrequencyDateOfMonth: 'Dzień miesiąca',
         disableApprovalPromptDescription: 'Wyłączenie zatwierdzeń usunie wszystkie istniejące przepływy pracy zatwierdzania.',
@@ -7923,6 +7915,30 @@ Oto *paragon testowy*, który pokazuje, jak to działa:`,
         },
         admins: {title: 'Administratorzy', findAdmin: 'Znajdź administratora'},
     },
+    gps: {
+        tooltip: 'Nie martw się, możesz spokojnie zajmować się innymi rzeczami. Zatrzymaj śledzenie, gdy dotrzesz na miejsce.',
+        disclaimer: 'Użyj GPS, aby utworzyć wydatek z podróży. Stuknij przycisk Start poniżej, aby rozpocząć śledzenie.',
+        error: {failedToStart: 'Nie udało się uruchomić śledzenia lokalizacji.', failedToGetPermissions: 'Nie udało się uzyskać wymaganych uprawnień lokalizacji.'},
+        trackingDistance: 'Śledzenie dystansu...',
+        stopped: 'Zatrzymano',
+        start: 'Start',
+        stop: 'Zatrzymaj',
+        discard: 'Odrzuć',
+        stopGpsTracking: 'Zatrzymaj śledzenie GPS',
+        resumeTracking: 'Wznów śledzenie',
+        stopGpsTrackingModal: {
+            title: 'Zatrzymać śledzenie GPS?',
+            prompt: 'Czy na pewno chcesz zatrzymać nagrywanie swojej lokalizacji GPS?',
+            cancel: 'Wznów śledzenie',
+            confirm: 'Zatrzymaj śledzenie GPS',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Odrzucić śledzenie dystansu?',
+            prompt: 'Czy na pewno chcesz odrzucić to śledzenie dystansu? Tej czynności nie można cofnąć.',
+            confirm: 'Odrzuć śledzenie dystansu',
+        },
+        zeroDistanceTripModal: {title: 'Nie można utworzyć wydatku', prompt: 'Nie możesz utworzyć wydatku z taką samą lokalizacją początkową i końcową.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 99b2466c..a139b230 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -1025,15 +1025,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: 'Assinatura',
         domains: 'Domínios',
     },
-    tabSelector: {
-        chat: 'Chat',
-        room: 'Sala',
-        distance: 'Distância',
-        manual: 'Manual',
-        scan: 'Escanear',
-        map: 'Mapa',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: 'Chat', room: 'Sala', distance: 'Distância', manual: 'Manual', scan: 'Escanear', map: 'Mapa', gps: 'GPS'},
     spreadsheet: {
         upload: 'Enviar uma planilha',
         import: 'Importar planilha',
@@ -7930,6 +7922,30 @@ Aqui está um *recibo de teste* para mostrar como funciona:`,
         },
         admins: {title: 'Administradores', findAdmin: 'Encontrar administrador'},
     },
+    gps: {
+        tooltip: 'Não se preocupe, você pode fazer outras coisas com segurança. Pare o rastreamento quando chegar.',
+        disclaimer: 'Use o GPS para criar uma despesa da sua jornada. Toque em Iniciar abaixo para começar a rastrear.',
+        error: {failedToStart: 'Falha ao iniciar o rastreamento de localização.', failedToGetPermissions: 'Falha ao obter as permissões de localização necessárias.'},
+        trackingDistance: 'Rastreando distância...',
+        stopped: 'Parado',
+        start: 'Iniciar',
+        stop: 'Parar',
+        discard: 'Descartar',
+        stopGpsTracking: 'Parar rastreamento de GPS',
+        resumeTracking: 'Retomar rastreamento',
+        stopGpsTrackingModal: {
+            title: 'Parar o rastreamento por GPS?',
+            prompt: 'Tem certeza de que deseja parar de registrar sua localização por GPS?',
+            cancel: 'Retomar rastreamento',
+            confirm: 'Parar rastreamento de GPS',
+        },
+        discardDistanceTrackingModal: {
+            title: 'Descartar o rastreamento de distância?',
+            prompt: 'Tem certeza de que deseja descartar esse rastreamento de distância? Essa ação não pode ser desfeita.',
+            confirm: 'Descartar rastreamento de distância',
+        },
+        zeroDistanceTripModal: {title: 'Não é possível criar despesa', prompt: 'Você não pode criar uma despesa com o mesmo local de início e de término.'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 840e0297..a3821cde 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1014,15 +1014,7 @@ const translations: TranslationDeepObject<typeof en> = {
         subscription: '订阅',
         domains: '域名',
     },
-    tabSelector: {
-        chat: '聊天',
-        room: '房间',
-        distance: '距离',
-        manual: '手动',
-        scan: '扫描',
-        map: '地图',
-        gps: 'GPS',
-    },
+    tabSelector: {chat: '聊天', room: '房间', distance: '距离', manual: '手动', scan: '扫描', map: '地图', gps: 'GPS'},
     spreadsheet: {
         upload: '上传电子表格',
         import: '导入电子表格',
@@ -2207,7 +2199,7 @@ ${amount},商户:${merchant} - ${date}`,
         addApprovalTip: '此默认工作流程适用于所有成员,除非存在更具体的工作流程。',
         approver: '审批人',
         addApprovalsDescription: '在授权付款前需要额外审批。',
-        makeOrTrackPaymentsTitle: '发起或跟踪付款',
+        makeOrTrackPaymentsTitle: '进行或跟踪付款',
         makeOrTrackPaymentsDescription: '为在 Expensify 中进行的付款添加授权付款人,或跟踪在其他地方进行的付款。',
         customApprovalWorkflowEnabled:
             '<muted-text-label>此工作区已启用自定义审批流程。要查看或更改此流程,请联系您的<account-manager-link>客户经理</account-manager-link>或<concierge-link>Concierge</concierge-link>。</muted-text-label>',
@@ -7756,6 +7748,21 @@ ${reportName}
         enhancedSecurity: {title: '增强的安全性', subtitle: '要求您域内的成员使用单点登录登录、限制工作区创建等。', enable: '启用'},
         admins: {title: '管理员', findAdmin: '查找管理员'},
     },
+    gps: {
+        tooltip: '别担心,你可以放心去做其他事情。到达后再停止跟踪即可。',
+        disclaimer: '使用 GPS 根据您的行程创建一笔费用。点击下方的“开始”以开始跟踪。',
+        error: {failedToStart: '启动位置跟踪失败。', failedToGetPermissions: '获取所需的定位权限失败。'},
+        trackingDistance: '正在跟踪距离…',
+        stopped: '已停止',
+        start: '开始',
+        stop: '停止',
+        discard: '丢弃',
+        stopGpsTracking: '停止 GPS 跟踪',
+        resumeTracking: '恢复跟踪',
+        stopGpsTrackingModal: {title: '停止 GPS 跟踪?', prompt: '确定要停止记录您的 GPS 位置吗?', cancel: '恢复跟踪', confirm: '停止 GPS 跟踪'},
+        discardDistanceTrackingModal: {title: '放弃距离跟踪?', prompt: '确定要放弃此次距离追踪吗?此操作无法撤销。', confirm: '丢弃距离跟踪'},
+        zeroDistanceTripModal: {title: '无法创建报销', prompt: '您不能创建起点和终点相同的报销。'},
+    },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,
 // so if you change it here, please update it there as well.

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

@GCyganek GCyganek marked this pull request as ready for review December 18, 2025 09:46
@GCyganek GCyganek requested review from a team as code owners December 18, 2025 09:46
@melvin-bot melvin-bot bot requested review from inimaga and trjExpensify and removed request for a team December 18, 2025 09:46
@melvin-bot
Copy link

melvin-bot bot commented Dec 18, 2025

@inimaga Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@GCyganek
Copy link
Contributor Author

@inimaga @trjExpensify please ignore, this PR will be reviewed by @dukenv0307 and @AndrewGable

@dannymcclain
Copy link
Contributor

This is looking pretty sweet so far. A couple small things:

Is there anything we can do to avoid this little jumping in the UI? Maybe a transition? Maybe adding some eyebrow text above the distance before you've started tracking?

CleanShot.2025-12-18.at.08.53.59.mp4

This educational tooltip looks off to me. The spacing, color, and border radius all look a little different than what I'd expect:

This PR Figma
CleanShot 2025-12-18 at 08 54 39@2x CleanShot 2025-12-18 at 08 58 10@2x
(No worries about the different button color in the mock - we changed that after we made these mocks)

@dubielzyk-expensify for 👀 too

@inimaga inimaga requested review from AndrewGable and dukenv0307 and removed request for inimaga December 18, 2025 14:33
@dubielzyk-expensify
Copy link
Contributor

dubielzyk-expensify commented Dec 18, 2025

Good spots, Danny.

This educational tooltip looks off to me. The spacing, color, and border radius all look a little different than what I'd expect:

Definitely looks off. We've already used this tooltip component in the app before so we should just reuse that

The color of the button changed last minute so that's on me 👍 But otherwise that's the case. Might wanna run a test build when we're ready too.

@dubielzyk-expensify
Copy link
Contributor

The idea on the jumping is that the tracking distance number always stays in the same spot

@dannymcclain
Copy link
Contributor

The idea on the jumping is that the tracking distance number always stays in the same spot

Yes exactly. Thank you for clarifying, I should've been more clear.

@GCyganek
Copy link
Contributor Author

I can't see the GPS option in track distance. Do I need to be part of a beta or something?

Oh yes, you should be added first to be able to test. @AndrewGable could add Jon to the GPS beta?

@trjExpensify
Copy link
Contributor

@dubielzyk-expensify if you use an expensifail.com account, it'll be on the beta as any account on that domain gets access to all betas.

@dannymcclain
Copy link
Contributor

Styles for me are looking pretty good! A couple things I noticed:

  • The map shows my correct location, but when I use the GPS it puts me in San Francisco?
  • The education tooltip is showing every time I start tracking, instead of just the first time
  • Would it be madness to let the user edit the start/stop location when they stop tracking but before they click Next to create the expense? I know that's kind of counter to the whole "use the GPS data" but depending on the fidelity of the GPS tracking, I could see how short trips might end up being super frustrating if the app didn't pick up my location change exactly (this might be a moot point if there's something not quite right with gathering my GPS data in that build)
  • I can't get past the stop screen so I can't really test the rest of the flow right now 🤷

Here's a video I recorded—it's kinda all over the place but it at least illustrates some of my comments above:

ScreenRecording_12-19-2025.09-30-38_1.MP4

@GCyganek
Copy link
Contributor Author

GCyganek commented Dec 19, 2025

Styles for me are looking pretty good! A couple things I noticed:

  • The map shows my correct location, but when I use the GPS it puts me in San Francisco?
  • The education tooltip is showing every time I start tracking, instead of just the first time
  • Would it be madness to let the user edit the start/stop location when they stop tracking but before they click Next to create the expense? I know that's kind of counter to the whole "use the GPS data" but depending on the fidelity of the GPS tracking, I could see how short trips might end up being super frustrating if the app didn't pick up my location change exactly (this might be a moot point if there's something not quite right with gathering my GPS data in that build)
  • I can't get past the stop screen so I can't really test the rest of the flow right now 🤷

Here's a video I recorded—it's kinda all over the place but it at least illustrates some of my comments above:

ScreenRecording_12-19-2025.09-30-38_1.MP4

As I mentioned in the description, this PR only adds the UI elements that will be used in the GPS location tracking, but the location tracking is not in scope of this PR and Start/Stop buttons only simulate UI changes. So to answer points 1 & 4, this will work, but not yet, the code is ready, but we want to merge changes step by step to not miss anything and to make it easier to review.

As for the 2nd point, the plan is to stop showing it when user finishes the trip and successfully creates an expense based on the captured GPS data. So it will not appear anymore after that. Additionally, when you click X button the tooltip closes and won't appear unless you leave the Track distance screen and then go back to the GPS screen and start the trip (unless you successfully create a GPS expense in the meantime). Of course I'm open to suggestions on when we should be displaying the tooltip

As for the 3rd point I think you, other designers and @AndrewGable can discuss it, although I think that for the feature parity we will not want to add this option at least until we have the whole feature working as we planned in the design doc

@dannymcclain
Copy link
Contributor

Perfect—that all sounds great. And yes, let's just ignore my comment in point 3. Also, super sorry about not reading the PR well enough to realize that it is actually working as intended!! 🙇 #nobody-reads

@GCyganek
Copy link
Contributor Author

Perfect—that all sounds great. And yes, let's just ignore my comment in point 3. Also, super sorry about not reading the PR well enough to realize that it is actually working as intended!! 🙇 #nobody-reads

No problem, it happens! Glad everything is looking good now 🫡


function Disclaimer() {
const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {canBeMissing: true});
const icons = useMemoizedLazyExpensifyIcons(['Crosshair'] as const);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const icons = useMemoizedLazyExpensifyIcons(['Crosshair'] as const);
const icons = useMemoizedLazyExpensifyIcons(['Crosshair']);

const {windowWidth} = useWindowDimensions();
const theme = useTheme();
const {translate} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['Close', 'Lightbulb'] as const);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const icons = useMemoizedLazyExpensifyIcons(['Close', 'Lightbulb'] as const);
const icons = useMemoizedLazyExpensifyIcons(['Close', 'Lightbulb']);

@dukenv0307
Copy link
Contributor

Left some minor comments. The rest looks good

@melvin-bot
Copy link

melvin-bot bot commented Dec 22, 2025

🎯 @dukenv0307, thanks for reviewing and testing this PR! 🎉

An E/App issue has been created to issue payment here: #78290.

@dubielzyk-expensify
Copy link
Contributor

I had the same reaction as you for 3rd point, Danny. I also found it weird you couldn't edit, but saw that OldDot was the same so went with that.

(Sidenote and nothing to action: When testing and using it now everything looks pretty great. It does make me want the pause on stop and not another dialog. It feels pretty dialog heavy, but that's again by design at the moment)

@dubielzyk-expensify
Copy link
Contributor

There's some spacing issues on the start/stop. The intention here was that the spacing around and between start/stop should basically just be the same as it is on the Map screen but without the reorder icon and chevron:
CleanShot 2025-12-29 at 13 51 42@2x

You can see when we put it side by side that they look quite tight on the GPS screen:

CleanShot 2025-12-29 at 13 54 56@2x

The spacing in Figma is probably not fully correct either, but let's just rip off the spacing as much as we can. cc @dannymcclain for thoughts.

@GCyganek
Copy link
Contributor Author

GCyganek commented Jan 5, 2026

@dubielzyk-expensify yes, I was following the spacing from Figma. I've updated the spacing around and between the waypoints:

Screenshot 2026-01-05 at 11 23 27

It's now 24px around and 24px between the waypoints as on the Map screen

@dannymcclain
Copy link
Contributor

let's just rip off the spacing as much as we can

Totally agree, great shout.

@AndrewGable
Copy link
Contributor

@Expensify/design is this good to go? I think yes, but just wanted to confirm.

@dannymcclain
Copy link
Contributor

I think so, as long as @dubielzyk-expensify is happy with the updated spacing!

@AndrewGable
Copy link
Contributor

Ok great - Let's get this on staging and then we can double check things there and adjust if needed!

@AndrewGable AndrewGable merged commit 7326577 into Expensify:main Jan 5, 2026
31 checks passed
@OSBotify
Copy link
Contributor

OSBotify commented Jan 5, 2026

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@kavimuru
Copy link

kavimuru commented Jan 6, 2026

@GCyganek could you share the QA steps?

@OSBotify
Copy link
Contributor

OSBotify commented Jan 6, 2026

🚀 Deployed to staging by https://github.com/AndrewGable in version: 9.2.94-0 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

abzokhattab pushed a commit to abzokhattab/App that referenced this pull request Jan 6, 2026
…ek/gps/gps-screen-ui

GPS expense page UI
@arosiclair arosiclair changed the title GPS expense page UI [No QA] GPS expense page UI Jan 6, 2026
@OSBotify
Copy link
Contributor

OSBotify commented Jan 7, 2026

🚀 Deployed to production by https://github.com/arosiclair in version: 9.2.94-4 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants