diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx
index 8567028..3db695c 100644
--- a/src/app/_layout.tsx
+++ b/src/app/_layout.tsx
@@ -197,6 +197,32 @@ function RootLayoutContent() {
title: "Open-source licenses",
}}
/>
+
+
+ {t`Send feedback`}
+
+ router.back()}
+ />
+
+
+
+ router.back()} />
+
+
+ >
+ );
+}
+
+const styles = StyleSheet.create({
+ sheet: { flex: 1 },
+});
diff --git a/src/app/settings/index.tsx b/src/app/settings/index.tsx
index 875a9e5..4bb505e 100644
--- a/src/app/settings/index.tsx
+++ b/src/app/settings/index.tsx
@@ -125,6 +125,12 @@ export default function SettingsScreen() {
+ router.navigate("/settings/feedback")}
+ />
+
+
+ {form.submitted ? (
+ <>
+
+ {t`Thank you for your feedback`}
+
+
+ {t`Your feedback was submitted successfully.`}
+
+
+ >
+ ) : (
+ <>
+ {showCloseButton ? (
+
+ {t`Send feedback`}
+
+
+
+
+
+ ) : null}
+
+ {t`Tell us what worked well or what we can improve.`}
+
+
+ {t`Feedback type`}
+
+ {form.feedbackTypes.map((option) => (
+ form.setFeedbackType(option.value)}
+ selected={option.value === form.feedbackType}
+ >
+
+
+ {option.label}
+
+
+
+ ))}
+
+
+
+ {t`Add your name and email if you would like us to contact you with follow-up questions or updates about your feedback.`}
+
+
+
+ {t`Name (optional)`}
+
+
+ {
+ if (!focused) form.validateCurrentEmail();
+ }}
+ onValueChange={form.changeEmail}
+ singleLine
+ >
+
+ {t`Email (optional)`}
+
+ {form.emailError ? (
+
+ {form.emailError}
+
+ ) : null}
+
+
+
+ {t`What would you like us to know?`}
+
+ {form.messageError || form.submissionError ? (
+
+ {form.messageError ?? form.submissionError}
+
+ ) : null}
+
+
+ {t`Your message and optional contact details will be sent to eduMFA through Sentry.`}
+
+
+ >
+ )}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ host: {
+ flex: 1,
+ },
+});
diff --git a/src/components/feedback-form.d.ts b/src/components/feedback-form.d.ts
new file mode 100644
index 0000000..7c2622d
--- /dev/null
+++ b/src/components/feedback-form.d.ts
@@ -0,0 +1,5 @@
+import type { FeedbackFormProps } from "@/components/feedback-form.types";
+
+export declare function FeedbackForm(
+ props: FeedbackFormProps,
+): React.JSX.Element;
diff --git a/src/components/feedback-form.ios.tsx b/src/components/feedback-form.ios.tsx
new file mode 100644
index 0000000..463b94f
--- /dev/null
+++ b/src/components/feedback-form.ios.tsx
@@ -0,0 +1,185 @@
+import type { FeedbackFormProps } from "@/components/feedback-form.types";
+import { Spacing } from "@/constants/theme";
+import { useFeedbackForm } from "@/hooks/use-feedback-form";
+import {
+ Button,
+ Form,
+ Host,
+ HStack,
+ Picker,
+ Section,
+ Spacer,
+ Text,
+ TextField,
+ VStack,
+} from "@expo/ui/swift-ui";
+import {
+ buttonStyle,
+ controlSize,
+ font,
+ foregroundStyle,
+ frame,
+ keyboardType,
+ lineLimit,
+ listRowBackground,
+ listRowInsets,
+ pickerStyle,
+ scrollDismissesKeyboard,
+ tag,
+ textContentType,
+} from "@expo/ui/swift-ui/modifiers";
+import { useLingui } from "@lingui/react/macro";
+import { StyleSheet } from "react-native";
+
+const ERROR_MODIFIERS = [foregroundStyle("#FF3B30")];
+
+export function FeedbackForm({
+ onClose,
+ showCloseButton = false,
+}: FeedbackFormProps) {
+ const { t } = useLingui();
+ const form = useFeedbackForm();
+
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ host: {
+ flex: 1,
+ },
+});
diff --git a/src/components/feedback-form.types.ts b/src/components/feedback-form.types.ts
new file mode 100644
index 0000000..3a57eb7
--- /dev/null
+++ b/src/components/feedback-form.types.ts
@@ -0,0 +1,4 @@
+export type FeedbackFormProps = {
+ onClose: () => void;
+ showCloseButton?: boolean;
+};
diff --git a/src/hooks/use-feedback-form.ts b/src/hooks/use-feedback-form.ts
new file mode 100644
index 0000000..050179f
--- /dev/null
+++ b/src/hooks/use-feedback-form.ts
@@ -0,0 +1,88 @@
+import { submitUserFeedback, type UserFeedbackType } from "@/utils/sentry";
+import { useLingui } from "@lingui/react/macro";
+import { useState } from "react";
+
+const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+export function useFeedbackForm() {
+ const { t } = useLingui();
+ const [email, setEmail] = useState("");
+ const [emailError, setEmailError] = useState(null);
+ const [feedbackType, setFeedbackType] = useState("general");
+ const [message, setMessage] = useState("");
+ const [messageError, setMessageError] = useState(null);
+ const [name, setName] = useState("");
+ const [submissionError, setSubmissionError] = useState(null);
+ const [submitted, setSubmitted] = useState(false);
+ const feedbackTypes: readonly {
+ label: string;
+ value: UserFeedbackType;
+ }[] = [
+ { label: t`Bug report`, value: "bug_report" },
+ { label: t`General`, value: "general" },
+ { label: t`Feature request`, value: "feature_request" },
+ ];
+
+ const validateEmail = (value: string): string | null => {
+ const trimmedEmail = value.trim();
+ return trimmedEmail && !EMAIL_PATTERN.test(trimmedEmail)
+ ? t`Enter a valid email address.`
+ : null;
+ };
+
+ const changeEmail = (value: string) => {
+ setEmail(value);
+ if (emailError) setEmailError(null);
+ };
+
+ const changeMessage = (value: string) => {
+ setMessage(value);
+ if (messageError) setMessageError(null);
+ };
+
+ const validateCurrentEmail = () => setEmailError(validateEmail(email));
+
+ const submit = () => {
+ const trimmedMessage = message.trim();
+ const nextEmailError = validateEmail(email);
+ const nextMessageError = trimmedMessage
+ ? null
+ : t`Please describe your feedback.`;
+
+ setEmailError(nextEmailError);
+ setMessageError(nextMessageError);
+ setSubmissionError(null);
+
+ if (nextEmailError || nextMessageError) return;
+
+ try {
+ submitUserFeedback(
+ {
+ email: email.trim() || undefined,
+ message: trimmedMessage,
+ name: name.trim() || undefined,
+ },
+ feedbackType,
+ );
+ setSubmitted(true);
+ } catch (error) {
+ if (__DEV__) console.warn("Failed to submit feedback:", error);
+ setSubmissionError(t`Feedback could not be sent. Please try again.`);
+ }
+ };
+
+ return {
+ changeEmail,
+ changeMessage,
+ emailError,
+ feedbackType,
+ feedbackTypes,
+ messageError,
+ setFeedbackType,
+ setName,
+ submissionError,
+ submit,
+ submitted,
+ validateCurrentEmail,
+ };
+}
diff --git a/src/locales/de/messages.js b/src/locales/de/messages.js
index d511075..ad0f50f 100644
--- a/src/locales/de/messages.js
+++ b/src/locales/de/messages.js
@@ -1 +1 @@
-/*eslint-disable*/module.exports={messages:JSON.parse("{\"-o5oci\":[\"eduMFA verwendet \",[\"licenseCount\"],\" Open-Source-Pakete. Die Paketdetails werden aus den installierten Produktionsabhängigkeiten generiert.\"],\"0DlQl0\":[\"Noch keine Token vorhanden\"],\"0EvA_s\":[\"Möchtest du diesen Token wirklich löschen?\"],\"0VZTWA\":[\"Genehmige die Anmeldung nur, wenn du sie selbst gestartet hast. Lehne sie im Zweifel ab.\"],\"0_bbfl\":[\"Bewahre deine Authentifizierungs-Token an einem Ort auf und genehmige Anmeldungen sicher von diesem Gerät aus.\"],\"1QfxQT\":[\"Schließen\"],\"1njn7W\":[\"Hell\"],\"2b4lV4\":[\"Aktualisierung fehlgeschlagen\"],\"3OQxEV\":[\"Servernachricht\"],\"47yaVS\":[\"Google Play-Dienste sind nicht verfügbar, daher können Anfragen nicht automatisch eingehen. Ziehe die Token-Liste nach unten, um nach ausstehenden Anfragen zu suchen.\"],\"4aqCBZ\":[\"Keine Anmeldeanfrage verpassen\"],\"4xUhXP\":[\"Benachrichtigungseinstellungen öffnen\"],\"6jfS51\":[\"Willkommen\"],\"6mYeNy\":[\"Serverregistrierung fehlgeschlagen\"],\"79FlZ7\":[\"Benachrichtigungen sind aktiviert\"],\"7GD6En\":[\"Anmeldeanfragen können unbemerkt bleiben\"],\"7yOnZx\":[\"Das Gerät konnte das für diesen Token erforderliche RSA-Schlüsselpaar nicht erstellen. Versuche den Rollout erneut und lasse die App währenddessen geöffnet.\"],\"858Cvj\":[\"Der QR-Code wird von dieser App nicht unterstützt. Bitte verwenden Sie eine allgemeine Authenticator-App.\"],\"87a_t_\":[\"Bezeichnung\"],\"8DuEDH\":[\"Noch keine Interaktionen\"],\"BrFR00\":[\"Registrierung wird abgeschlossen\"],\"CsH7i4\":[\"Anonymisierte Absturz- und Fehlerdiagnosen senden\"],\"DEKI9D\":[\"Projektwebsite\"],\"Dnn2XG\":[\"Automatisch\"],\"DvOF3m\":[\"Teile Absturz- und Fehlerberichte, um die Zuverlässigkeit zu verbessern. Sie enthalten niemals Token-Geheimnisse, Passwörter oder Namen von Einrichtungen.\"],\"DyF_C_\":[\"Lizenzinformationen sind nicht verfügbar.\"],\"EAyh56\":[\"Anmeldung prüfen\"],\"EBRcBj\":[\"Haptik\"],\"ET7frA\":[\"Kamerazugriff erlauben\"],\"EfBEmE\":[\"Rollout läuft...\"],\"F37c1s\":[\"Einstellungen öffnen\"],\"FEr96N\":[\"Erscheinungsbild\"],\"FSVCou\":[\"Nach unten ziehen, um Anfragen abzurufen\"],\"G0TPBU\":[\"QR-Code-Scanner\"],\"I5kL4f\":[\"Aktivitätsprotokoll\"],\"I92Z-b\":[\"Benachrichtigungen aktivieren\"],\"IPFr7d\":[\"Wähle, ob du anonymisierte Absturz- und Fehlerberichte teilen möchtest.\"],\"IRRc6m\":[\"Anonyme Berichte teilen\"],\"IT-f_-\":[\"Schlüssel werden generiert\"],\"Ijsgym\":[\"Token löschen\"],\"JG7Ls-\":[\"Aktiviere den Kamerazugriff in den Einstellungen, um QR-Codes zu scannen.\"],\"Lu4tTO\":[\"Platziere den QR-Code im Rahmen\"],\"Mi0UmV\":[\"Registrierungsantwort fehlgeschlagen\"],\"NkQliA\":[\"Du kannst jetzt Anmeldeanfragen empfangen und freigeben.\"],\"NsiA8m\":[\"Rollout fehlgeschlagen\"],\"O7obZD\":[\"Dieses Token konnte nicht aktualisiert werden, weil der Push-Dienst nicht erreichbar war. Prüfe deine Verbindung oder versuche es später erneut.\"],\"On0aF2\":[\"Webseite\"],\"OugAQq\":[\"QR-Code hochladen\"],\"PBxg_E\":[\"Nicht jetzt\"],\"PvN-FE\":[\"Die Token-Version wird von dieser App nicht unterstützt. Bitte aktualisiere die App auf die neueste Version oder kontaktiere deine Institution für Unterstützung.\"],\"Qm1NmK\":[\"ODER\"],\"QmIEhK\":[\"Authenticator-Apps suchen\"],\"R8Gqnt\":[\"Open-Source-Lizenzen\"],\"ROheaS\":[\"Version \",[\"itemVersion\"],\" · \",[\"itemLicense\"]],\"RkXlPZ\":[\"GitHub\"],\"Ro1gox\":[\"Token bearbeiten\"],\"SWOmlM\":[\"Aktualisiere den Anzeigenamen für diesen Token.\"],\"ScJ9fj\":[\"Datenschutzerklärung\"],\"TP9_K5\":[\"Token\"],\"TfpFBG\":[\"Deine Wahl\"],\"TqCsD2\":[[\"totalPending\"],\" Anfragen warten. Prüfe jede einzeln.\"],\"Tz0i8g\":[\"Einstellungen\"],\"UMeccq\":[\"Schlüsselgenerierung fehlgeschlagen\"],\"UO-3EH\":[\"Dieses Token hat die Registrierung nicht abgeschlossen und kann keine Push-Anfragen empfangen, bis der Rollout erfolgreich ist.\"],\"URmyfc\":[\"Details\"],\"UbRKMZ\":[\"Ausstehend\"],\"VIZNLO\":[\"Öffentlicher Schlüssel wird registriert\"],\"WRdUP2\":[\"Kamerazugriff ist erforderlich, um den QR-Code zu scannen und einen Token zu koppeln.\"],\"Weq9zb\":[\"Allgemein\"],\"Z2CsKg\":[\"Anonyme Berichte nicht teilen\"],\"Z7ZXbT\":[\"Freigeben\"],\"ZDIydz\":[\"Loslegen\"],\"ZYIECH\":[\"Benachrichtigungen sind deaktiviert\"],\"_ijUgq\":[\"Lass dich benachrichtigen, wenn eine Anmeldung deine Genehmigung erfordert.\"],\"aAIQg2\":[\"Darstellung\"],\"b75KIN\":[\"App-Sprache in den Systemeinstellungen ändern\"],\"bfECx6\":[\"Dieses Token konnte nicht beim Registrierungsserver registriert werden. Prüfe deine Verbindung oder versuche es später erneut.\"],\"c7uz-G\":[\"Aktiviere Benachrichtigungen in den Einstellungen, damit du reagieren kannst, sobald eine Anfrage eingeht.\"],\"cFCKYZ\":[\"Ablehnen\"],\"cnGeoo\":[\"Löschen\"],\"cu6w0K\":[\"Token-Genehmigungen, Ablehnungen, Aktualisierungsereignisse und Anmeldeaktivitäten werden hier angezeigt.\"],\"dEgA5A\":[\"Abbrechen\"],\"dN-1Th\":[\"Öffnen mit deiner Authenticator-App\"],\"dum6Mb\":[\"Aktualisiere...\"],\"eMwMfT\":[\"Aktiviere Benachrichtigungen, um Push-Genehmigungsanfragen auf diesem Gerät zu empfangen.\"],\"ePK91l\":[\"Bearbeiten\"],\"g3UF2V\":[\"Akzeptieren\"],\"g7oOIY\":[\"Token suchen\"],\"g7yB5h\":[\"Die Serverantwort konnte nicht verarbeitet werden oder enthielt nicht das erwartete Tokenmaterial.\"],\"hD6g-j\":[\"Willkommen bei eduMFA\"],\"hevXA2\":[\"Zuletzt fehlgeschlagen\"],\"i5IMTS\":[\"Füge deinen ersten eduMFA-Token hinzu, um Anmeldungen sicher von diesem Gerät aus zu genehmigen.\"],\"iAqBor\":[\"Gib eine Bezeichnung für dieses Token ein.\"],\"iDNBZe\":[\"Benachrichtigungen\"],\"iQmbPb\":[\"Lizenz\"],\"jbq7j2\":[\"Ablehnen\"],\"jiR92q\":[\"Neues Push-Token koppeln\"],\"jrtxCX\":[\"eduMFA bewerten\"],\"k4nIkP\":[\"Keine Token gefunden\"],\"kLttbL\":[\"Registrierung fehlgeschlagen\"],\"lF4FiS\":[\"Kein QR-Code gefunden\"],\"liw26p\":[\"Anonyme Berichte\"],\"mVSlBU\":[\"Registriert\"],\"mfi038\":[\"Hilf, eduMFA zu verbessern\"],\"ml5TCa\":[\"Der bereitgestellte QR-Code ist kein gültiger eduMFA-Token.\"],\"o4_Dwt\":[\"Kamerazugriff ist deaktiviert\"],\"pphVNl\":[\"Dein Bild enthielt keinen QR-Code.\"],\"pvnfJD\":[\"Dunkel\"],\"q9XKrW\":[\"Antwortverarbeitung fehlgeschlagen\"],\"rjGI_Q\":[\"Datenschutz\"],\"tfDRzk\":[\"Speichern\"],\"tsg9Ze\":[\"Dieses Token konnte nicht aktualisiert werden. Es wurde möglicherweise auf dem Server entfernt oder die Institution, die den Push-Dienst betreibt, hat ein technisches Problem.\"],\"u5FNLt\":[\"Absturz- und Fehlerberichte\"],\"uAQUqI\":[\"Status\"],\"uI1l6M\":[\"Bezeichnung erforderlich\"],\"uh8i5u\":[\"Rollout erneut versuchen\"],\"uyJsf6\":[\"Über\"],\"vXIe7J\":[\"Sprache\"],\"xGVfLh\":[\"Weiter\"],\"xPl9LS\":[\"Versuche einen anderen Token-Namen, Aussteller oder eine andere Seriennummer.\"],\"xkjgVR\":[\"Token konnte nicht hinzugefügt werden\"],\"ypfsn_\":[\"Seriennummer\"],\"zXZJoN\":[\"Token hinzufügen\"]}")};
\ No newline at end of file
+/*eslint-disable*/module.exports={messages:JSON.parse("{\"-o5oci\":[\"eduMFA verwendet \",[\"licenseCount\"],\" Open-Source-Pakete. Die Paketdetails werden aus den installierten Produktionsabhängigkeiten generiert.\"],\"0DlQl0\":[\"Noch keine Token vorhanden\"],\"0EvA_s\":[\"Möchtest du diesen Token wirklich löschen?\"],\"0VZTWA\":[\"Genehmige die Anmeldung nur, wenn du sie selbst gestartet hast. Lehne sie im Zweifel ab.\"],\"0_bbfl\":[\"Bewahre deine Authentifizierungs-Token an einem Ort auf und genehmige Anmeldungen sicher von diesem Gerät aus.\"],\"1QfxQT\":[\"Schließen\"],\"1m41_U\":[\"Feedback senden\"],\"1njn7W\":[\"Hell\"],\"2b4lV4\":[\"Aktualisierung fehlgeschlagen\"],\"3OQxEV\":[\"Servernachricht\"],\"47yaVS\":[\"Google Play-Dienste sind nicht verfügbar, daher können Anfragen nicht automatisch eingehen. Ziehe die Token-Liste nach unten, um nach ausstehenden Anfragen zu suchen.\"],\"4aqCBZ\":[\"Keine Anmeldeanfrage verpassen\"],\"4xUhXP\":[\"Benachrichtigungseinstellungen öffnen\"],\"6jfS51\":[\"Willkommen\"],\"6mYeNy\":[\"Serverregistrierung fehlgeschlagen\"],\"79FlZ7\":[\"Benachrichtigungen sind aktiviert\"],\"7GD6En\":[\"Anmeldeanfragen können unbemerkt bleiben\"],\"7yOnZx\":[\"Das Gerät konnte das für diesen Token erforderliche RSA-Schlüsselpaar nicht erstellen. Versuche den Rollout erneut und lasse die App währenddessen geöffnet.\"],\"858Cvj\":[\"Der QR-Code wird von dieser App nicht unterstützt. Bitte verwenden Sie eine allgemeine Authenticator-App.\"],\"87a_t_\":[\"Bezeichnung\"],\"88u4WE\":[\"Feedback-Typ\"],\"8DuEDH\":[\"Noch keine Interaktionen\"],\"8ZFiCO\":[\"Dein Feedback wurde erfolgreich übermittelt.\"],\"B_-dtj\":[\"Gib deinen Namen und deine E-Mail-Adresse an, wenn wir dich bei Rückfragen oder Neuigkeiten zu deinem Feedback kontaktieren dürfen.\"],\"BrFR00\":[\"Registrierung wird abgeschlossen\"],\"CVIHyt\":[\"Bitte beschreibe dein Feedback.\"],\"CsH7i4\":[\"Anonymisierte Absturz- und Fehlerdiagnosen senden\"],\"DEKI9D\":[\"Projektwebsite\"],\"DPfwMq\":[\"Fertig\"],\"Dnn2XG\":[\"Automatisch\"],\"DuOYea\":[\"Feedback (Expo UI)\"],\"DvOF3m\":[\"Teile Absturz- und Fehlerberichte, um die Zuverlässigkeit zu verbessern. Sie enthalten niemals Token-Geheimnisse, Passwörter oder Namen von Einrichtungen.\"],\"DyF_C_\":[\"Lizenzinformationen sind nicht verfügbar.\"],\"EAyh56\":[\"Anmeldung prüfen\"],\"EBRcBj\":[\"Haptik\"],\"ED9KJC\":[\"Was möchtest du uns mitteilen?\"],\"ET7frA\":[\"Kamerazugriff erlauben\"],\"EfBEmE\":[\"Rollout läuft...\"],\"F37c1s\":[\"Einstellungen öffnen\"],\"FEr96N\":[\"Erscheinungsbild\"],\"FSVCou\":[\"Nach unten ziehen, um Anfragen abzurufen\"],\"G0TPBU\":[\"QR-Code-Scanner\"],\"I5kL4f\":[\"Aktivitätsprotokoll\"],\"I92Z-b\":[\"Benachrichtigungen aktivieren\"],\"IPFr7d\":[\"Wähle, ob du anonymisierte Absturz- und Fehlerberichte teilen möchtest.\"],\"IRRc6m\":[\"Anonyme Berichte teilen\"],\"IT-f_-\":[\"Schlüssel werden generiert\"],\"Ijsgym\":[\"Token löschen\"],\"JG7Ls-\":[\"Aktiviere den Kamerazugriff in den Einstellungen, um QR-Codes zu scannen.\"],\"Lu4tTO\":[\"Platziere den QR-Code im Rahmen\"],\"Mi0UmV\":[\"Registrierungsantwort fehlgeschlagen\"],\"NkQliA\":[\"Du kannst jetzt Anmeldeanfragen empfangen und freigeben.\"],\"NsiA8m\":[\"Rollout fehlgeschlagen\"],\"O7obZD\":[\"Dieses Token konnte nicht aktualisiert werden, weil der Push-Dienst nicht erreichbar war. Prüfe deine Verbindung oder versuche es später erneut.\"],\"On0aF2\":[\"Webseite\"],\"OugAQq\":[\"QR-Code hochladen\"],\"PBxg_E\":[\"Nicht jetzt\"],\"PvN-FE\":[\"Die Token-Version wird von dieser App nicht unterstützt. Bitte aktualisiere die App auf die neueste Version oder kontaktiere deine Institution für Unterstützung.\"],\"Q__-1-\":[\"Fehlerbericht\"],\"QchA_G\":[\"E-Mail (optional)\"],\"Qm1NmK\":[\"ODER\"],\"QmIEhK\":[\"Authenticator-Apps suchen\"],\"R0wpRt\":[\"Deine Nachricht und optionalen Kontaktdaten werden über Sentry an eduMFA gesendet.\"],\"R1QFqA\":[\"Funktionswunsch\"],\"R8Gqnt\":[\"Open-Source-Lizenzen\"],\"ROheaS\":[\"Version \",[\"itemVersion\"],\" · \",[\"itemLicense\"]],\"RkXlPZ\":[\"GitHub\"],\"Ro1gox\":[\"Token bearbeiten\"],\"RoafuO\":[\"Feedback senden\"],\"SWOmlM\":[\"Aktualisiere den Anzeigenamen für diesen Token.\"],\"ScJ9fj\":[\"Datenschutzerklärung\"],\"TP9_K5\":[\"Token\"],\"TfpFBG\":[\"Deine Wahl\"],\"TqCsD2\":[[\"totalPending\"],\" Anfragen warten. Prüfe jede einzeln.\"],\"Tz0i8g\":[\"Einstellungen\"],\"UMeccq\":[\"Schlüsselgenerierung fehlgeschlagen\"],\"UO-3EH\":[\"Dieses Token hat die Registrierung nicht abgeschlossen und kann keine Push-Anfragen empfangen, bis der Rollout erfolgreich ist.\"],\"URmyfc\":[\"Details\"],\"UbRKMZ\":[\"Ausstehend\"],\"VIZNLO\":[\"Öffentlicher Schlüssel wird registriert\"],\"VSWXlh\":[\"Feedback-Details\"],\"WRdUP2\":[\"Kamerazugriff ist erforderlich, um den QR-Code zu scannen und einen Token zu koppeln.\"],\"Weq9zb\":[\"Allgemein\"],\"YirHq7\":[\"Feedback\"],\"Z2CsKg\":[\"Anonyme Berichte nicht teilen\"],\"Z7ZXbT\":[\"Freigeben\"],\"ZDIydz\":[\"Loslegen\"],\"ZYIECH\":[\"Benachrichtigungen sind deaktiviert\"],\"_ijUgq\":[\"Lass dich benachrichtigen, wenn eine Anmeldung deine Genehmigung erfordert.\"],\"aAIQg2\":[\"Darstellung\"],\"b75KIN\":[\"App-Sprache in den Systemeinstellungen ändern\"],\"bfECx6\":[\"Dieses Token konnte nicht beim Registrierungsserver registriert werden. Prüfe deine Verbindung oder versuche es später erneut.\"],\"c7uz-G\":[\"Aktiviere Benachrichtigungen in den Einstellungen, damit du reagieren kannst, sobald eine Anfrage eingeht.\"],\"cFCKYZ\":[\"Ablehnen\"],\"cnGeoo\":[\"Löschen\"],\"cu6w0K\":[\"Token-Genehmigungen, Ablehnungen, Aktualisierungsereignisse und Anmeldeaktivitäten werden hier angezeigt.\"],\"dEgA5A\":[\"Abbrechen\"],\"dN-1Th\":[\"Öffnen mit deiner Authenticator-App\"],\"dum6Mb\":[\"Aktualisiere...\"],\"e4u4yb\":[\"Teile uns mit, was gut funktioniert hat oder was wir verbessern können.\"],\"eMwMfT\":[\"Aktiviere Benachrichtigungen, um Push-Genehmigungsanfragen auf diesem Gerät zu empfangen.\"],\"ePK91l\":[\"Bearbeiten\"],\"g3UF2V\":[\"Akzeptieren\"],\"g7oOIY\":[\"Token suchen\"],\"g7yB5h\":[\"Die Serverantwort konnte nicht verarbeitet werden oder enthielt nicht das erwartete Tokenmaterial.\"],\"hD6g-j\":[\"Willkommen bei eduMFA\"],\"hevXA2\":[\"Zuletzt fehlgeschlagen\"],\"i5IMTS\":[\"Füge deinen ersten eduMFA-Token hinzu, um Anmeldungen sicher von diesem Gerät aus zu genehmigen.\"],\"iAqBor\":[\"Gib eine Bezeichnung für dieses Token ein.\"],\"iDNBZe\":[\"Benachrichtigungen\"],\"iQmbPb\":[\"Lizenz\"],\"jbq7j2\":[\"Ablehnen\"],\"jiR92q\":[\"Neues Push-Token koppeln\"],\"jrtxCX\":[\"eduMFA bewerten\"],\"k4nIkP\":[\"Keine Token gefunden\"],\"kLttbL\":[\"Registrierung fehlgeschlagen\"],\"lF4FiS\":[\"Kein QR-Code gefunden\"],\"liw26p\":[\"Anonyme Berichte\"],\"mRd7qL\":[\"Feedback konnte nicht gesendet werden. Bitte versuche es erneut.\"],\"mVSlBU\":[\"Registriert\"],\"mfi038\":[\"Hilf, eduMFA zu verbessern\"],\"ml5TCa\":[\"Der bereitgestellte QR-Code ist kein gültiger eduMFA-Token.\"],\"o4_Dwt\":[\"Kamerazugriff ist deaktiviert\"],\"pphVNl\":[\"Dein Bild enthielt keinen QR-Code.\"],\"pvnfJD\":[\"Dunkel\"],\"q9XKrW\":[\"Antwortverarbeitung fehlgeschlagen\"],\"rjGI_Q\":[\"Datenschutz\"],\"t-Mdx7\":[\"Wähle die Kategorie, die am besten zu deinem Feedback passt. Deine Nachricht und optionalen Kontaktdaten werden über Sentry an eduMFA gesendet.\"],\"tfDRzk\":[\"Speichern\"],\"tsg9Ze\":[\"Dieses Token konnte nicht aktualisiert werden. Es wurde möglicherweise auf dem Server entfernt oder die Institution, die den Push-Dienst betreibt, hat ein technisches Problem.\"],\"u5FNLt\":[\"Absturz- und Fehlerberichte\"],\"uAQUqI\":[\"Status\"],\"uI1l6M\":[\"Bezeichnung erforderlich\"],\"uZ25pH\":[\"Kontakt (optional)\"],\"uh8i5u\":[\"Rollout erneut versuchen\"],\"uyJsf6\":[\"Über\"],\"vXIe7J\":[\"Sprache\"],\"wJAhPZ\":[\"Name (optional)\"],\"xGVfLh\":[\"Weiter\"],\"xPl9LS\":[\"Versuche einen anderen Token-Namen, Aussteller oder eine andere Seriennummer.\"],\"xkjgVR\":[\"Token konnte nicht hinzugefügt werden\"],\"xoqD_n\":[\"Gib eine gültige E-Mail-Adresse ein.\"],\"yOrM5-\":[\"Vielen Dank für dein Feedback\"],\"ypfsn_\":[\"Seriennummer\"],\"yz7wBu\":[\"Schließen\"],\"zXZJoN\":[\"Token hinzufügen\"]}")};
\ No newline at end of file
diff --git a/src/locales/de/messages.po b/src/locales/de/messages.po
index 67935e8..c0ce491 100644
--- a/src/locales/de/messages.po
+++ b/src/locales/de/messages.po
@@ -39,6 +39,11 @@ msgstr "Token hinzufügen"
msgid "Add your first eduMFA token to approve sign-ins securely from this device."
msgstr "Füge deinen ersten eduMFA-Token hinzu, um Anmeldungen sicher von diesem Gerät aus zu genehmigen."
+#: src/components/feedback-form.android.tsx:111
+#: src/components/feedback-form.ios.tsx:130
+msgid "Add your name and email if you would like us to contact you with follow-up questions or updates about your feedback."
+msgstr "Gib deinen Namen und deine E-Mail-Adresse an, wenn wir dich bei Rückfragen oder Neuigkeiten zu deinem Feedback kontaktieren dürfen."
+
#: src/components/token-add/camera-permission-splash.tsx:37
msgid "Allow camera access"
msgstr "Kamerazugriff erlauben"
@@ -63,6 +68,10 @@ msgstr "Möchtest du diesen Token wirklich löschen?"
msgid "Automatic"
msgstr "Automatisch"
+#: src/hooks/use-feedback-form.ts:21
+msgid "Bug report"
+msgstr "Fehlerbericht"
+
#: src/components/token-add/camera-permission-splash.tsx:44
msgid "Camera access is required to scan the QR code and pair a token."
msgstr "Kamerazugriff ist erforderlich, um den QR-Code zu scannen und einen Token zu koppeln."
@@ -79,18 +88,31 @@ msgstr "Abbrechen"
msgid "Change the app language in system settings"
msgstr "App-Sprache in den Systemeinstellungen ändern"
+#: src/components/feedback-form.ios.tsx:95
+msgid "Choose the category that best matches your feedback. Your message and optional contact details will be sent to eduMFA through Sentry."
+msgstr "Wähle die Kategorie, die am besten zu deinem Feedback passt. Deine Nachricht und optionalen Kontaktdaten werden über Sentry an eduMFA gesendet."
+
#: src/components/onboarding-sequence.tsx:112
msgid "Choose whether to share anonymous crash and error reports."
msgstr "Wähle, ob du anonymisierte Absturz- und Fehlerberichte teilen möchtest."
+#: src/components/feedback-form.android.tsx:81
+#: src/components/feedback-form.ios.tsx:83
+msgid "Close"
+msgstr "Schließen"
+
+#: src/components/feedback-form.ios.tsx:127
+msgid "Contact (optional)"
+msgstr "Kontakt (optional)"
+
#: src/components/onboarding-sequence/step-actions.tsx:85
#: src/components/onboarding-sequence/step-actions.tsx:106
#: src/components/token-add/camera-permission-splash.tsx:64
msgid "Continue"
msgstr "Weiter"
-#: src/app/settings/index.tsx:172
#: src/app/settings/index.tsx:178
+#: src/app/settings/index.tsx:184
msgid "Crash and error reports"
msgstr "Absturz- und Fehlerberichte"
@@ -128,6 +150,11 @@ msgstr "Schließen"
msgid "Don't share anonymous reports"
msgstr "Anonyme Berichte nicht teilen"
+#: src/components/feedback-form.android.tsx:66
+#: src/components/feedback-form.ios.tsx:54
+msgid "Done"
+msgstr "Fertig"
+
#: src/app/index.tsx:252
#: src/app/token/[tokenId].tsx:192
msgid "Edit"
@@ -141,6 +168,11 @@ msgstr "Token bearbeiten"
msgid "eduMFA uses {licenseCount} open-source packages. Package details are generated from the installed production dependency graph."
msgstr "eduMFA verwendet {licenseCount} Open-Source-Pakete. Die Paketdetails werden aus den installierten Produktionsabhängigkeiten generiert."
+#: src/components/feedback-form.android.tsx:134
+#: src/components/feedback-form.ios.tsx:144
+msgid "Email (optional)"
+msgstr "E-Mail (optional)"
+
#: src/components/token-add/camera-permission-denied.tsx:40
msgid "Enable camera access in Settings to scan QR codes."
msgstr "Aktiviere den Kamerazugriff in den Einstellungen, um QR-Codes zu scannen."
@@ -165,17 +197,47 @@ msgstr "Registrierungsantwort fehlgeschlagen"
msgid "Enter a label for this token."
msgstr "Gib eine Bezeichnung für dieses Token ein."
+#: src/hooks/use-feedback-form.ts:29
+msgid "Enter a valid email address."
+msgstr "Gib eine gültige E-Mail-Adresse ein."
+
#: src/hooks/use-handle-token-uri.ts:50
#: src/hooks/use-handle-token-uri.ts:60
#: src/hooks/use-handle-token-uri.ts:92
msgid "Failed to add token"
msgstr "Token konnte nicht hinzugefügt werden"
+#: src/hooks/use-feedback-form.ts:23
+msgid "Feature request"
+msgstr "Funktionswunsch"
+
+#: src/app/settings/index.tsx:130
+msgid "Feedback"
+msgstr "Feedback"
+
+#: src/app/settings/index.tsx:128
+#~ msgid "Feedback (Expo UI)"
+#~ msgstr "Feedback (Expo UI)"
+
+#: src/hooks/use-feedback-form.ts:70
+msgid "Feedback could not be sent. Please try again."
+msgstr "Feedback konnte nicht gesendet werden. Bitte versuche es erneut."
+
+#: src/components/feedback-form.ios.tsx:90
+msgid "Feedback details"
+msgstr "Feedback-Details"
+
+#: src/components/feedback-form.android.tsx:92
+#: src/components/feedback-form.ios.tsx:100
+msgid "Feedback type"
+msgstr "Feedback-Typ"
+
#: src/components/token-detail/token-detail-utils.ts:85
msgid "Finalizing enrollment"
msgstr "Registrierung wird abgeschlossen"
#: src/app/settings/index.tsx:104
+#: src/hooks/use-feedback-form.ts:22
msgid "General"
msgstr "Allgemein"
@@ -191,7 +253,7 @@ msgstr "Lass dich benachrichtigen, wenn eine Anmeldung deine Genehmigung erforde
msgid "Get started"
msgstr "Loslegen"
-#: src/app/settings/index.tsx:151
+#: src/app/settings/index.tsx:157
msgid "GitHub"
msgstr "GitHub"
@@ -251,6 +313,11 @@ msgstr "Lizenzinformationen sind nicht verfügbar."
msgid "Light"
msgstr "Hell"
+#: src/components/feedback-form.android.tsx:120
+#: src/components/feedback-form.ios.tsx:136
+msgid "Name (optional)"
+msgstr "Name (optional)"
+
#: src/components/onboarding-sequence.tsx:104
msgid "Never miss a sign-in request"
msgstr "Keine Anmeldeanfrage verpassen"
@@ -303,7 +370,7 @@ msgstr "Einstellungen öffnen"
msgid "Open with your Authenticator App"
msgstr "Öffnen mit deiner Authenticator-App"
-#: src/app/settings/index.tsx:163
+#: src/app/settings/index.tsx:169
#: src/app/settings/licenses.tsx:83
msgid "Open-source licenses"
msgstr "Open-Source-Lizenzen"
@@ -324,11 +391,15 @@ msgstr "Ausstehend"
msgid "Place the QR code inside the frame"
msgstr "Platziere den QR-Code im Rahmen"
-#: src/app/settings/index.tsx:168
+#: src/hooks/use-feedback-form.ts:50
+msgid "Please describe your feedback."
+msgstr "Bitte beschreibe dein Feedback."
+
+#: src/app/settings/index.tsx:174
msgid "Privacy"
msgstr "Datenschutz"
-#: src/app/settings/index.tsx:142
+#: src/app/settings/index.tsx:148
msgid "Privacy policy"
msgstr "Datenschutzerklärung"
@@ -373,7 +444,7 @@ msgstr "Antwortverarbeitung fehlgeschlagen"
msgid "Retry Rollout"
msgstr "Rollout erneut versuchen"
-#: src/app/settings/index.tsx:130
+#: src/app/settings/index.tsx:136
msgid "Review eduMFA"
msgstr "eduMFA bewerten"
@@ -402,10 +473,16 @@ msgstr "Authenticator-Apps suchen"
msgid "Search tokens"
msgstr "Token suchen"
-#: src/app/settings/index.tsx:170
+#: src/app/settings/index.tsx:176
msgid "Send anonymized crash and error diagnostics"
msgstr "Anonymisierte Absturz- und Fehlerdiagnosen senden"
+#: src/app/settings/feedback.tsx:23
+#: src/components/feedback-form.android.tsx:77
+#: src/components/feedback-form.ios.tsx:79
+msgid "Send feedback"
+msgstr "Feedback senden"
+
#: src/components/token-detail/token-overview-content.tsx:169
msgid "Serial"
msgstr "Seriennummer"
@@ -418,7 +495,7 @@ msgstr "Servernachricht"
msgid "Server registration failed"
msgstr "Serverregistrierung fehlgeschlagen"
-#: src/app/settings/index.tsx:194
+#: src/app/settings/index.tsx:200
msgid "Settings"
msgstr "Einstellungen"
@@ -434,6 +511,20 @@ msgstr "Anmeldeanfragen können unbemerkt bleiben"
msgid "Status"
msgstr "Status"
+#: src/components/feedback-form.android.tsx:163
+#: src/components/feedback-form.ios.tsx:170
+msgid "Submit feedback"
+msgstr "Feedback senden"
+
+#: src/components/feedback-form.android.tsx:89
+msgid "Tell us what worked well or what we can improve."
+msgstr "Teile uns mit, was gut funktioniert hat oder was wir verbessern können."
+
+#: src/components/feedback-form.android.tsx:60
+#: src/components/feedback-form.ios.tsx:50
+msgid "Thank you for your feedback"
+msgstr "Vielen Dank für dein Feedback"
+
#: src/components/token-detail/token-detail-utils.ts:49
msgid "The device could not create the RSA key pair required for this token. Retry rollout and keep the app open while it runs."
msgstr "Das Gerät konnte das für diesen Token erforderliche RSA-Schlüsselpaar nicht erstellen. Versuche den Rollout erneut und lasse die App währenddessen geöffnet."
@@ -502,7 +593,7 @@ msgstr "QR-Code hochladen"
msgid "Version {itemVersion} · {itemLicense}"
msgstr "Version {itemVersion} · {itemLicense}"
-#: src/app/settings/index.tsx:157
+#: src/app/settings/index.tsx:163
msgid "Website"
msgstr "Webseite"
@@ -514,6 +605,11 @@ msgstr "Willkommen"
msgid "Welcome to eduMFA"
msgstr "Willkommen bei eduMFA"
+#: src/components/feedback-form.android.tsx:151
+#: src/components/feedback-form.ios.tsx:115
+msgid "What would you like us to know?"
+msgstr "Was möchtest du uns mitteilen?"
+
#: src/components/onboarding-sequence/step-actions.tsx:97
msgid "You’re ready to receive and approve sign-in requests."
msgstr "Du kannst jetzt Anmeldeanfragen empfangen und freigeben."
@@ -522,6 +618,15 @@ msgstr "Du kannst jetzt Anmeldeanfragen empfangen und freigeben."
msgid "Your choice"
msgstr "Deine Wahl"
+#: src/components/feedback-form.android.tsx:63
+#: src/components/feedback-form.ios.tsx:52
+msgid "Your feedback was submitted successfully."
+msgstr "Dein Feedback wurde erfolgreich übermittelt."
+
#: src/hooks/use-handle-token-uri.ts:36
msgid "Your image did not contain a QR code."
msgstr "Dein Bild enthielt keinen QR-Code."
+
+#: src/components/feedback-form.android.tsx:160
+msgid "Your message and optional contact details will be sent to eduMFA through Sentry."
+msgstr "Deine Nachricht und optionalen Kontaktdaten werden über Sentry an eduMFA gesendet."
diff --git a/src/locales/en/messages.js b/src/locales/en/messages.js
index d41a279..4a9c9d2 100644
--- a/src/locales/en/messages.js
+++ b/src/locales/en/messages.js
@@ -1 +1 @@
-/*eslint-disable*/module.exports={messages:JSON.parse("{\"-o5oci\":[\"eduMFA uses \",[\"licenseCount\"],\" open-source packages. Package details are generated from the installed production dependency graph.\"],\"0DlQl0\":[\"No tokens yet\"],\"0EvA_s\":[\"Are you sure you want to delete this token?\"],\"0VZTWA\":[\"Only approve if you started this sign-in. If you are unsure, deny it.\"],\"0_bbfl\":[\"Keep your authentication tokens in one place and approve sign-ins securely from this device.\"],\"1QfxQT\":[\"Dismiss\"],\"1njn7W\":[\"Light\"],\"2b4lV4\":[\"Refresh failed\"],\"3OQxEV\":[\"Server message\"],\"47yaVS\":[\"Google Play services are unavailable, so requests cannot arrive automatically. Pull down on the token list to check for pending requests.\"],\"4aqCBZ\":[\"Never miss a sign-in request\"],\"4xUhXP\":[\"Open notification settings\"],\"6jfS51\":[\"Welcome\"],\"6mYeNy\":[\"Server registration failed\"],\"79FlZ7\":[\"Notifications are enabled\"],\"7GD6En\":[\"Sign-in requests may go unnoticed\"],\"7yOnZx\":[\"The device could not create the RSA key pair required for this token. Retry rollout and keep the app open while it runs.\"],\"858Cvj\":[\"The QR code isn't supported by this app. Please use a general Authenticator app.\"],\"87a_t_\":[\"Label\"],\"8DuEDH\":[\"No interactions yet\"],\"BrFR00\":[\"Finalizing enrollment\"],\"CsH7i4\":[\"Send anonymized crash and error diagnostics\"],\"DEKI9D\":[\"Project website\"],\"Dnn2XG\":[\"Automatic\"],\"DvOF3m\":[\"Help improve reliability by sharing crash and error reports. They never include token secrets, passwords, or institution names.\"],\"DyF_C_\":[\"License information is unavailable.\"],\"EAyh56\":[\"Review sign-in\"],\"EBRcBj\":[\"Haptics\"],\"ET7frA\":[\"Allow camera access\"],\"EfBEmE\":[\"Rolling out...\"],\"F37c1s\":[\"Open Settings\"],\"FEr96N\":[\"Theme\"],\"FSVCou\":[\"Pull to refresh for requests\"],\"G0TPBU\":[\"QR code scanner\"],\"I5kL4f\":[\"Activity Log\"],\"I92Z-b\":[\"Enable notifications\"],\"IPFr7d\":[\"Choose whether to share anonymous crash and error reports.\"],\"IRRc6m\":[\"Share anonymous reports\"],\"IT-f_-\":[\"Generating keys\"],\"Ijsgym\":[\"Delete token\"],\"JG7Ls-\":[\"Enable camera access in Settings to scan QR codes.\"],\"Lu4tTO\":[\"Place the QR code inside the frame\"],\"Mi0UmV\":[\"Enrollment response failed\"],\"NkQliA\":[\"You’re ready to receive and approve sign-in requests.\"],\"NsiA8m\":[\"Rollout Failed\"],\"O7obZD\":[\"This token could not be refreshed because the push service could not be reached. Check your connection or try again later.\"],\"On0aF2\":[\"Website\"],\"OugAQq\":[\"Upload QR Code\"],\"PBxg_E\":[\"Not now\"],\"PvN-FE\":[\"The token version is not supported by this app. Please update the app to the latest version or contact your institution for assistance.\"],\"Qm1NmK\":[\"OR\"],\"QmIEhK\":[\"Search Authenticator Apps\"],\"R8Gqnt\":[\"Open-source licenses\"],\"ROheaS\":[\"Version \",[\"itemVersion\"],\" · \",[\"itemLicense\"]],\"RkXlPZ\":[\"GitHub\"],\"Ro1gox\":[\"Edit token\"],\"SWOmlM\":[\"Update the display name for this token.\"],\"ScJ9fj\":[\"Privacy policy\"],\"TP9_K5\":[\"Token\"],\"TfpFBG\":[\"Your choice\"],\"TqCsD2\":[[\"totalPending\"],\" requests are waiting. Review each one separately.\"],\"Tz0i8g\":[\"Settings\"],\"UMeccq\":[\"Key generation failed\"],\"UO-3EH\":[\"This token did not finish enrollment and cannot receive push requests until rollout succeeds.\"],\"URmyfc\":[\"Details\"],\"UbRKMZ\":[\"Pending\"],\"VIZNLO\":[\"Registering public key\"],\"WRdUP2\":[\"Camera access is required to scan the QR code and pair a token.\"],\"Weq9zb\":[\"General\"],\"Z2CsKg\":[\"Don't share anonymous reports\"],\"Z7ZXbT\":[\"Approve\"],\"ZDIydz\":[\"Get started\"],\"ZYIECH\":[\"Notifications are disabled\"],\"_ijUgq\":[\"Get notified when a sign-in needs your approval.\"],\"aAIQg2\":[\"Appearance\"],\"b75KIN\":[\"Change the app language in system settings\"],\"bfECx6\":[\"This token could not be registered with the enrollment server. Check your connection or try again later.\"],\"c7uz-G\":[\"Turn on notifications in Settings so you can respond when a request arrives.\"],\"cFCKYZ\":[\"Deny\"],\"cnGeoo\":[\"Delete\"],\"cu6w0K\":[\"Token approvals, denials, refresh events, and enrollment activity will appear here.\"],\"dEgA5A\":[\"Cancel\"],\"dN-1Th\":[\"Open with your Authenticator App\"],\"dum6Mb\":[\"Refreshing...\"],\"eMwMfT\":[\"Enable notifications to receive push approval requests on this device.\"],\"ePK91l\":[\"Edit\"],\"g3UF2V\":[\"Accept\"],\"g7oOIY\":[\"Search tokens\"],\"g7yB5h\":[\"The server response could not be parsed or did not include the expected token material.\"],\"hD6g-j\":[\"Welcome to eduMFA\"],\"hevXA2\":[\"Last failed\"],\"i5IMTS\":[\"Add your first eduMFA token to approve sign-ins securely from this device.\"],\"iAqBor\":[\"Enter a label for this token.\"],\"iDNBZe\":[\"Notifications\"],\"iQmbPb\":[\"License\"],\"jbq7j2\":[\"Decline\"],\"jiR92q\":[\"Pair new Push Token\"],\"jrtxCX\":[\"Review eduMFA\"],\"k4nIkP\":[\"No tokens found\"],\"kLttbL\":[\"Registration failed\"],\"lF4FiS\":[\"No QR code found\"],\"liw26p\":[\"Anonymous reports\"],\"mVSlBU\":[\"Enrolled\"],\"mfi038\":[\"Help improve eduMFA\"],\"ml5TCa\":[\"The provided QR code isn't a valid eduMFA token.\"],\"o4_Dwt\":[\"Camera access is turned off\"],\"pphVNl\":[\"Your image did not contain a QR code.\"],\"pvnfJD\":[\"Dark\"],\"q9XKrW\":[\"Response parsing failed\"],\"rjGI_Q\":[\"Privacy\"],\"tfDRzk\":[\"Save\"],\"tsg9Ze\":[\"This token could not be refreshed. It may have been removed on the server or the institution hosting the push service may be having a technical issue.\"],\"u5FNLt\":[\"Crash and error reports\"],\"uAQUqI\":[\"Status\"],\"uI1l6M\":[\"Label required\"],\"uh8i5u\":[\"Retry Rollout\"],\"uyJsf6\":[\"About\"],\"vXIe7J\":[\"Language\"],\"xGVfLh\":[\"Continue\"],\"xPl9LS\":[\"Try another token name, issuer, or serial number.\"],\"xkjgVR\":[\"Failed to add token\"],\"ypfsn_\":[\"Serial\"],\"zXZJoN\":[\"Add token\"]}")};
\ No newline at end of file
+/*eslint-disable*/module.exports={messages:JSON.parse("{\"-o5oci\":[\"eduMFA uses \",[\"licenseCount\"],\" open-source packages. Package details are generated from the installed production dependency graph.\"],\"0DlQl0\":[\"No tokens yet\"],\"0EvA_s\":[\"Are you sure you want to delete this token?\"],\"0VZTWA\":[\"Only approve if you started this sign-in. If you are unsure, deny it.\"],\"0_bbfl\":[\"Keep your authentication tokens in one place and approve sign-ins securely from this device.\"],\"1QfxQT\":[\"Dismiss\"],\"1m41_U\":[\"Submit feedback\"],\"1njn7W\":[\"Light\"],\"2b4lV4\":[\"Refresh failed\"],\"3OQxEV\":[\"Server message\"],\"47yaVS\":[\"Google Play services are unavailable, so requests cannot arrive automatically. Pull down on the token list to check for pending requests.\"],\"4aqCBZ\":[\"Never miss a sign-in request\"],\"4xUhXP\":[\"Open notification settings\"],\"6jfS51\":[\"Welcome\"],\"6mYeNy\":[\"Server registration failed\"],\"79FlZ7\":[\"Notifications are enabled\"],\"7GD6En\":[\"Sign-in requests may go unnoticed\"],\"7yOnZx\":[\"The device could not create the RSA key pair required for this token. Retry rollout and keep the app open while it runs.\"],\"858Cvj\":[\"The QR code isn't supported by this app. Please use a general Authenticator app.\"],\"87a_t_\":[\"Label\"],\"88u4WE\":[\"Feedback type\"],\"8DuEDH\":[\"No interactions yet\"],\"8ZFiCO\":[\"Your feedback was submitted successfully.\"],\"B_-dtj\":[\"Add your name and email if you would like us to contact you with follow-up questions or updates about your feedback.\"],\"BrFR00\":[\"Finalizing enrollment\"],\"CVIHyt\":[\"Please describe your feedback.\"],\"CsH7i4\":[\"Send anonymized crash and error diagnostics\"],\"DEKI9D\":[\"Project website\"],\"DPfwMq\":[\"Done\"],\"Dnn2XG\":[\"Automatic\"],\"DuOYea\":[\"Feedback (Expo UI)\"],\"DvOF3m\":[\"Help improve reliability by sharing crash and error reports. They never include token secrets, passwords, or institution names.\"],\"DyF_C_\":[\"License information is unavailable.\"],\"EAyh56\":[\"Review sign-in\"],\"EBRcBj\":[\"Haptics\"],\"ED9KJC\":[\"What would you like us to know?\"],\"ET7frA\":[\"Allow camera access\"],\"EfBEmE\":[\"Rolling out...\"],\"F37c1s\":[\"Open Settings\"],\"FEr96N\":[\"Theme\"],\"FSVCou\":[\"Pull to refresh for requests\"],\"G0TPBU\":[\"QR code scanner\"],\"I5kL4f\":[\"Activity Log\"],\"I92Z-b\":[\"Enable notifications\"],\"IPFr7d\":[\"Choose whether to share anonymous crash and error reports.\"],\"IRRc6m\":[\"Share anonymous reports\"],\"IT-f_-\":[\"Generating keys\"],\"Ijsgym\":[\"Delete token\"],\"JG7Ls-\":[\"Enable camera access in Settings to scan QR codes.\"],\"Lu4tTO\":[\"Place the QR code inside the frame\"],\"Mi0UmV\":[\"Enrollment response failed\"],\"NkQliA\":[\"You’re ready to receive and approve sign-in requests.\"],\"NsiA8m\":[\"Rollout Failed\"],\"O7obZD\":[\"This token could not be refreshed because the push service could not be reached. Check your connection or try again later.\"],\"On0aF2\":[\"Website\"],\"OugAQq\":[\"Upload QR Code\"],\"PBxg_E\":[\"Not now\"],\"PvN-FE\":[\"The token version is not supported by this app. Please update the app to the latest version or contact your institution for assistance.\"],\"Q__-1-\":[\"Bug report\"],\"QchA_G\":[\"Email (optional)\"],\"Qm1NmK\":[\"OR\"],\"QmIEhK\":[\"Search Authenticator Apps\"],\"R0wpRt\":[\"Your message and optional contact details will be sent to eduMFA through Sentry.\"],\"R1QFqA\":[\"Feature request\"],\"R8Gqnt\":[\"Open-source licenses\"],\"ROheaS\":[\"Version \",[\"itemVersion\"],\" · \",[\"itemLicense\"]],\"RkXlPZ\":[\"GitHub\"],\"Ro1gox\":[\"Edit token\"],\"RoafuO\":[\"Send feedback\"],\"SWOmlM\":[\"Update the display name for this token.\"],\"ScJ9fj\":[\"Privacy policy\"],\"TP9_K5\":[\"Token\"],\"TfpFBG\":[\"Your choice\"],\"TqCsD2\":[[\"totalPending\"],\" requests are waiting. Review each one separately.\"],\"Tz0i8g\":[\"Settings\"],\"UMeccq\":[\"Key generation failed\"],\"UO-3EH\":[\"This token did not finish enrollment and cannot receive push requests until rollout succeeds.\"],\"URmyfc\":[\"Details\"],\"UbRKMZ\":[\"Pending\"],\"VIZNLO\":[\"Registering public key\"],\"VSWXlh\":[\"Feedback details\"],\"WRdUP2\":[\"Camera access is required to scan the QR code and pair a token.\"],\"Weq9zb\":[\"General\"],\"YirHq7\":[\"Feedback\"],\"Z2CsKg\":[\"Don't share anonymous reports\"],\"Z7ZXbT\":[\"Approve\"],\"ZDIydz\":[\"Get started\"],\"ZYIECH\":[\"Notifications are disabled\"],\"_ijUgq\":[\"Get notified when a sign-in needs your approval.\"],\"aAIQg2\":[\"Appearance\"],\"b75KIN\":[\"Change the app language in system settings\"],\"bfECx6\":[\"This token could not be registered with the enrollment server. Check your connection or try again later.\"],\"c7uz-G\":[\"Turn on notifications in Settings so you can respond when a request arrives.\"],\"cFCKYZ\":[\"Deny\"],\"cnGeoo\":[\"Delete\"],\"cu6w0K\":[\"Token approvals, denials, refresh events, and enrollment activity will appear here.\"],\"dEgA5A\":[\"Cancel\"],\"dN-1Th\":[\"Open with your Authenticator App\"],\"dum6Mb\":[\"Refreshing...\"],\"e4u4yb\":[\"Tell us what worked well or what we can improve.\"],\"eMwMfT\":[\"Enable notifications to receive push approval requests on this device.\"],\"ePK91l\":[\"Edit\"],\"g3UF2V\":[\"Accept\"],\"g7oOIY\":[\"Search tokens\"],\"g7yB5h\":[\"The server response could not be parsed or did not include the expected token material.\"],\"hD6g-j\":[\"Welcome to eduMFA\"],\"hevXA2\":[\"Last failed\"],\"i5IMTS\":[\"Add your first eduMFA token to approve sign-ins securely from this device.\"],\"iAqBor\":[\"Enter a label for this token.\"],\"iDNBZe\":[\"Notifications\"],\"iQmbPb\":[\"License\"],\"jbq7j2\":[\"Decline\"],\"jiR92q\":[\"Pair new Push Token\"],\"jrtxCX\":[\"Review eduMFA\"],\"k4nIkP\":[\"No tokens found\"],\"kLttbL\":[\"Registration failed\"],\"lF4FiS\":[\"No QR code found\"],\"liw26p\":[\"Anonymous reports\"],\"mRd7qL\":[\"Feedback could not be sent. Please try again.\"],\"mVSlBU\":[\"Enrolled\"],\"mfi038\":[\"Help improve eduMFA\"],\"ml5TCa\":[\"The provided QR code isn't a valid eduMFA token.\"],\"o4_Dwt\":[\"Camera access is turned off\"],\"pphVNl\":[\"Your image did not contain a QR code.\"],\"pvnfJD\":[\"Dark\"],\"q9XKrW\":[\"Response parsing failed\"],\"rjGI_Q\":[\"Privacy\"],\"t-Mdx7\":[\"Choose the category that best matches your feedback. Your message and optional contact details will be sent to eduMFA through Sentry.\"],\"tfDRzk\":[\"Save\"],\"tsg9Ze\":[\"This token could not be refreshed. It may have been removed on the server or the institution hosting the push service may be having a technical issue.\"],\"u5FNLt\":[\"Crash and error reports\"],\"uAQUqI\":[\"Status\"],\"uI1l6M\":[\"Label required\"],\"uZ25pH\":[\"Contact (optional)\"],\"uh8i5u\":[\"Retry Rollout\"],\"uyJsf6\":[\"About\"],\"vXIe7J\":[\"Language\"],\"wJAhPZ\":[\"Name (optional)\"],\"xGVfLh\":[\"Continue\"],\"xPl9LS\":[\"Try another token name, issuer, or serial number.\"],\"xkjgVR\":[\"Failed to add token\"],\"xoqD_n\":[\"Enter a valid email address.\"],\"yOrM5-\":[\"Thank you for your feedback\"],\"ypfsn_\":[\"Serial\"],\"yz7wBu\":[\"Close\"],\"zXZJoN\":[\"Add token\"]}")};
\ No newline at end of file
diff --git a/src/locales/en/messages.po b/src/locales/en/messages.po
index 8dd280a..5a9d65f 100644
--- a/src/locales/en/messages.po
+++ b/src/locales/en/messages.po
@@ -39,6 +39,11 @@ msgstr "Add token"
msgid "Add your first eduMFA token to approve sign-ins securely from this device."
msgstr "Add your first eduMFA token to approve sign-ins securely from this device."
+#: src/components/feedback-form.android.tsx:111
+#: src/components/feedback-form.ios.tsx:130
+msgid "Add your name and email if you would like us to contact you with follow-up questions or updates about your feedback."
+msgstr "Add your name and email if you would like us to contact you with follow-up questions or updates about your feedback."
+
#: src/components/token-add/camera-permission-splash.tsx:37
msgid "Allow camera access"
msgstr "Allow camera access"
@@ -63,6 +68,10 @@ msgstr "Are you sure you want to delete this token?"
msgid "Automatic"
msgstr "Automatic"
+#: src/hooks/use-feedback-form.ts:21
+msgid "Bug report"
+msgstr "Bug report"
+
#: src/components/token-add/camera-permission-splash.tsx:44
msgid "Camera access is required to scan the QR code and pair a token."
msgstr "Camera access is required to scan the QR code and pair a token."
@@ -79,18 +88,31 @@ msgstr "Cancel"
msgid "Change the app language in system settings"
msgstr "Change the app language in system settings"
+#: src/components/feedback-form.ios.tsx:95
+msgid "Choose the category that best matches your feedback. Your message and optional contact details will be sent to eduMFA through Sentry."
+msgstr "Choose the category that best matches your feedback. Your message and optional contact details will be sent to eduMFA through Sentry."
+
#: src/components/onboarding-sequence.tsx:112
msgid "Choose whether to share anonymous crash and error reports."
msgstr "Choose whether to share anonymous crash and error reports."
+#: src/components/feedback-form.android.tsx:81
+#: src/components/feedback-form.ios.tsx:83
+msgid "Close"
+msgstr "Close"
+
+#: src/components/feedback-form.ios.tsx:127
+msgid "Contact (optional)"
+msgstr "Contact (optional)"
+
#: src/components/onboarding-sequence/step-actions.tsx:85
#: src/components/onboarding-sequence/step-actions.tsx:106
#: src/components/token-add/camera-permission-splash.tsx:64
msgid "Continue"
msgstr "Continue"
-#: src/app/settings/index.tsx:172
#: src/app/settings/index.tsx:178
+#: src/app/settings/index.tsx:184
msgid "Crash and error reports"
msgstr "Crash and error reports"
@@ -128,6 +150,11 @@ msgstr "Dismiss"
msgid "Don't share anonymous reports"
msgstr "Don't share anonymous reports"
+#: src/components/feedback-form.android.tsx:66
+#: src/components/feedback-form.ios.tsx:54
+msgid "Done"
+msgstr "Done"
+
#: src/app/index.tsx:252
#: src/app/token/[tokenId].tsx:192
msgid "Edit"
@@ -141,6 +168,11 @@ msgstr "Edit token"
msgid "eduMFA uses {licenseCount} open-source packages. Package details are generated from the installed production dependency graph."
msgstr "eduMFA uses {licenseCount} open-source packages. Package details are generated from the installed production dependency graph."
+#: src/components/feedback-form.android.tsx:134
+#: src/components/feedback-form.ios.tsx:144
+msgid "Email (optional)"
+msgstr "Email (optional)"
+
#: src/components/token-add/camera-permission-denied.tsx:40
msgid "Enable camera access in Settings to scan QR codes."
msgstr "Enable camera access in Settings to scan QR codes."
@@ -165,17 +197,47 @@ msgstr "Enrollment response failed"
msgid "Enter a label for this token."
msgstr "Enter a label for this token."
+#: src/hooks/use-feedback-form.ts:29
+msgid "Enter a valid email address."
+msgstr "Enter a valid email address."
+
#: src/hooks/use-handle-token-uri.ts:50
#: src/hooks/use-handle-token-uri.ts:60
#: src/hooks/use-handle-token-uri.ts:92
msgid "Failed to add token"
msgstr "Failed to add token"
+#: src/hooks/use-feedback-form.ts:23
+msgid "Feature request"
+msgstr "Feature request"
+
+#: src/app/settings/index.tsx:130
+msgid "Feedback"
+msgstr "Feedback"
+
+#: src/app/settings/index.tsx:128
+#~ msgid "Feedback (Expo UI)"
+#~ msgstr "Feedback (Expo UI)"
+
+#: src/hooks/use-feedback-form.ts:70
+msgid "Feedback could not be sent. Please try again."
+msgstr "Feedback could not be sent. Please try again."
+
+#: src/components/feedback-form.ios.tsx:90
+msgid "Feedback details"
+msgstr "Feedback details"
+
+#: src/components/feedback-form.android.tsx:92
+#: src/components/feedback-form.ios.tsx:100
+msgid "Feedback type"
+msgstr "Feedback type"
+
#: src/components/token-detail/token-detail-utils.ts:85
msgid "Finalizing enrollment"
msgstr "Finalizing enrollment"
#: src/app/settings/index.tsx:104
+#: src/hooks/use-feedback-form.ts:22
msgid "General"
msgstr "General"
@@ -191,7 +253,7 @@ msgstr "Get notified when a sign-in needs your approval."
msgid "Get started"
msgstr "Get started"
-#: src/app/settings/index.tsx:151
+#: src/app/settings/index.tsx:157
msgid "GitHub"
msgstr "GitHub"
@@ -251,6 +313,11 @@ msgstr "License information is unavailable."
msgid "Light"
msgstr "Light"
+#: src/components/feedback-form.android.tsx:120
+#: src/components/feedback-form.ios.tsx:136
+msgid "Name (optional)"
+msgstr "Name (optional)"
+
#: src/components/onboarding-sequence.tsx:104
msgid "Never miss a sign-in request"
msgstr "Never miss a sign-in request"
@@ -303,7 +370,7 @@ msgstr "Open Settings"
msgid "Open with your Authenticator App"
msgstr "Open with your Authenticator App"
-#: src/app/settings/index.tsx:163
+#: src/app/settings/index.tsx:169
#: src/app/settings/licenses.tsx:83
msgid "Open-source licenses"
msgstr "Open-source licenses"
@@ -324,11 +391,15 @@ msgstr "Pending"
msgid "Place the QR code inside the frame"
msgstr "Place the QR code inside the frame"
-#: src/app/settings/index.tsx:168
+#: src/hooks/use-feedback-form.ts:50
+msgid "Please describe your feedback."
+msgstr "Please describe your feedback."
+
+#: src/app/settings/index.tsx:174
msgid "Privacy"
msgstr "Privacy"
-#: src/app/settings/index.tsx:142
+#: src/app/settings/index.tsx:148
msgid "Privacy policy"
msgstr "Privacy policy"
@@ -373,7 +444,7 @@ msgstr "Response parsing failed"
msgid "Retry Rollout"
msgstr "Retry Rollout"
-#: src/app/settings/index.tsx:130
+#: src/app/settings/index.tsx:136
msgid "Review eduMFA"
msgstr "Review eduMFA"
@@ -402,10 +473,16 @@ msgstr "Search Authenticator Apps"
msgid "Search tokens"
msgstr "Search tokens"
-#: src/app/settings/index.tsx:170
+#: src/app/settings/index.tsx:176
msgid "Send anonymized crash and error diagnostics"
msgstr "Send anonymized crash and error diagnostics"
+#: src/app/settings/feedback.tsx:23
+#: src/components/feedback-form.android.tsx:77
+#: src/components/feedback-form.ios.tsx:79
+msgid "Send feedback"
+msgstr "Send feedback"
+
#: src/components/token-detail/token-overview-content.tsx:169
msgid "Serial"
msgstr "Serial"
@@ -418,7 +495,7 @@ msgstr "Server message"
msgid "Server registration failed"
msgstr "Server registration failed"
-#: src/app/settings/index.tsx:194
+#: src/app/settings/index.tsx:200
msgid "Settings"
msgstr "Settings"
@@ -434,6 +511,20 @@ msgstr "Sign-in requests may go unnoticed"
msgid "Status"
msgstr "Status"
+#: src/components/feedback-form.android.tsx:163
+#: src/components/feedback-form.ios.tsx:170
+msgid "Submit feedback"
+msgstr "Submit feedback"
+
+#: src/components/feedback-form.android.tsx:89
+msgid "Tell us what worked well or what we can improve."
+msgstr "Tell us what worked well or what we can improve."
+
+#: src/components/feedback-form.android.tsx:60
+#: src/components/feedback-form.ios.tsx:50
+msgid "Thank you for your feedback"
+msgstr "Thank you for your feedback"
+
#: src/components/token-detail/token-detail-utils.ts:49
msgid "The device could not create the RSA key pair required for this token. Retry rollout and keep the app open while it runs."
msgstr "The device could not create the RSA key pair required for this token. Retry rollout and keep the app open while it runs."
@@ -502,7 +593,7 @@ msgstr "Upload QR Code"
msgid "Version {itemVersion} · {itemLicense}"
msgstr "Version {itemVersion} · {itemLicense}"
-#: src/app/settings/index.tsx:157
+#: src/app/settings/index.tsx:163
msgid "Website"
msgstr "Website"
@@ -514,6 +605,11 @@ msgstr "Welcome"
msgid "Welcome to eduMFA"
msgstr "Welcome to eduMFA"
+#: src/components/feedback-form.android.tsx:151
+#: src/components/feedback-form.ios.tsx:115
+msgid "What would you like us to know?"
+msgstr "What would you like us to know?"
+
#: src/components/onboarding-sequence/step-actions.tsx:97
msgid "You’re ready to receive and approve sign-in requests."
msgstr "You’re ready to receive and approve sign-in requests."
@@ -522,6 +618,15 @@ msgstr "You’re ready to receive and approve sign-in requests."
msgid "Your choice"
msgstr "Your choice"
+#: src/components/feedback-form.android.tsx:63
+#: src/components/feedback-form.ios.tsx:52
+msgid "Your feedback was submitted successfully."
+msgstr "Your feedback was submitted successfully."
+
#: src/hooks/use-handle-token-uri.ts:36
msgid "Your image did not contain a QR code."
msgstr "Your image did not contain a QR code."
+
+#: src/components/feedback-form.android.tsx:160
+msgid "Your message and optional contact details will be sent to eduMFA through Sentry."
+msgstr "Your message and optional contact details will be sent to eduMFA through Sentry."
diff --git a/src/utils/sentry.ts b/src/utils/sentry.ts
index 1a6c352..6380bb1 100644
--- a/src/utils/sentry.ts
+++ b/src/utils/sentry.ts
@@ -1,4 +1,9 @@
-import type { Breadcrumb, ErrorEvent, Exception } from "@sentry/react-native";
+import type {
+ Breadcrumb,
+ ErrorEvent,
+ Exception,
+ SendFeedbackParams,
+} from "@sentry/react-native";
import * as Sentry from "@sentry/react-native";
import { isRunningInExpoGo } from "expo";
import type { ComponentType } from "react";
@@ -16,6 +21,8 @@ const OTP_AUTH_URI_PATTERN = /otpauth:\/\/[^\s"'<>)]*/gi;
let sentryInitialized = false;
let sentryTrackingEnabled = false;
+export type UserFeedbackType = "bug_report" | "feature_request" | "general";
+
export function setSentryTrackingEnabled(enabled: boolean): void {
if (enabled) {
if (!sentryTrackingEnabled) {
@@ -29,6 +36,7 @@ export function setSentryTrackingEnabled(enabled: boolean): void {
}
sentryTrackingEnabled = false;
+ sentryInitialized = false;
void Sentry.close().catch((error: unknown) => {
if (__DEV__) {
@@ -37,13 +45,32 @@ export function setSentryTrackingEnabled(enabled: boolean): void {
});
}
-function initSentry(enabled: boolean): void {
- const enableNative = !isRunningInExpoGo();
+export function submitUserFeedback(
+ feedback: Pick,
+ type: UserFeedbackType,
+): void {
+ if (!sentryTrackingEnabled) {
+ // Explicit feedback is allowed independently of automatic reporting.
+ // Other events remain blocked by beforeSend below.
+ initSentry(false);
+ }
+
+ Sentry.withScope((scope) => {
+ scope.setTag("feedback.type", type);
+ return Sentry.captureFeedback({
+ ...feedback,
+ source: "settings",
+ });
+ });
+}
+
+function initSentry(trackingEnabled: boolean): void {
+ const enableNative = trackingEnabled && !isRunningInExpoGo();
Sentry.init({
dsn: SENTRY_DSN,
environment: SENTRY_ENVIRONMENT,
- enabled,
+ enabled: true,
debug: false,
sendDefaultPii: false,
sampleRate: 1,
@@ -72,11 +99,18 @@ function initSentry(enabled: boolean): void {
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
beforeBreadcrumb: sanitizeBreadcrumb,
- beforeSend: sanitizeEvent,
+ beforeSend: (event) => {
+ if (event.type === "feedback") {
+ return event;
+ }
+
+ return sentryTrackingEnabled ? sanitizeEvent(event) : null;
+ },
+ integrations: [Sentry.feedbackIntegration({})],
});
sentryInitialized = true;
- sentryTrackingEnabled = enabled;
+ sentryTrackingEnabled = trackingEnabled;
}
export function withSentryRoot