From 817eac82107f668f71ab51e8d071ca62c472e62d Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 27 Nov 2024 14:00:17 +0200
Subject: [PATCH 01/73] Update the client implementation to use the new capture
 feedback js api

---
 packages/core/src/js/client.ts    | 16 +++-----
 packages/core/test/client.test.ts | 64 ++++++++++++++-----------------
 packages/core/test/testutils.ts   |  9 +----
 3 files changed, 34 insertions(+), 55 deletions(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index b9fe2ad27d..d2f7b1ab71 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -1,4 +1,4 @@
-import { eventFromException, eventFromMessage } from '@sentry/browser';
+import { captureFeedback as captureFeedbackApi, eventFromException, eventFromMessage } from '@sentry/browser';
 import { BaseClient } from '@sentry/core';
 import type {
   ClientReportEnvelope,
@@ -7,9 +7,9 @@ import type {
   Event,
   EventHint,
   Outcome,
+  SendFeedbackParams,
   SeverityLevel,
   TransportMakeRequestResponse,
-  UserFeedback,
 } from '@sentry/types';
 import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils';
 import { Alert } from 'react-native';
@@ -20,7 +20,7 @@ import { getDefaultSidecarUrl } from './integrations/spotlight';
 import type { ReactNativeClientOptions } from './options';
 import type { mobileReplayIntegration } from './replay/mobilereplay';
 import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay';
-import { createUserFeedbackEnvelope, items } from './utils/envelope';
+import { items } from './utils/envelope';
 import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
 import { mergeOutcomes } from './utils/outcome';
 import { ReactNativeLibraries } from './utils/rnlibraries';
@@ -86,14 +86,8 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
   /**
    * Sends user feedback to Sentry.
    */
-  public captureUserFeedback(feedback: UserFeedback): void {
-    const envelope = createUserFeedbackEnvelope(feedback, {
-      metadata: this._options._metadata,
-      dsn: this.getDsn(),
-      tunnel: undefined,
-    });
-    // eslint-disable-next-line @typescript-eslint/no-floating-promises
-    this.sendEnvelope(envelope);
+  public captureFeedback(feedback: SendFeedbackParams): void {
+    captureFeedbackApi(feedback);
   }
 
   /**
diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts
index a0e5b13201..9d4a430089 100644
--- a/packages/core/test/client.test.ts
+++ b/packages/core/test/client.test.ts
@@ -1,8 +1,15 @@
 import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative';
 jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative);
 
-import { defaultStackParser } from '@sentry/browser';
-import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/types';
+import { captureFeedback as captureFeedbackApi, defaultStackParser } from '@sentry/browser';
+import type {
+  Envelope,
+  Event,
+  Outcome,
+  SendFeedbackParams,
+  Transport,
+  TransportMakeRequestResponse,
+} from '@sentry/types';
 import { rejectedSyncPromise, SentryError } from '@sentry/utils';
 import * as RN from 'react-native';
 
@@ -19,7 +26,6 @@ import {
   envelopeItems,
   firstArg,
   getMockSession,
-  getMockUserFeedback,
   getSyncPromiseRejectOnFirstCall,
 } from './testutils';
 
@@ -76,6 +82,14 @@ jest.mock(
   }),
 );
 
+jest.mock('@sentry/browser', () => {
+  const actual = jest.requireActual('@sentry/browser');
+  return {
+    ...actual,
+    captureFeedback: jest.fn(),
+  };
+});
+
 const EXAMPLE_DSN = 'https://6890c2f6677340daa4804f8194804ea2@o19635.ingest.sentry.io/148053';
 
 const DEFAULT_OPTIONS: ReactNativeClientOptions = {
@@ -187,15 +201,6 @@ describe('Tests ReactNativeClient', () => {
       expect(mockTransport.send).not.toBeCalled();
     });
 
-    test('captureUserFeedback does not call transport when enabled false', () => {
-      const mockTransport = createMockTransport();
-      const client = createDisabledClientWith(mockTransport);
-
-      client.captureUserFeedback(getMockUserFeedback());
-
-      expect(mockTransport.send).not.toBeCalled();
-    });
-
     function createDisabledClientWith(transport: Transport) {
       return new ReactNativeClient({
         ...DEFAULT_OPTIONS,
@@ -290,34 +295,26 @@ describe('Tests ReactNativeClient', () => {
   });
 
   describe('UserFeedback', () => {
-    test('sends UserFeedback to native Layer', () => {
+    test('sends UserFeedback', () => {
       const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve());
       const client = new ReactNativeClient({
         ...DEFAULT_OPTIONS,
         dsn: EXAMPLE_DSN,
-        transport: () => ({
-          send: mockTransportSend,
-          flush: jest.fn(),
-        }),
       });
+      jest.mock('@sentry/browser', () => ({
+        captureFeedback: jest.fn(),
+      }));
 
-      client.captureUserFeedback({
-        comments: 'Test Comments',
+      const feedback: SendFeedbackParams = {
+        message: 'Test Comments',
         email: 'test@email.com',
         name: 'Test User',
-        event_id: 'testEvent123',
-      });
+        associatedEventId: 'testEvent123',
+      };
 
-      expect(mockTransportSend.mock.calls[0][firstArg][envelopeHeader].event_id).toEqual('testEvent123');
-      expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemHeader].type).toEqual(
-        'user_report',
-      );
-      expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]).toEqual({
-        comments: 'Test Comments',
-        email: 'test@email.com',
-        name: 'Test User',
-        event_id: 'testEvent123',
-      });
+      client.captureFeedback(feedback);
+
+      expect(captureFeedbackApi).toHaveBeenCalledWith(feedback);
     });
   });
 
@@ -417,11 +414,6 @@ describe('Tests ReactNativeClient', () => {
       client.captureSession(getMockSession());
       expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
     });
-
-    test('send SdkInfo in the user feedback envelope header', () => {
-      client.captureUserFeedback(getMockUserFeedback());
-      expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
-    });
   });
 
   describe('event data enhancement', () => {
diff --git a/packages/core/test/testutils.ts b/packages/core/test/testutils.ts
index 4548eef092..593a51f838 100644
--- a/packages/core/test/testutils.ts
+++ b/packages/core/test/testutils.ts
@@ -1,4 +1,4 @@
-import type { Session, Transport, UserFeedback } from '@sentry/types';
+import type { Session, Transport } from '@sentry/types';
 import { rejectedSyncPromise } from '@sentry/utils';
 
 export type MockInterface<T> = {
@@ -36,13 +36,6 @@ export const getMockSession = (): Session => ({
   }),
 });
 
-export const getMockUserFeedback = (): UserFeedback => ({
-  comments: 'comments_test_value',
-  email: 'email_test_value',
-  name: 'name_test_value',
-  event_id: 'event_id_test_value',
-});
-
 export const getSyncPromiseRejectOnFirstCall = <Y extends any[]>(reason: unknown): jest.Mock => {
   let shouldSyncReject = true;
   return jest.fn((..._args: Y) => {

From 5370a990006b5d53b738ebc250c7e240a7bffa36 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 27 Nov 2024 14:00:54 +0200
Subject: [PATCH 02/73] Updates SDK API

---
 packages/core/src/js/index.ts | 13 ++++++++++++-
 packages/core/src/js/sdk.tsx  | 18 ++++++++++++++++--
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index f62a8624eb..079ec253d4 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -4,6 +4,7 @@ export type {
   SdkInfo,
   Event,
   Exception,
+  SendFeedbackParams,
   SeverityLevel,
   StackFrame,
   Stacktrace,
@@ -59,7 +60,17 @@ export { SDK_NAME, SDK_VERSION } from './version';
 export type { ReactNativeOptions } from './options';
 export { ReactNativeClient } from './client';
 
-export { init, wrap, nativeCrash, flush, close, captureUserFeedback, withScope, crashedLastRun } from './sdk';
+export {
+  init,
+  wrap,
+  nativeCrash,
+  flush,
+  close,
+  captureFeedback,
+  captureUserFeedback,
+  withScope,
+  crashedLastRun,
+} from './sdk';
 export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';
 
 export {
diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx
index 039b44850d..f17a6ef058 100644
--- a/packages/core/src/js/sdk.tsx
+++ b/packages/core/src/js/sdk.tsx
@@ -4,7 +4,7 @@ import {
   defaultStackParser,
   makeFetchTransport,
 } from '@sentry/react';
-import type { Breadcrumb, BreadcrumbHint, Integration, Scope, UserFeedback } from '@sentry/types';
+import type { Breadcrumb, BreadcrumbHint, Integration, Scope, SendFeedbackParams, UserFeedback } from '@sentry/types';
 import { logger, stackParserFromStackParserOptions } from '@sentry/utils';
 import * as React from 'react';
 
@@ -219,9 +219,23 @@ export async function close(): Promise<void> {
 
 /**
  * Captures user feedback and sends it to Sentry.
+ * @deprecated Use `Sentry.captureFeedback` instead.
  */
 export function captureUserFeedback(feedback: UserFeedback): void {
-  getClient<ReactNativeClient>()?.captureUserFeedback(feedback);
+  const feedbackParams = {
+    name: feedback.name,
+    email: feedback.email,
+    message: feedback.comments,
+    associatedEventId: feedback.event_id,
+  };
+  captureFeedback(feedbackParams);
+}
+
+/**
+ * Captures user feedback and sends it to Sentry.
+ */
+export function captureFeedback(feedbackParams: SendFeedbackParams): void {
+  getClient<ReactNativeClient>()?.captureFeedback(feedbackParams);
 }
 
 /**

From 42e2fa14823f067a6b4088770f5ff0aea9a22b69 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 27 Nov 2024 14:01:18 +0200
Subject: [PATCH 03/73] Adds new feedback button in the sample

---
 .../src/components/UserFeedbackModal.tsx      | 19 ++++++++++++++++++-
 .../src/components/UserFeedbackModal.tsx      | 19 ++++++++++++++++++-
 2 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/samples/react-native-macos/src/components/UserFeedbackModal.tsx b/samples/react-native-macos/src/components/UserFeedbackModal.tsx
index 60e17af757..1f8dd6a4a3 100644
--- a/samples/react-native-macos/src/components/UserFeedbackModal.tsx
+++ b/samples/react-native-macos/src/components/UserFeedbackModal.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { View, StyleSheet, Text, TextInput, Image, Button } from 'react-native';
 import * as Sentry from '@sentry/react-native';
-import { UserFeedback } from '@sentry/react-native';
+import { SendFeedbackParams, UserFeedback } from '@sentry/react-native';
 
 export const DEFAULT_COMMENTS = "It's broken again! Please fix it.";
 
@@ -48,6 +48,23 @@ export function UserFeedbackModal(props: { onDismiss: () => void }) {
             }}
           />
           <View style={styles.buttonSpacer} />
+          <Button
+            title="Send feedback without event"
+            color="#6C5FC7"
+            onPress={async () => {
+              onDismiss();
+
+              const userFeedback: SendFeedbackParams = {
+                message: comments,
+                name: 'John Doe',
+                email: 'john@doe.com',
+              };
+
+              Sentry.captureFeedback(userFeedback);
+              clearComments();
+            }}
+          />
+          <View style={styles.buttonSpacer} />
           <Button
             title="Close"
             color="#6C5FC7"
diff --git a/samples/react-native/src/components/UserFeedbackModal.tsx b/samples/react-native/src/components/UserFeedbackModal.tsx
index 60e17af757..1f8dd6a4a3 100644
--- a/samples/react-native/src/components/UserFeedbackModal.tsx
+++ b/samples/react-native/src/components/UserFeedbackModal.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { View, StyleSheet, Text, TextInput, Image, Button } from 'react-native';
 import * as Sentry from '@sentry/react-native';
-import { UserFeedback } from '@sentry/react-native';
+import { SendFeedbackParams, UserFeedback } from '@sentry/react-native';
 
 export const DEFAULT_COMMENTS = "It's broken again! Please fix it.";
 
@@ -48,6 +48,23 @@ export function UserFeedbackModal(props: { onDismiss: () => void }) {
             }}
           />
           <View style={styles.buttonSpacer} />
+          <Button
+            title="Send feedback without event"
+            color="#6C5FC7"
+            onPress={async () => {
+              onDismiss();
+
+              const userFeedback: SendFeedbackParams = {
+                message: comments,
+                name: 'John Doe',
+                email: 'john@doe.com',
+              };
+
+              Sentry.captureFeedback(userFeedback);
+              clearComments();
+            }}
+          />
+          <View style={styles.buttonSpacer} />
           <Button
             title="Close"
             color="#6C5FC7"

From 514b102ba091403966c44db4a08146684278422f Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 27 Nov 2024 14:12:21 +0200
Subject: [PATCH 04/73] Adds changelog

---
 CHANGELOG.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9ce7b8f1a..65ea93baf8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,26 @@
 
 ## Unreleased
 
+### Features
+
+- Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320))
+
+  ```jsx
+  import * as Sentry from "@sentry/react-native";
+  import { SendFeedbackParams } from "@sentry/react-native";
+
+  const eventId = Sentry.captureMessage("My Message");
+  // OR: const eventId = Sentry.lastEventId();
+
+  const userFeedback: SendFeedbackParams = {
+    name: "John Doe",
+    email: "john@doe.com",
+    message: "Hello World!",
+    associatedEventId: eventId,// Optional
+  };
+  Sentry.captureFeedback(userFeedback);
+  ```
+
 ### Fixes
 
 - Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))

From 0935bbdca47ad0afcc19e7260afc1f4b33d5fefe Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 27 Nov 2024 14:17:08 +0200
Subject: [PATCH 05/73] Removes unused mock

---
 packages/core/test/client.test.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts
index 9d4a430089..879f0fb4ce 100644
--- a/packages/core/test/client.test.ts
+++ b/packages/core/test/client.test.ts
@@ -296,7 +296,6 @@ describe('Tests ReactNativeClient', () => {
 
   describe('UserFeedback', () => {
     test('sends UserFeedback', () => {
-      const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve());
       const client = new ReactNativeClient({
         ...DEFAULT_OPTIONS,
         dsn: EXAMPLE_DSN,

From 9ea5496ccffe8408a52c966e2601322fc014b12c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 13:42:48 +0200
Subject: [PATCH 06/73] Update CHANGELOG.md

Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
---
 CHANGELOG.md | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 65ea93baf8..0f4cfc6fc5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,20 +12,18 @@
 
 - Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320))
 
+
   ```jsx
   import * as Sentry from "@sentry/react-native";
-  import { SendFeedbackParams } from "@sentry/react-native";
 
-  const eventId = Sentry.captureMessage("My Message");
-  // OR: const eventId = Sentry.lastEventId();
+  const eventId = Sentry.lastEventId();
 
-  const userFeedback: SendFeedbackParams = {
+  Sentry.captureFeedback({
     name: "John Doe",
     email: "john@doe.com",
     message: "Hello World!",
-    associatedEventId: eventId,// Optional
-  };
-  Sentry.captureFeedback(userFeedback);
+    associatedEventId: eventId, // optional
+  });
   ```
 
 ### Fixes

From da0d4ac091e20b0232f841ff9a5597597e47070c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 15:41:33 +0200
Subject: [PATCH 07/73] Directly use captureFeedback from sentry/core

---
 packages/core/src/js/client.ts    |  8 ------
 packages/core/src/js/index.ts     | 13 ++--------
 packages/core/src/js/sdk.tsx      | 11 ++------
 packages/core/test/client.test.ts | 42 ++-----------------------------
 4 files changed, 6 insertions(+), 68 deletions(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index d2f7b1ab71..5708b52f3a 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -7,7 +7,6 @@ import type {
   Event,
   EventHint,
   Outcome,
-  SendFeedbackParams,
   SeverityLevel,
   TransportMakeRequestResponse,
 } from '@sentry/types';
@@ -83,13 +82,6 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
     });
   }
 
-  /**
-   * Sends user feedback to Sentry.
-   */
-  public captureFeedback(feedback: SendFeedbackParams): void {
-    captureFeedbackApi(feedback);
-  }
-
   /**
    * @inheritdoc
    */
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index 079ec253d4..4aa7e2fa21 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -17,6 +17,7 @@ export {
   addBreadcrumb,
   captureException,
   captureEvent,
+  captureFeedback,
   captureMessage,
   Scope,
   setContext,
@@ -60,17 +61,7 @@ export { SDK_NAME, SDK_VERSION } from './version';
 export type { ReactNativeOptions } from './options';
 export { ReactNativeClient } from './client';
 
-export {
-  init,
-  wrap,
-  nativeCrash,
-  flush,
-  close,
-  captureFeedback,
-  captureUserFeedback,
-  withScope,
-  crashedLastRun,
-} from './sdk';
+export { init, wrap, nativeCrash, flush, close, captureUserFeedback, withScope, crashedLastRun } from './sdk';
 export { TouchEventBoundary, withTouchEventBoundary } from './touchevents';
 
 export {
diff --git a/packages/core/src/js/sdk.tsx b/packages/core/src/js/sdk.tsx
index f17a6ef058..448ce99148 100644
--- a/packages/core/src/js/sdk.tsx
+++ b/packages/core/src/js/sdk.tsx
@@ -1,5 +1,5 @@
 /* eslint-disable complexity */
-import { getClient, getGlobalScope,getIntegrationsToSetup, getIsolationScope,initAndBind, withScope as coreWithScope } from '@sentry/core';
+import { captureFeedback, getClient, getGlobalScope,getIntegrationsToSetup, getIsolationScope,initAndBind, withScope as coreWithScope } from '@sentry/core';
 import {
   defaultStackParser,
   makeFetchTransport,
@@ -222,7 +222,7 @@ export async function close(): Promise<void> {
  * @deprecated Use `Sentry.captureFeedback` instead.
  */
 export function captureUserFeedback(feedback: UserFeedback): void {
-  const feedbackParams = {
+  const feedbackParams: SendFeedbackParams = {
     name: feedback.name,
     email: feedback.email,
     message: feedback.comments,
@@ -231,13 +231,6 @@ export function captureUserFeedback(feedback: UserFeedback): void {
   captureFeedback(feedbackParams);
 }
 
-/**
- * Captures user feedback and sends it to Sentry.
- */
-export function captureFeedback(feedbackParams: SendFeedbackParams): void {
-  getClient<ReactNativeClient>()?.captureFeedback(feedbackParams);
-}
-
 /**
  * Creates a new scope with and executes the given operation within.
  * The scope is automatically removed once the operation
diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts
index 879f0fb4ce..3d732e194c 100644
--- a/packages/core/test/client.test.ts
+++ b/packages/core/test/client.test.ts
@@ -1,15 +1,8 @@
 import * as mockedtimetodisplaynative from './tracing/mockedtimetodisplaynative';
 jest.mock('../src/js/tracing/timetodisplaynative', () => mockedtimetodisplaynative);
 
-import { captureFeedback as captureFeedbackApi, defaultStackParser } from '@sentry/browser';
-import type {
-  Envelope,
-  Event,
-  Outcome,
-  SendFeedbackParams,
-  Transport,
-  TransportMakeRequestResponse,
-} from '@sentry/types';
+import { defaultStackParser } from '@sentry/browser';
+import type { Envelope, Event, Outcome, Transport, TransportMakeRequestResponse } from '@sentry/types';
 import { rejectedSyncPromise, SentryError } from '@sentry/utils';
 import * as RN from 'react-native';
 
@@ -82,14 +75,6 @@ jest.mock(
   }),
 );
 
-jest.mock('@sentry/browser', () => {
-  const actual = jest.requireActual('@sentry/browser');
-  return {
-    ...actual,
-    captureFeedback: jest.fn(),
-  };
-});
-
 const EXAMPLE_DSN = 'https://6890c2f6677340daa4804f8194804ea2@o19635.ingest.sentry.io/148053';
 
 const DEFAULT_OPTIONS: ReactNativeClientOptions = {
@@ -294,29 +279,6 @@ describe('Tests ReactNativeClient', () => {
     });
   });
 
-  describe('UserFeedback', () => {
-    test('sends UserFeedback', () => {
-      const client = new ReactNativeClient({
-        ...DEFAULT_OPTIONS,
-        dsn: EXAMPLE_DSN,
-      });
-      jest.mock('@sentry/browser', () => ({
-        captureFeedback: jest.fn(),
-      }));
-
-      const feedback: SendFeedbackParams = {
-        message: 'Test Comments',
-        email: 'test@email.com',
-        name: 'Test User',
-        associatedEventId: 'testEvent123',
-      };
-
-      client.captureFeedback(feedback);
-
-      expect(captureFeedbackApi).toHaveBeenCalledWith(feedback);
-    });
-  });
-
   describe('attachStacktrace', () => {
     let mockTransportSend: jest.Mock;
     let client: ReactNativeClient;

From 3e36c6dcfcc20c32f8d0d0bc5585cf1d6dd4df30 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 15:41:49 +0200
Subject: [PATCH 08/73] Use import from core

---
 packages/core/src/js/client.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index 5708b52f3a..ba794ed4d8 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -1,4 +1,5 @@
-import { captureFeedback as captureFeedbackApi, eventFromException, eventFromMessage } from '@sentry/browser';
+import { eventFromMessage } from '@sentry/core';
+import { eventFromException } from '@sentry/browser';
 import { BaseClient } from '@sentry/core';
 import type {
   ClientReportEnvelope,

From 5f3df64d0ecca29e826782239e37d796cee81b7a Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 15:46:17 +0200
Subject: [PATCH 09/73] Fixes imports order lint issue

---
 packages/core/src/js/client.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index ba794ed4d8..e8de5e92fe 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -1,6 +1,5 @@
-import { eventFromMessage } from '@sentry/core';
 import { eventFromException } from '@sentry/browser';
-import { BaseClient } from '@sentry/core';
+import { BaseClient, eventFromMessage } from '@sentry/core';
 import type {
   ClientReportEnvelope,
   ClientReportItem,

From 71b28e8e48f5c997d74de43221fbe7c7bdb3304f Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 15:56:12 +0200
Subject: [PATCH 10/73] Fixes build issue

---
 packages/core/src/js/client.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index e8de5e92fe..1ae478ca71 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -1,5 +1,5 @@
-import { eventFromException } from '@sentry/browser';
-import { BaseClient, eventFromMessage } from '@sentry/core';
+import { eventFromException, eventFromMessage } from '@sentry/browser';
+import { BaseClient } from '@sentry/core';
 import type {
   ClientReportEnvelope,
   ClientReportItem,

From f9d2b59500ea58956f42f5250d395270a385ae31 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 16:55:57 +0200
Subject: [PATCH 11/73] Adds captureFeedback tests from sentry-javascript

---
 packages/core/test/feedback.test.ts | 473 ++++++++++++++++++++++++++++
 1 file changed, 473 insertions(+)
 create mode 100644 packages/core/test/feedback.test.ts

diff --git a/packages/core/test/feedback.test.ts b/packages/core/test/feedback.test.ts
new file mode 100644
index 0000000000..f36a697001
--- /dev/null
+++ b/packages/core/test/feedback.test.ts
@@ -0,0 +1,473 @@
+import {
+  addBreadcrumb,
+  captureFeedback,
+  getCurrentScope,
+  Scope,
+  setCurrentClient,
+  startSpan,
+  withIsolationScope,
+  withScope,
+} from '@sentry/core';
+import type { Span } from '@sentry/types';
+
+import { getDefaultTestClientOptions, TestClient } from './mocks/client';
+
+describe('captureFeedback', () => {
+  beforeEach(() => {
+    getCurrentScope().setClient(undefined);
+    getCurrentScope().clear();
+  });
+
+  test('it works without a client', () => {
+    const res = captureFeedback({
+      message: 'test',
+    });
+
+    expect(typeof res).toBe('string');
+  });
+
+  test('it works with minimal options', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    const eventId = captureFeedback({
+      message: 'test',
+    });
+
+    await client.flush();
+
+    expect(typeof eventId).toBe('string');
+
+    expect(mockTransport).toHaveBeenCalledWith([
+      {
+        event_id: eventId,
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: undefined,
+            contexts: {
+              trace: {
+                span_id: expect.any(String),
+                trace_id: expect.any(String),
+              },
+              feedback: {
+                message: 'test',
+              },
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: eventId,
+            timestamp: expect.any(Number),
+            tags: undefined,
+            type: 'feedback',
+          },
+        ],
+      ],
+    ]);
+  });
+
+  test('it works with full options', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    const eventId = captureFeedback({
+      name: 'doe',
+      email: 're@example.org',
+      message: 'mi',
+      url: 'http://example.com/',
+      source: 'custom-source',
+      associatedEventId: '1234',
+    });
+
+    await client.flush();
+
+    expect(typeof eventId).toBe('string');
+
+    expect(mockTransport).toHaveBeenCalledWith([
+      {
+        event_id: eventId,
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: undefined,
+            contexts: {
+              trace: {
+                span_id: expect.any(String),
+                trace_id: expect.any(String),
+              },
+              feedback: {
+                name: 'doe',
+                contact_email: 're@example.org',
+                message: 'mi',
+                url: 'http://example.com/',
+                source: 'custom-source',
+                associated_event_id: '1234',
+              },
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: eventId,
+            timestamp: expect.any(Number),
+            tags: undefined,
+            type: 'feedback',
+          },
+        ],
+      ],
+    ]);
+  });
+
+  test('it captures attachments', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    const attachment1 = new Uint8Array([1, 2, 3, 4, 5]);
+    const attachment2 = new Uint8Array([6, 7, 8, 9]);
+
+    const eventId = captureFeedback(
+      {
+        message: 'test',
+      },
+      {
+        attachments: [
+          {
+            data: attachment1,
+            filename: 'test-file.txt',
+          },
+          {
+            data: attachment2,
+            filename: 'test-file2.txt',
+          },
+        ],
+      },
+    );
+
+    await client.flush();
+
+    expect(typeof eventId).toBe('string');
+
+    expect(mockTransport).toHaveBeenCalledTimes(1);
+
+    const [feedbackEnvelope] = mockTransport.mock.calls;
+
+    expect(feedbackEnvelope).toHaveLength(1);
+    expect(feedbackEnvelope![0]).toEqual([
+      {
+        event_id: eventId,
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: undefined,
+            contexts: {
+              trace: {
+                span_id: expect.any(String),
+                trace_id: expect.any(String),
+              },
+              feedback: {
+                message: 'test',
+              },
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: eventId,
+            timestamp: expect.any(Number),
+            tags: undefined,
+            type: 'feedback',
+          },
+        ],
+        [
+          {
+            type: 'attachment',
+            length: 5,
+            filename: 'test-file.txt',
+          },
+          attachment1,
+        ],
+        [
+          {
+            type: 'attachment',
+            length: 4,
+            filename: 'test-file2.txt',
+          },
+          attachment2,
+        ],
+      ],
+    ]);
+  });
+
+  test('it captures DSC from scope', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    const traceId = '4C79F60C11214EB38604F4AE0781BFB2';
+    const spanId = 'FA90FDEAD5F74052';
+    const dsc = {
+      trace_id: traceId,
+      span_id: spanId,
+      sampled: 'true',
+    };
+
+    getCurrentScope().setPropagationContext({
+      traceId,
+      spanId,
+      dsc,
+    });
+
+    const eventId = captureFeedback({
+      message: 'test',
+    });
+
+    await client.flush();
+
+    expect(typeof eventId).toBe('string');
+
+    expect(mockTransport).toHaveBeenCalledWith([
+      {
+        event_id: eventId,
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: undefined,
+            contexts: {
+              trace: {
+                trace_id: traceId,
+                span_id: spanId,
+              },
+              feedback: {
+                message: 'test',
+              },
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: eventId,
+            timestamp: expect.any(Number),
+            tags: undefined,
+            type: 'feedback',
+          },
+        ],
+      ],
+    ]);
+  });
+
+  test('it captures data from active span', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+        enableTracing: true,
+        // We don't care about transactions here...
+        beforeSendTransaction() {
+          return null;
+        },
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    let span: Span | undefined;
+    const eventId = startSpan({ name: 'test-span' }, _span => {
+      span = _span;
+      return captureFeedback({
+        message: 'test',
+      });
+    });
+
+    await client.flush();
+
+    expect(typeof eventId).toBe('string');
+    expect(span).toBeDefined();
+
+    const { spanId, traceId } = span!.spanContext();
+
+    expect(mockTransport).toHaveBeenCalledWith([
+      {
+        event_id: eventId,
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: undefined,
+            contexts: {
+              trace: {
+                trace_id: traceId,
+                span_id: spanId,
+              },
+              feedback: {
+                message: 'test',
+              },
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: eventId,
+            timestamp: expect.any(Number),
+            tags: undefined,
+            type: 'feedback',
+          },
+        ],
+      ],
+    ]);
+  });
+
+  it('applies scope data to feedback', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+        enableTracing: true,
+        // We don't care about transactions here...
+        beforeSendTransaction() {
+          return null;
+        },
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+
+    withIsolationScope(isolationScope => {
+      isolationScope.setTag('test-1', 'tag');
+      isolationScope.setExtra('test-1', 'extra');
+
+      return withScope(scope => {
+        scope.setTag('test-2', 'tag');
+        scope.setExtra('test-2', 'extra');
+
+        addBreadcrumb({ message: 'test breadcrumb', timestamp: 12345 });
+
+        captureFeedback({
+          name: 'doe',
+          email: 're@example.org',
+          message: 'mi',
+        });
+      });
+    });
+
+    expect(mockTransport).toHaveBeenCalledWith([
+      {
+        event_id: expect.any(String),
+        sent_at: expect.any(String),
+      },
+      [
+        [
+          { type: 'feedback' },
+          {
+            breadcrumbs: [{ message: 'test breadcrumb', timestamp: 12345 }],
+            contexts: {
+              trace: {
+                span_id: expect.any(String),
+                trace_id: expect.any(String),
+              },
+              feedback: {
+                contact_email: 're@example.org',
+                message: 'mi',
+                name: 'doe',
+              },
+            },
+            extra: {
+              'test-1': 'extra',
+              'test-2': 'extra',
+            },
+            tags: {
+              'test-1': 'tag',
+              'test-2': 'tag',
+            },
+            level: 'info',
+            environment: 'production',
+            event_id: expect.any(String),
+            timestamp: expect.any(Number),
+            type: 'feedback',
+          },
+        ],
+      ],
+    ]);
+  });
+
+  test('it allows to pass a custom client', async () => {
+    const client = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+      }),
+    );
+    setCurrentClient(client);
+    client.init();
+
+    const client2 = new TestClient(
+      getDefaultTestClientOptions({
+        dsn: 'https://dsn@ingest.f00.f00/1',
+        enableSend: true,
+        defaultIntegrations: false,
+      }),
+    );
+    client2.init();
+    const scope = new Scope();
+    scope.setClient(client2);
+
+    const mockTransport = jest.spyOn(client.getTransport()!, 'send');
+    const mockTransport2 = jest.spyOn(client2.getTransport()!, 'send');
+
+    const eventId = captureFeedback(
+      {
+        message: 'test',
+      },
+      {},
+      scope,
+    );
+
+    await client.flush();
+    await client2.flush();
+
+    expect(typeof eventId).toBe('string');
+
+    expect(mockTransport).not.toHaveBeenCalled();
+    expect(mockTransport2).toHaveBeenCalledTimes(1);
+  });
+});

From d05d5315356a03f4c050ad906ac53e4bb6ff0d26 Mon Sep 17 00:00:00 2001
From: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
Date: Thu, 28 Nov 2024 16:53:39 +0100
Subject: [PATCH 12/73] Update CHANGELOG.md

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f4cfc6fc5..e34ec75485 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,6 @@
 
 - Adds new `captureFeedback` and deprecates the `captureUserFeedback` API ([#4320](https://github.com/getsentry/sentry-react-native/pull/4320))
 
-
   ```jsx
   import * as Sentry from "@sentry/react-native";
 

From 2bb104b889fe1b3ca5ddae559c2eebf4d47085ab Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 28 Nov 2024 18:14:45 +0200
Subject: [PATCH 13/73] Only deprecate client captureUserFeedback

---
 packages/core/src/js/client.ts    | 17 ++++++++++-
 packages/core/test/client.test.ts | 47 +++++++++++++++++++++++++++++++
 packages/core/test/testutils.ts   |  9 +++++-
 3 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/packages/core/src/js/client.ts b/packages/core/src/js/client.ts
index 1ae478ca71..1e2c858264 100644
--- a/packages/core/src/js/client.ts
+++ b/packages/core/src/js/client.ts
@@ -9,6 +9,7 @@ import type {
   Outcome,
   SeverityLevel,
   TransportMakeRequestResponse,
+  UserFeedback,
 } from '@sentry/types';
 import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils';
 import { Alert } from 'react-native';
@@ -19,7 +20,7 @@ import { getDefaultSidecarUrl } from './integrations/spotlight';
 import type { ReactNativeClientOptions } from './options';
 import type { mobileReplayIntegration } from './replay/mobilereplay';
 import { MOBILE_REPLAY_INTEGRATION_NAME } from './replay/mobilereplay';
-import { items } from './utils/envelope';
+import { createUserFeedbackEnvelope, items } from './utils/envelope';
 import { ignoreRequireCycleLogs } from './utils/ignorerequirecyclelogs';
 import { mergeOutcomes } from './utils/outcome';
 import { ReactNativeLibraries } from './utils/rnlibraries';
@@ -82,6 +83,20 @@ export class ReactNativeClient extends BaseClient<ReactNativeClientOptions> {
     });
   }
 
+  /**
+   * Sends user feedback to Sentry.
+   * @deprecated Use `Sentry.captureFeedback` instead.
+   */
+  public captureUserFeedback(feedback: UserFeedback): void {
+    const envelope = createUserFeedbackEnvelope(feedback, {
+      metadata: this._options._metadata,
+      dsn: this.getDsn(),
+      tunnel: undefined,
+    });
+    // eslint-disable-next-line @typescript-eslint/no-floating-promises
+    this.sendEnvelope(envelope);
+  }
+
   /**
    * @inheritdoc
    */
diff --git a/packages/core/test/client.test.ts b/packages/core/test/client.test.ts
index 3d732e194c..a0e5b13201 100644
--- a/packages/core/test/client.test.ts
+++ b/packages/core/test/client.test.ts
@@ -19,6 +19,7 @@ import {
   envelopeItems,
   firstArg,
   getMockSession,
+  getMockUserFeedback,
   getSyncPromiseRejectOnFirstCall,
 } from './testutils';
 
@@ -186,6 +187,15 @@ describe('Tests ReactNativeClient', () => {
       expect(mockTransport.send).not.toBeCalled();
     });
 
+    test('captureUserFeedback does not call transport when enabled false', () => {
+      const mockTransport = createMockTransport();
+      const client = createDisabledClientWith(mockTransport);
+
+      client.captureUserFeedback(getMockUserFeedback());
+
+      expect(mockTransport.send).not.toBeCalled();
+    });
+
     function createDisabledClientWith(transport: Transport) {
       return new ReactNativeClient({
         ...DEFAULT_OPTIONS,
@@ -279,6 +289,38 @@ describe('Tests ReactNativeClient', () => {
     });
   });
 
+  describe('UserFeedback', () => {
+    test('sends UserFeedback to native Layer', () => {
+      const mockTransportSend: jest.Mock = jest.fn(() => Promise.resolve());
+      const client = new ReactNativeClient({
+        ...DEFAULT_OPTIONS,
+        dsn: EXAMPLE_DSN,
+        transport: () => ({
+          send: mockTransportSend,
+          flush: jest.fn(),
+        }),
+      });
+
+      client.captureUserFeedback({
+        comments: 'Test Comments',
+        email: 'test@email.com',
+        name: 'Test User',
+        event_id: 'testEvent123',
+      });
+
+      expect(mockTransportSend.mock.calls[0][firstArg][envelopeHeader].event_id).toEqual('testEvent123');
+      expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemHeader].type).toEqual(
+        'user_report',
+      );
+      expect(mockTransportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload]).toEqual({
+        comments: 'Test Comments',
+        email: 'test@email.com',
+        name: 'Test User',
+        event_id: 'testEvent123',
+      });
+    });
+  });
+
   describe('attachStacktrace', () => {
     let mockTransportSend: jest.Mock;
     let client: ReactNativeClient;
@@ -375,6 +417,11 @@ describe('Tests ReactNativeClient', () => {
       client.captureSession(getMockSession());
       expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
     });
+
+    test('send SdkInfo in the user feedback envelope header', () => {
+      client.captureUserFeedback(getMockUserFeedback());
+      expect(getSdkInfoFrom(mockTransportSend)).toStrictEqual(expectedSdkInfo);
+    });
   });
 
   describe('event data enhancement', () => {
diff --git a/packages/core/test/testutils.ts b/packages/core/test/testutils.ts
index 593a51f838..4548eef092 100644
--- a/packages/core/test/testutils.ts
+++ b/packages/core/test/testutils.ts
@@ -1,4 +1,4 @@
-import type { Session, Transport } from '@sentry/types';
+import type { Session, Transport, UserFeedback } from '@sentry/types';
 import { rejectedSyncPromise } from '@sentry/utils';
 
 export type MockInterface<T> = {
@@ -36,6 +36,13 @@ export const getMockSession = (): Session => ({
   }),
 });
 
+export const getMockUserFeedback = (): UserFeedback => ({
+  comments: 'comments_test_value',
+  email: 'email_test_value',
+  name: 'name_test_value',
+  event_id: 'event_id_test_value',
+});
+
 export const getSyncPromiseRejectOnFirstCall = <Y extends any[]>(reason: unknown): jest.Mock => {
   let shouldSyncReject = true;
   return jest.fn((..._args: Y) => {

From 4339274aaaad995213eceb1a284551ac8376c24c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 29 Nov 2024 16:56:34 +0200
Subject: [PATCH 14/73] Add simple form UI

---
 .../src/js/feedback/FeedbackFormScreen.tsx    | 112 ++++++++++++++++++
 packages/core/src/js/index.ts                 |   2 +
 samples/react-native/src/App.tsx              |  12 ++
 .../react-native/src/Screens/ErrorsScreen.tsx |   6 +
 4 files changed, 132 insertions(+)
 create mode 100644 packages/core/src/js/feedback/FeedbackFormScreen.tsx

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
new file mode 100644
index 0000000000..1a1aacb9be
--- /dev/null
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import type { KeyboardTypeOptions } from "react-native";
+import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
+
+interface FeedbackFormScreenProps {
+  closeScreen: () => void;
+}
+
+export const FeedbackFormScreen: React.FC<FeedbackFormScreenProps> = ({ closeScreen }) => {
+
+  const handleFeedbackSubmit = (): void => {
+    closeScreen();
+  };
+
+  const addScreenshot = (): void => {
+    Alert.alert("Info", "Attachments are not supported yet.");
+    // TODO
+  };
+
+  return (
+    <View style={styles.container}>
+      <Text style={styles.title}>Feedback Form</Text>
+
+      <TextInput
+        style={styles.input}
+        placeholder="Name"
+      />
+
+      <TextInput
+        style={styles.input}
+        placeholder="Email"
+        keyboardType={"email-address" as KeyboardTypeOptions}
+      />
+
+      <TextInput
+        style={[styles.input, styles.textArea]}
+        placeholder="Description (required)"
+        multiline
+      />
+
+      <TouchableOpacity style={styles.screenshotButton} onPress={addScreenshot}>
+        <Text style={styles.screenshotText}>Add Screenshot</Text>
+      </TouchableOpacity>
+
+      <TouchableOpacity style={styles.submitButton} onPress={handleFeedbackSubmit}>
+        <Text style={styles.submitText}>Send Feedback</Text>
+      </TouchableOpacity>
+
+      <TouchableOpacity style={styles.cancelButton} onPress={closeScreen}>
+        <Text style={styles.cancelText}>Cancel</Text>
+      </TouchableOpacity>
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    padding: 20,
+    backgroundColor: "#fff",
+  },
+  title: {
+    fontSize: 24,
+    fontWeight: "bold",
+    marginBottom: 20,
+    textAlign: "center",
+  },
+  input: {
+    height: 50,
+    borderColor: "#ccc",
+    borderWidth: 1,
+    borderRadius: 5,
+    paddingHorizontal: 10,
+    marginBottom: 15,
+    fontSize: 16,
+  },
+  textArea: {
+    height: 100,
+    textAlignVertical: "top",
+  },
+  screenshotButton: {
+    backgroundColor: "#eee",
+    padding: 15,
+    borderRadius: 5,
+    marginBottom: 20,
+    alignItems: "center",
+  },
+  screenshotText: {
+    color: "#333",
+    fontSize: 16,
+  },
+  submitButton: {
+    backgroundColor: "#6a1b9a",
+    paddingVertical: 15,
+    borderRadius: 5,
+    alignItems: "center",
+    marginBottom: 10,
+  },
+  submitText: {
+    color: "#fff",
+    fontSize: 18,
+    fontWeight: "bold",
+  },
+  cancelButton: {
+    paddingVertical: 15,
+    alignItems: "center",
+  },
+  cancelText: {
+    color: "#6a1b9a",
+    fontSize: 16,
+  },
+});
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index 4aa7e2fa21..ba28710da8 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -83,3 +83,5 @@ export {
 export type { TimeToDisplayProps } from './tracing';
 
 export { Mask, Unmask } from './replay/CustomMask';
+
+export { FeedbackFormScreen } from './feedback/FeedbackFormScreen';
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 04348fa5c6..5a30fcafba 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -16,6 +16,7 @@ import Animated, {
 
 // Import the Sentry React Native SDK
 import * as Sentry from '@sentry/react-native';
+import { FeedbackFormScreen } from '@sentry/react-native';
 
 import { SENTRY_INTERNAL_DSN } from './dsn';
 import ErrorsScreen from './Screens/ErrorsScreen';
@@ -151,6 +152,17 @@ const ErrorsTabNavigator = Sentry.withProfiler(
               component={ErrorsScreen}
               options={{ title: 'Errors' }}
             />
+            <Stack.Screen
+              name="FeedbackForm"
+              options={{ presentation: 'modal', headerShown: false }}
+            >
+              {(props) => (
+                <FeedbackFormScreen
+                  {...props}
+                  closeScreen={props.navigation.goBack}
+                />
+              )}
+            </Stack.Screen>
           </Stack.Navigator>
         </Provider>
       </GestureHandlerRootView>
diff --git a/samples/react-native/src/Screens/ErrorsScreen.tsx b/samples/react-native/src/Screens/ErrorsScreen.tsx
index 5f2f405677..4788fa407a 100644
--- a/samples/react-native/src/Screens/ErrorsScreen.tsx
+++ b/samples/react-native/src/Screens/ErrorsScreen.tsx
@@ -220,6 +220,12 @@ const ErrorsScreen = (_props: Props) => {
             }
           }}
         />
+        <Button
+          title="Feedback form"
+          onPress={() => {
+            _props.navigation.navigate('FeedbackForm');
+          }}
+        />
         <Button
           title="Send user feedback"
           onPress={() => {

From 6ce799b2646adff1653f7a1469d7b29a5dc143d6 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 29 Nov 2024 23:15:26 +0200
Subject: [PATCH 15/73] Adds basic form functionality

---
 .../src/js/feedback/FeedbackFormScreen.tsx    | 121 +++++++++++++-----
 samples/react-native/src/App.tsx              |  10 ++
 2 files changed, 96 insertions(+), 35 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 1a1aacb9be..6d8aaa20e6 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -1,73 +1,124 @@
-import React from 'react';
-import type { KeyboardTypeOptions } from "react-native";
-import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
+import { captureFeedback } from '@sentry/core';
+import React, { useState } from 'react';
+import type { KeyboardTypeOptions, TextStyle,ViewStyle } from 'react-native';
+import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
+
+import type { SendFeedbackParams } from '..';
 
 interface FeedbackFormScreenProps {
   closeScreen: () => void;
+  text: FeedbackFormText;
+  styles?: FeedbackFormScreenStyles;
+}
+
+interface FeedbackFormText {
+  formTitle?: string;
+  namePlaceholder?: string;
+  emailPlaceholder?: string;
+  descriptionPlaceholder?: string;
+  addAttachmentButton?: string;
+  submitButton?: string;
+  cancelButton?: string;
+  formError?: string;
 }
 
-export const FeedbackFormScreen: React.FC<FeedbackFormScreenProps> = ({ closeScreen }) => {
+interface FeedbackFormScreenStyles {
+  container?: ViewStyle;
+  title?: TextStyle;
+  input?: TextStyle;
+  textArea?: ViewStyle;
+  screenshotButton?: ViewStyle;
+  screenshotText?: TextStyle;
+  submitButton?: ViewStyle;
+  submitText?: TextStyle;
+  cancelButton?: ViewStyle;
+  cancelText?: TextStyle;
+}
+
+export const FeedbackFormScreen: React.FC<FeedbackFormScreenProps> = ({ closeScreen, text, styles }) => {
+  const [name, setName] = useState('');
+  const [email, setEmail] = useState('');
+  const [description, setDescription] = useState('');
 
   const handleFeedbackSubmit = (): void => {
+    if (!name || !email || !description) {
+      const errorMessage = text?.formError || 'Please fill out all required fields.';
+      Alert.alert('Error', errorMessage);
+      return;
+    }
+    const userFeedback: SendFeedbackParams = {
+      message: description,
+      name: name,
+      email: email,
+    };
+
+    captureFeedback(userFeedback);
+
     closeScreen();
   };
 
   const addScreenshot = (): void => {
-    Alert.alert("Info", "Attachments are not supported yet.");
+    Alert.alert('Info', 'Attachments are not supported yet.');
     // TODO
   };
 
   return (
-    <View style={styles.container}>
-      <Text style={styles.title}>Feedback Form</Text>
+    <View style={styles?.container || defaultStyles.container}>
+      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || 'Feedback Form'}</Text>
 
       <TextInput
-        style={styles.input}
-        placeholder="Name"
+        style={styles?.input || defaultStyles.input}
+        placeholder={text?.namePlaceholder || 'Name'}
+        value={name}
+        onChangeText={setName}
       />
 
       <TextInput
-        style={styles.input}
-        placeholder="Email"
-        keyboardType={"email-address" as KeyboardTypeOptions}
+        style={styles?.input || defaultStyles.input}
+        placeholder={text?.emailPlaceholder || 'Email'}
+        keyboardType={'email-address' as KeyboardTypeOptions}
+        value={email}
+        onChangeText={setEmail}
       />
 
       <TextInput
-        style={[styles.input, styles.textArea]}
-        placeholder="Description (required)"
+        style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
+        placeholder={text?.descriptionPlaceholder || 'Description (required)'}
+        value={description}
+        onChangeText={setDescription}
         multiline
       />
 
-      <TouchableOpacity style={styles.screenshotButton} onPress={addScreenshot}>
-        <Text style={styles.screenshotText}>Add Screenshot</Text>
+      <TouchableOpacity style={styles?.screenshotButton || defaultStyles.screenshotButton} onPress={addScreenshot}>
+        <Text style={styles?.screenshotText || defaultStyles.screenshotText}>{text?.addAttachmentButton || 'Add Screenshot'}</Text>
       </TouchableOpacity>
 
-      <TouchableOpacity style={styles.submitButton} onPress={handleFeedbackSubmit}>
-        <Text style={styles.submitText}>Send Feedback</Text>
+      <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={handleFeedbackSubmit}>
+        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || 'Send Feedback'}</Text>
       </TouchableOpacity>
 
-      <TouchableOpacity style={styles.cancelButton} onPress={closeScreen}>
-        <Text style={styles.cancelText}>Cancel</Text>
+      <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
+        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || 'Cancel'}</Text>
       </TouchableOpacity>
     </View>
   );
 };
 
-const styles = StyleSheet.create({
+const defaultStyles = StyleSheet.create({
   container: {
     flex: 1,
     padding: 20,
-    backgroundColor: "#fff",
+    backgroundColor: '#fff',
   },
   title: {
     fontSize: 24,
-    fontWeight: "bold",
+    fontWeight: 'bold',
     marginBottom: 20,
-    textAlign: "center",
+    textAlign: 'center',
   },
   input: {
     height: 50,
-    borderColor: "#ccc",
+    borderColor: '#ccc',
     borderWidth: 1,
     borderRadius: 5,
     paddingHorizontal: 10,
@@ -76,37 +127,37 @@ const styles = StyleSheet.create({
   },
   textArea: {
     height: 100,
-    textAlignVertical: "top",
+    textAlignVertical: 'top',
   },
   screenshotButton: {
-    backgroundColor: "#eee",
+    backgroundColor: '#eee',
     padding: 15,
     borderRadius: 5,
     marginBottom: 20,
-    alignItems: "center",
+    alignItems: 'center',
   },
   screenshotText: {
-    color: "#333",
+    color: '#333',
     fontSize: 16,
   },
   submitButton: {
-    backgroundColor: "#6a1b9a",
+    backgroundColor: '#6a1b9a',
     paddingVertical: 15,
     borderRadius: 5,
-    alignItems: "center",
+    alignItems: 'center',
     marginBottom: 10,
   },
   submitText: {
-    color: "#fff",
+    color: '#fff',
     fontSize: 18,
-    fontWeight: "bold",
+    fontWeight: 'bold',
   },
   cancelButton: {
     paddingVertical: 15,
-    alignItems: "center",
+    alignItems: 'center',
   },
   cancelText: {
-    color: "#6a1b9a",
+    color: '#6a1b9a',
     fontSize: 16,
   },
 });
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 5a30fcafba..532966ad11 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -160,6 +160,16 @@ const ErrorsTabNavigator = Sentry.withProfiler(
                 <FeedbackFormScreen
                   {...props}
                   closeScreen={props.navigation.goBack}
+                  styles={{
+                    submitButton: {
+                      backgroundColor: '#ff0000',
+                      paddingVertical: 15,
+                      borderRadius: 5,
+                      alignItems: 'center',
+                      marginBottom: 10,
+                    },
+                  }}
+                  text={{namePlaceholder: 'Fullname', addAttachmentButton: 'Add Attachment'}}
                 />
               )}
             </Stack.Screen>

From f2cefc6a5e8f4ed43df03796c4848a9d22dc37ea Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 29 Nov 2024 23:31:16 +0200
Subject: [PATCH 16/73] Update imports

---
 packages/core/src/js/feedback/FeedbackFormScreen.tsx | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 6d8aaa20e6..e4b293eef7 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -1,10 +1,9 @@
 import { captureFeedback } from '@sentry/core';
+import type { SendFeedbackParams } from '@sentry/types';
 import React, { useState } from 'react';
 import type { KeyboardTypeOptions, TextStyle,ViewStyle } from 'react-native';
 import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
-import type { SendFeedbackParams } from '..';
-
 interface FeedbackFormScreenProps {
   closeScreen: () => void;
   text: FeedbackFormText;

From 694ee333a5a7027e35ddccef68f5d8f33cd75c26 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 10:59:56 +0200
Subject: [PATCH 17/73] Update imports

---
 packages/core/src/js/feedback/FeedbackFormScreen.tsx | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index e4b293eef7..3e3715e53e 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -1,6 +1,7 @@
 import { captureFeedback } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
-import React, { useState } from 'react';
+import * as React from 'react';
+import { useState } from 'react';
 import type { KeyboardTypeOptions, TextStyle,ViewStyle } from 'react-native';
 import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
 

From 034efdea49b2da296842969a58e73c6819b788e7 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 17:22:13 +0200
Subject: [PATCH 18/73] Remove useState hook to avoid multiple react instances
 issues

---
 .../src/js/feedback/FeedbackFormScreen.tsx    | 162 ++++++++++--------
 1 file changed, 93 insertions(+), 69 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 3e3715e53e..d522e8fade 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -1,7 +1,6 @@
 import { captureFeedback } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
-import { useState } from 'react';
 import type { KeyboardTypeOptions, TextStyle,ViewStyle } from 'react-native';
 import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
@@ -35,74 +34,11 @@ interface FeedbackFormScreenStyles {
   cancelText?: TextStyle;
 }
 
-export const FeedbackFormScreen: React.FC<FeedbackFormScreenProps> = ({ closeScreen, text, styles }) => {
-  const [name, setName] = useState('');
-  const [email, setEmail] = useState('');
-  const [description, setDescription] = useState('');
-
-  const handleFeedbackSubmit = (): void => {
-    if (!name || !email || !description) {
-      const errorMessage = text?.formError || 'Please fill out all required fields.';
-      Alert.alert('Error', errorMessage);
-      return;
-    }
-    const userFeedback: SendFeedbackParams = {
-      message: description,
-      name: name,
-      email: email,
-    };
-
-    captureFeedback(userFeedback);
-
-    closeScreen();
-  };
-
-  const addScreenshot = (): void => {
-    Alert.alert('Info', 'Attachments are not supported yet.');
-    // TODO
-  };
-
-  return (
-    <View style={styles?.container || defaultStyles.container}>
-      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || 'Feedback Form'}</Text>
-
-      <TextInput
-        style={styles?.input || defaultStyles.input}
-        placeholder={text?.namePlaceholder || 'Name'}
-        value={name}
-        onChangeText={setName}
-      />
-
-      <TextInput
-        style={styles?.input || defaultStyles.input}
-        placeholder={text?.emailPlaceholder || 'Email'}
-        keyboardType={'email-address' as KeyboardTypeOptions}
-        value={email}
-        onChangeText={setEmail}
-      />
-
-      <TextInput
-        style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
-        placeholder={text?.descriptionPlaceholder || 'Description (required)'}
-        value={description}
-        onChangeText={setDescription}
-        multiline
-      />
-
-      <TouchableOpacity style={styles?.screenshotButton || defaultStyles.screenshotButton} onPress={addScreenshot}>
-        <Text style={styles?.screenshotText || defaultStyles.screenshotText}>{text?.addAttachmentButton || 'Add Screenshot'}</Text>
-      </TouchableOpacity>
-
-      <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={handleFeedbackSubmit}>
-        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || 'Send Feedback'}</Text>
-      </TouchableOpacity>
-
-      <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
-        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || 'Cancel'}</Text>
-      </TouchableOpacity>
-    </View>
-  );
-};
+interface FeedbackFormScreenState {
+  name: string;
+  email: string;
+  description: string;
+}
 
 const defaultStyles = StyleSheet.create({
   container: {
@@ -161,3 +97,91 @@ const defaultStyles = StyleSheet.create({
     fontSize: 16,
   },
 });
+
+/**
+ *
+ */
+export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps, FeedbackFormScreenState> {
+  public constructor(props: FeedbackFormScreenProps) {
+    super(props);
+    this.state = {
+      name: '',
+      email: '',
+      description: '',
+    };
+  }
+
+  public handleFeedbackSubmit: () => void = () => {
+    const { name, email, description } = this.state;
+    const { closeScreen, text } = this.props;
+
+    if (!name || !email || !description) {
+      const errorMessage = text?.formError || 'Please fill out all required fields.';
+      Alert.alert('Error', errorMessage);
+      return;
+    }
+
+    const userFeedback: SendFeedbackParams = {
+      message: description,
+      name,
+      email,
+    };
+
+    captureFeedback(userFeedback);
+    closeScreen();
+  };
+
+  public addScreenshot: () => void = () => {
+    Alert.alert('Info', 'Attachments are not supported yet.');
+    // TODO
+  };
+
+  /**
+   *
+   */
+  public render(): React.ReactNode {
+    const { closeScreen, text, styles } = this.props;
+    const { name, email, description } = this.state;
+
+    return (
+    <View style={styles?.container || defaultStyles.container}>
+      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || 'Feedback Form'}</Text>
+
+      <TextInput
+        style={styles?.input || defaultStyles.input}
+        placeholder={text?.namePlaceholder || 'Name'}
+        value={name}
+        onChangeText={(value) => this.setState({ name: value })}
+        />
+
+      <TextInput
+        style={styles?.input || defaultStyles.input}
+        placeholder={text?.emailPlaceholder || 'Email'}
+        keyboardType={'email-address' as KeyboardTypeOptions}
+        value={email}
+        onChangeText={(value) => this.setState({ email: value })}
+        />
+
+      <TextInput
+        style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
+        placeholder={text?.descriptionPlaceholder || 'Description (required)'}
+        value={description}
+        onChangeText={(value) => this.setState({ description: value })}
+        multiline
+      />
+
+      <TouchableOpacity style={styles?.screenshotButton || defaultStyles.screenshotButton} onPress={this.addScreenshot}>
+        <Text style={styles?.screenshotText || defaultStyles.screenshotText}>{text?.addAttachmentButton || 'Add Screenshot'}</Text>
+      </TouchableOpacity>
+
+      <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={this.handleFeedbackSubmit}>
+        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || 'Send Feedback'}</Text>
+      </TouchableOpacity>
+
+      <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
+        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || 'Cancel'}</Text>
+      </TouchableOpacity>
+    </View>
+    );
+  }
+}

From 67a492dfa3aeb8d4bf1a9939dc4f4cddd8156f14 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 17:34:53 +0200
Subject: [PATCH 19/73] Move types and styles in different files

---
 .../js/feedback/FeedbackFormScreen.styles.ts  |  61 +++++++++++
 .../src/js/feedback/FeedbackFormScreen.tsx    | 103 +-----------------
 .../js/feedback/FeedbackFormScreen.types.ts   |  37 +++++++
 samples/react-native/src/App.tsx              |   2 +-
 4 files changed, 105 insertions(+), 98 deletions(-)
 create mode 100644 packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
 create mode 100644 packages/core/src/js/feedback/FeedbackFormScreen.types.ts

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts b/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
new file mode 100644
index 0000000000..d5fff2f385
--- /dev/null
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
@@ -0,0 +1,61 @@
+import { StyleSheet } from 'react-native';
+
+const defaultStyles = StyleSheet.create({
+  container: {
+    flex: 1,
+    padding: 20,
+    backgroundColor: '#fff',
+  },
+  title: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    marginBottom: 20,
+    textAlign: 'center',
+  },
+  input: {
+    height: 50,
+    borderColor: '#ccc',
+    borderWidth: 1,
+    borderRadius: 5,
+    paddingHorizontal: 10,
+    marginBottom: 15,
+    fontSize: 16,
+  },
+  textArea: {
+    height: 100,
+    textAlignVertical: 'top',
+  },
+  screenshotButton: {
+    backgroundColor: '#eee',
+    padding: 15,
+    borderRadius: 5,
+    marginBottom: 20,
+    alignItems: 'center',
+  },
+  screenshotText: {
+    color: '#333',
+    fontSize: 16,
+  },
+  submitButton: {
+    backgroundColor: '#6a1b9a',
+    paddingVertical: 15,
+    borderRadius: 5,
+    alignItems: 'center',
+    marginBottom: 10,
+  },
+  submitText: {
+    color: '#fff',
+    fontSize: 18,
+    fontWeight: 'bold',
+  },
+  cancelButton: {
+    paddingVertical: 15,
+    alignItems: 'center',
+  },
+  cancelText: {
+    color: '#6a1b9a',
+    fontSize: 16,
+  },
+});
+
+export default defaultStyles;
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index d522e8fade..0c250b8950 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -1,105 +1,14 @@
 import { captureFeedback } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
-import type { KeyboardTypeOptions, TextStyle,ViewStyle } from 'react-native';
-import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
+import type { KeyboardTypeOptions } from 'react-native';
+import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
-interface FeedbackFormScreenProps {
-  closeScreen: () => void;
-  text: FeedbackFormText;
-  styles?: FeedbackFormScreenStyles;
-}
-
-interface FeedbackFormText {
-  formTitle?: string;
-  namePlaceholder?: string;
-  emailPlaceholder?: string;
-  descriptionPlaceholder?: string;
-  addAttachmentButton?: string;
-  submitButton?: string;
-  cancelButton?: string;
-  formError?: string;
-}
-
-interface FeedbackFormScreenStyles {
-  container?: ViewStyle;
-  title?: TextStyle;
-  input?: TextStyle;
-  textArea?: ViewStyle;
-  screenshotButton?: ViewStyle;
-  screenshotText?: TextStyle;
-  submitButton?: ViewStyle;
-  submitText?: TextStyle;
-  cancelButton?: ViewStyle;
-  cancelText?: TextStyle;
-}
-
-interface FeedbackFormScreenState {
-  name: string;
-  email: string;
-  description: string;
-}
-
-const defaultStyles = StyleSheet.create({
-  container: {
-    flex: 1,
-    padding: 20,
-    backgroundColor: '#fff',
-  },
-  title: {
-    fontSize: 24,
-    fontWeight: 'bold',
-    marginBottom: 20,
-    textAlign: 'center',
-  },
-  input: {
-    height: 50,
-    borderColor: '#ccc',
-    borderWidth: 1,
-    borderRadius: 5,
-    paddingHorizontal: 10,
-    marginBottom: 15,
-    fontSize: 16,
-  },
-  textArea: {
-    height: 100,
-    textAlignVertical: 'top',
-  },
-  screenshotButton: {
-    backgroundColor: '#eee',
-    padding: 15,
-    borderRadius: 5,
-    marginBottom: 20,
-    alignItems: 'center',
-  },
-  screenshotText: {
-    color: '#333',
-    fontSize: 16,
-  },
-  submitButton: {
-    backgroundColor: '#6a1b9a',
-    paddingVertical: 15,
-    borderRadius: 5,
-    alignItems: 'center',
-    marginBottom: 10,
-  },
-  submitText: {
-    color: '#fff',
-    fontSize: 18,
-    fontWeight: 'bold',
-  },
-  cancelButton: {
-    paddingVertical: 15,
-    alignItems: 'center',
-  },
-  cancelText: {
-    color: '#6a1b9a',
-    fontSize: 16,
-  },
-});
+import defaultStyles from './FeedbackFormScreen.styles';
+import type { FeedbackFormScreenProps, FeedbackFormScreenState } from './FeedbackFormScreen.types';
 
 /**
- *
+ * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
 export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps, FeedbackFormScreenState> {
   public constructor(props: FeedbackFormScreenProps) {
@@ -137,7 +46,7 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
   };
 
   /**
-   *
+   * Renders the feedback form screen.
    */
   public render(): React.ReactNode {
     const { closeScreen, text, styles } = this.props;
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
new file mode 100644
index 0000000000..a0d4fd6795
--- /dev/null
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
@@ -0,0 +1,37 @@
+import type { TextStyle, ViewStyle } from 'react-native';
+
+export interface FeedbackFormScreenProps {
+  closeScreen: () => void;
+  text: FeedbackFormText;
+  styles?: FeedbackFormScreenStyles;
+}
+
+export interface FeedbackFormText {
+  formTitle?: string;
+  namePlaceholder?: string;
+  emailPlaceholder?: string;
+  descriptionPlaceholder?: string;
+  addAttachmentButton?: string;
+  submitButton?: string;
+  cancelButton?: string;
+  formError?: string;
+}
+
+export interface FeedbackFormScreenStyles {
+  container?: ViewStyle;
+  title?: TextStyle;
+  input?: TextStyle;
+  textArea?: ViewStyle;
+  screenshotButton?: ViewStyle;
+  screenshotText?: TextStyle;
+  submitButton?: ViewStyle;
+  submitText?: TextStyle;
+  cancelButton?: ViewStyle;
+  cancelText?: TextStyle;
+}
+
+export interface FeedbackFormScreenState {
+  name: string;
+  email: string;
+  description: string;
+}
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 532966ad11..7bce1b6067 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -162,7 +162,7 @@ const ErrorsTabNavigator = Sentry.withProfiler(
                   closeScreen={props.navigation.goBack}
                   styles={{
                     submitButton: {
-                      backgroundColor: '#ff0000',
+                      backgroundColor: '#6a1b9a',
                       paddingVertical: 15,
                       borderRadius: 5,
                       alignItems: 'center',

From 8eaa61d34cb062caf10b0e7113df009965b43b43 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 17:47:01 +0200
Subject: [PATCH 20/73] Removes attachment button to be added back separately
 along with the implementation

---
 .../core/src/js/feedback/FeedbackFormScreen.styles.ts | 11 -----------
 packages/core/src/js/feedback/FeedbackFormScreen.tsx  |  9 ---------
 .../core/src/js/feedback/FeedbackFormScreen.types.ts  |  3 ---
 samples/react-native/src/App.tsx                      |  2 +-
 4 files changed, 1 insertion(+), 24 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts b/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
index d5fff2f385..dbc936c7f3 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
@@ -25,17 +25,6 @@ const defaultStyles = StyleSheet.create({
     height: 100,
     textAlignVertical: 'top',
   },
-  screenshotButton: {
-    backgroundColor: '#eee',
-    padding: 15,
-    borderRadius: 5,
-    marginBottom: 20,
-    alignItems: 'center',
-  },
-  screenshotText: {
-    color: '#333',
-    fontSize: 16,
-  },
   submitButton: {
     backgroundColor: '#6a1b9a',
     paddingVertical: 15,
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 0c250b8950..c9a984897f 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -40,11 +40,6 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
     closeScreen();
   };
 
-  public addScreenshot: () => void = () => {
-    Alert.alert('Info', 'Attachments are not supported yet.');
-    // TODO
-  };
-
   /**
    * Renders the feedback form screen.
    */
@@ -79,10 +74,6 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
         multiline
       />
 
-      <TouchableOpacity style={styles?.screenshotButton || defaultStyles.screenshotButton} onPress={this.addScreenshot}>
-        <Text style={styles?.screenshotText || defaultStyles.screenshotText}>{text?.addAttachmentButton || 'Add Screenshot'}</Text>
-      </TouchableOpacity>
-
       <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={this.handleFeedbackSubmit}>
         <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || 'Send Feedback'}</Text>
       </TouchableOpacity>
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
index a0d4fd6795..85b36b95d5 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
@@ -11,7 +11,6 @@ export interface FeedbackFormText {
   namePlaceholder?: string;
   emailPlaceholder?: string;
   descriptionPlaceholder?: string;
-  addAttachmentButton?: string;
   submitButton?: string;
   cancelButton?: string;
   formError?: string;
@@ -22,8 +21,6 @@ export interface FeedbackFormScreenStyles {
   title?: TextStyle;
   input?: TextStyle;
   textArea?: ViewStyle;
-  screenshotButton?: ViewStyle;
-  screenshotText?: TextStyle;
   submitButton?: ViewStyle;
   submitText?: TextStyle;
   cancelButton?: ViewStyle;
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 7bce1b6067..4599d5725c 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -169,7 +169,7 @@ const ErrorsTabNavigator = Sentry.withProfiler(
                       marginBottom: 10,
                     },
                   }}
-                  text={{namePlaceholder: 'Fullname', addAttachmentButton: 'Add Attachment'}}
+                  text={{namePlaceholder: 'Fullname'}}
                 />
               )}
             </Stack.Screen>

From 0b88cc55f433c851a64037199aa5429e8e16f9ea Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 17:56:22 +0200
Subject: [PATCH 21/73] Add basic field validation

---
 .../core/src/js/feedback/FeedbackFormScreen.tsx  | 16 +++++++++++++++-
 .../src/js/feedback/FeedbackFormScreen.types.ts  |  1 +
 2 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index c9a984897f..5f4e996c97 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -24,12 +24,21 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
     const { name, email, description } = this.state;
     const { closeScreen, text } = this.props;
 
-    if (!name || !email || !description) {
+    const trimmedName = name?.trim();
+    const trimmedEmail = email?.trim();
+    const trimmedDescription = description?.trim();
+
+    if (!trimmedName || !trimmedEmail || !trimmedDescription) {
       const errorMessage = text?.formError || 'Please fill out all required fields.';
       Alert.alert('Error', errorMessage);
       return;
     }
 
+    if (!this._isValidEmail(trimmedEmail)) {
+      Alert.alert('Error', text?.emailError || 'Please enter a valid email address.');
+      return;
+    }
+
     const userFeedback: SendFeedbackParams = {
       message: description,
       name,
@@ -84,4 +93,9 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
     </View>
     );
   }
+
+  private _isValidEmail = (email: string): boolean => {
+    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+    return emailRegex.test(email);
+  };
 }
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
index 85b36b95d5..ca21d7df8f 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
@@ -14,6 +14,7 @@ export interface FeedbackFormText {
   submitButton?: string;
   cancelButton?: string;
   formError?: string;
+  emailError?: string;
 }
 
 export interface FeedbackFormScreenStyles {

From ae11b8d26b5674654efa02a6fa3fc66390b51de8 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 18:05:41 +0200
Subject: [PATCH 22/73] Adds changelog

---
 CHANGELOG.md | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66be6a45c1..425182efd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,28 @@
   });
   ```
 
+  - Adds feedback form ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+  You can add the form component in your UI and customise it like:
+  ```jsx
+  import { FeedbackFormScreen } from "@sentry/react-native";
+  ...
+  <FeedbackFormScreen
+    {...props}
+    closeScreen={props.navigation.goBack}
+    styles={{
+      submitButton: {
+        backgroundColor: '#6a1b9a',
+        paddingVertical: 15,
+        borderRadius: 5,
+        alignItems: 'center',
+        marginBottom: 10,
+      },
+    }}
+    text={{namePlaceholder: 'Fullname'}}
+  />
+  ```
+  Check [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/) for more configuration options.
+
 ### Fixes
 
 - Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))

From 7f2ca06b5520cb4a3f2ed31e548f640a381dc8de Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 18:06:27 +0200
Subject: [PATCH 23/73] Updates changelog

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 425182efd5..f056754b1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,7 @@
   });
   ```
 
-  - Adds feedback form ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+- Adds feedback form ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
   You can add the form component in your UI and customise it like:
   ```jsx
   import { FeedbackFormScreen } from "@sentry/react-native";

From 064b6c4f6f70b63cc50ce961409e1548b601ec18 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 18:07:18 +0200
Subject: [PATCH 24/73] Updates changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f056754b1d..d946cd5c4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,7 @@
   ```
 
 - Adds feedback form ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+
   You can add the form component in your UI and customise it like:
   ```jsx
   import { FeedbackFormScreen } from "@sentry/react-native";

From e21718a28ca072608fc2301af453e1c56d9ab3b4 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 18:47:30 +0200
Subject: [PATCH 25/73] Trim whitespaces from the submitted feedback

---
 packages/core/src/js/feedback/FeedbackFormScreen.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 5f4e996c97..219225ec08 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -40,9 +40,9 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
     }
 
     const userFeedback: SendFeedbackParams = {
-      message: description,
-      name,
-      email,
+      message: trimmedDescription,
+      name: trimmedName,
+      email: trimmedEmail,
     };
 
     captureFeedback(userFeedback);

From 407f179b425be81ce759868d81d4e81d19705f96 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 2 Dec 2024 19:41:06 +0200
Subject: [PATCH 26/73] Adds tests

---
 .../src/js/feedback/FeedbackFormScreen.tsx    |   5 +-
 .../js/feedback/FeedbackFormScreen.types.ts   |   1 +
 .../test/feedback/FeedbackFormScreen.test.tsx | 111 ++++++++++++++++++
 3 files changed, 115 insertions(+), 2 deletions(-)
 create mode 100644 packages/core/test/feedback/FeedbackFormScreen.test.tsx

diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
index 219225ec08..aea2a38fe0 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.tsx
@@ -30,12 +30,13 @@ export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps,
 
     if (!trimmedName || !trimmedEmail || !trimmedDescription) {
       const errorMessage = text?.formError || 'Please fill out all required fields.';
-      Alert.alert('Error', errorMessage);
+      Alert.alert(text?.errorTitle || 'Error', errorMessage);
       return;
     }
 
     if (!this._isValidEmail(trimmedEmail)) {
-      Alert.alert('Error', text?.emailError || 'Please enter a valid email address.');
+      const errorMessage = text?.emailError || 'Please enter a valid email address.';
+      Alert.alert(text?.errorTitle || 'Error', errorMessage);
       return;
     }
 
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
index ca21d7df8f..dbe5ce850b 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
+++ b/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
@@ -13,6 +13,7 @@ export interface FeedbackFormText {
   descriptionPlaceholder?: string;
   submitButton?: string;
   cancelButton?: string;
+  errorTitle?: string;
   formError?: string;
   emailError?: string;
 }
diff --git a/packages/core/test/feedback/FeedbackFormScreen.test.tsx b/packages/core/test/feedback/FeedbackFormScreen.test.tsx
new file mode 100644
index 0000000000..3232186f7b
--- /dev/null
+++ b/packages/core/test/feedback/FeedbackFormScreen.test.tsx
@@ -0,0 +1,111 @@
+import { captureFeedback } from '@sentry/core';
+import { fireEvent, render, waitFor } from '@testing-library/react-native';
+import * as React from 'react';
+import { Alert } from 'react-native';
+
+import { FeedbackFormScreen } from '../../src/js/feedback/FeedbackFormScreen';
+import type { FeedbackFormScreenProps } from '../../src/js/feedback/FeedbackFormScreen.types';
+
+const mockCloseScreen = jest.fn();
+
+jest.spyOn(Alert, 'alert');
+
+jest.mock('@sentry/core', () => ({
+  captureFeedback: jest.fn(),
+}));
+
+const defaultProps: FeedbackFormScreenProps = {
+  closeScreen: mockCloseScreen,
+  text: {
+    formTitle: 'Feedback Form',
+    namePlaceholder: 'Name',
+    emailPlaceholder: 'Email',
+    descriptionPlaceholder: 'Description',
+    submitButton: 'Submit',
+    cancelButton: 'Cancel',
+    errorTitle: 'Error',
+    formError: 'Please fill out all required fields.',
+    emailError: 'The email address is not valid.',
+  },
+};
+
+describe('FeedbackFormScreen', () => {
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it('renders correctly', () => {
+    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    expect(getByText(defaultProps.text.formTitle)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.text.namePlaceholder)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.text.emailPlaceholder)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.text.descriptionPlaceholder)).toBeTruthy();
+    expect(getByText(defaultProps.text.submitButton)).toBeTruthy();
+    expect(getByText(defaultProps.text.cancelButton)).toBeTruthy();
+  });
+
+  it('shows an error message if required fields are empty', async () => {
+    const { getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    fireEvent.press(getByText(defaultProps.text.submitButton));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.text.errorTitle, defaultProps.text.formError);
+    });
+  });
+
+  it('shows an error message if the email is not valid', async () => {
+    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'not-an-email');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.text.submitButton));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.text.errorTitle, defaultProps.text.emailError);
+    });
+  });
+
+  it('calls captureFeedback when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.text.submitButton));
+
+    await waitFor(() => {
+      expect(captureFeedback).toHaveBeenCalledWith({
+        message: 'This is a feedback message.',
+        name: 'John Doe',
+        email: 'john.doe@example.com',
+      });
+    });
+  });
+
+  it('calls closeScreen when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.text.submitButton));
+
+    await waitFor(() => {
+      expect(mockCloseScreen).toHaveBeenCalled();
+    });
+  });
+
+  it('calls closeScreen when the cancel button is pressed', () => {
+    const { getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+
+    fireEvent.press(getByText(defaultProps.text.cancelButton));
+
+    expect(mockCloseScreen).toHaveBeenCalled();
+  });
+});

From ddade0036d6c24513d7942c841b59849f5bc345c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 6 Dec 2024 13:53:21 +0100
Subject: [PATCH 27/73] Renames FeedbackFormScreen to FeedbackForm

---
 CHANGELOG.md                                  |  4 ++--
 ...creen.styles.ts => FeedbackForm.styles.ts} |  0
 ...eedbackFormScreen.tsx => FeedbackForm.tsx} |  8 ++++----
 ...mScreen.types.ts => FeedbackForm.types.ts} |  8 ++++----
 packages/core/src/js/index.ts                 |  2 +-
 ...mScreen.test.tsx => FeedbackForm.test.tsx} | 20 +++++++++----------
 samples/react-native/src/App.tsx              |  4 ++--
 7 files changed, 23 insertions(+), 23 deletions(-)
 rename packages/core/src/js/feedback/{FeedbackFormScreen.styles.ts => FeedbackForm.styles.ts} (100%)
 rename packages/core/src/js/feedback/{FeedbackFormScreen.tsx => FeedbackForm.tsx} (91%)
 rename packages/core/src/js/feedback/{FeedbackFormScreen.types.ts => FeedbackForm.types.ts} (79%)
 rename packages/core/test/feedback/{FeedbackFormScreen.test.tsx => FeedbackForm.test.tsx} (87%)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 91d3f0f9ce..40dbcec5da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,9 +39,9 @@
 
   You can add the form component in your UI and customise it like:
   ```jsx
-  import { FeedbackFormScreen } from "@sentry/react-native";
+  import { FeedbackForm } from "@sentry/react-native";
   ...
-  <FeedbackFormScreen
+  <FeedbackForm
     {...props}
     closeScreen={props.navigation.goBack}
     styles={{
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
similarity index 100%
rename from packages/core/src/js/feedback/FeedbackFormScreen.styles.ts
rename to packages/core/src/js/feedback/FeedbackForm.styles.ts
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
similarity index 91%
rename from packages/core/src/js/feedback/FeedbackFormScreen.tsx
rename to packages/core/src/js/feedback/FeedbackForm.tsx
index aea2a38fe0..a4e8972be5 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -4,14 +4,14 @@ import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
-import defaultStyles from './FeedbackFormScreen.styles';
-import type { FeedbackFormScreenProps, FeedbackFormScreenState } from './FeedbackFormScreen.types';
+import defaultStyles from './FeedbackForm.styles';
+import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
 
 /**
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
-export class FeedbackFormScreen extends React.Component<FeedbackFormScreenProps, FeedbackFormScreenState> {
-  public constructor(props: FeedbackFormScreenProps) {
+export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
+  public constructor(props: FeedbackFormProps) {
     super(props);
     this.state = {
       name: '',
diff --git a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
similarity index 79%
rename from packages/core/src/js/feedback/FeedbackFormScreen.types.ts
rename to packages/core/src/js/feedback/FeedbackForm.types.ts
index dbe5ce850b..7ece6590ac 100644
--- a/packages/core/src/js/feedback/FeedbackFormScreen.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,9 +1,9 @@
 import type { TextStyle, ViewStyle } from 'react-native';
 
-export interface FeedbackFormScreenProps {
+export interface FeedbackFormProps {
   closeScreen: () => void;
   text: FeedbackFormText;
-  styles?: FeedbackFormScreenStyles;
+  styles?: FeedbackFormStyles;
 }
 
 export interface FeedbackFormText {
@@ -18,7 +18,7 @@ export interface FeedbackFormText {
   emailError?: string;
 }
 
-export interface FeedbackFormScreenStyles {
+export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
   input?: TextStyle;
@@ -29,7 +29,7 @@ export interface FeedbackFormScreenStyles {
   cancelText?: TextStyle;
 }
 
-export interface FeedbackFormScreenState {
+export interface FeedbackFormState {
   name: string;
   email: string;
   description: string;
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index ba28710da8..5dc78efa45 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -84,4 +84,4 @@ export type { TimeToDisplayProps } from './tracing';
 
 export { Mask, Unmask } from './replay/CustomMask';
 
-export { FeedbackFormScreen } from './feedback/FeedbackFormScreen';
+export { FeedbackForm } from './feedback/FeedbackForm';
diff --git a/packages/core/test/feedback/FeedbackFormScreen.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
similarity index 87%
rename from packages/core/test/feedback/FeedbackFormScreen.test.tsx
rename to packages/core/test/feedback/FeedbackForm.test.tsx
index 3232186f7b..4f26fab428 100644
--- a/packages/core/test/feedback/FeedbackFormScreen.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -3,8 +3,8 @@ import { fireEvent, render, waitFor } from '@testing-library/react-native';
 import * as React from 'react';
 import { Alert } from 'react-native';
 
-import { FeedbackFormScreen } from '../../src/js/feedback/FeedbackFormScreen';
-import type { FeedbackFormScreenProps } from '../../src/js/feedback/FeedbackFormScreen.types';
+import { FeedbackForm } from '../../src/js/feedback/FeedbackForm';
+import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types';
 
 const mockCloseScreen = jest.fn();
 
@@ -14,7 +14,7 @@ jest.mock('@sentry/core', () => ({
   captureFeedback: jest.fn(),
 }));
 
-const defaultProps: FeedbackFormScreenProps = {
+const defaultProps: FeedbackFormProps = {
   closeScreen: mockCloseScreen,
   text: {
     formTitle: 'Feedback Form',
@@ -29,13 +29,13 @@ const defaultProps: FeedbackFormScreenProps = {
   },
 };
 
-describe('FeedbackFormScreen', () => {
+describe('FeedbackForm', () => {
   afterEach(() => {
     jest.clearAllMocks();
   });
 
   it('renders correctly', () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     expect(getByText(defaultProps.text.formTitle)).toBeTruthy();
     expect(getByPlaceholderText(defaultProps.text.namePlaceholder)).toBeTruthy();
@@ -46,7 +46,7 @@ describe('FeedbackFormScreen', () => {
   });
 
   it('shows an error message if required fields are empty', async () => {
-    const { getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.press(getByText(defaultProps.text.submitButton));
 
@@ -56,7 +56,7 @@ describe('FeedbackFormScreen', () => {
   });
 
   it('shows an error message if the email is not valid', async () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'not-an-email');
@@ -70,7 +70,7 @@ describe('FeedbackFormScreen', () => {
   });
 
   it('calls captureFeedback when the form is submitted successfully', async () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
@@ -88,7 +88,7 @@ describe('FeedbackFormScreen', () => {
   });
 
   it('calls closeScreen when the form is submitted successfully', async () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
     fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
@@ -102,7 +102,7 @@ describe('FeedbackFormScreen', () => {
   });
 
   it('calls closeScreen when the cancel button is pressed', () => {
-    const { getByText } = render(<FeedbackFormScreen {...defaultProps} />);
+    const { getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.press(getByText(defaultProps.text.cancelButton));
 
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 4599d5725c..cd2656edcf 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -16,7 +16,7 @@ import Animated, {
 
 // Import the Sentry React Native SDK
 import * as Sentry from '@sentry/react-native';
-import { FeedbackFormScreen } from '@sentry/react-native';
+import { FeedbackForm } from '@sentry/react-native';
 
 import { SENTRY_INTERNAL_DSN } from './dsn';
 import ErrorsScreen from './Screens/ErrorsScreen';
@@ -157,7 +157,7 @@ const ErrorsTabNavigator = Sentry.withProfiler(
               options={{ presentation: 'modal', headerShown: false }}
             >
               {(props) => (
-                <FeedbackFormScreen
+                <FeedbackForm
                   {...props}
                   closeScreen={props.navigation.goBack}
                   styles={{

From 1bc1e4c5cc33a1080f0e0768e8ff221351107fde Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 6 Dec 2024 14:01:34 +0100
Subject: [PATCH 28/73] Add beta label

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index a4e8972be5..b36029cc4e 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -8,6 +8,7 @@ import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
 
 /**
+ * @beta
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
 export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {

From 793475679677fde090d48686116c7346dde51663 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 6 Dec 2024 14:20:51 +0100
Subject: [PATCH 29/73] Extract default text to constants

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 30 ++++++++++++-------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index b36029cc4e..9270227404 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -7,6 +7,16 @@ import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
 
+const defaultFormTitle = 'Feedback Form';
+const defaultNamePlaceholder ='Name';
+const defaultEmailPlaceholder = 'Email';
+const defaultDescriptionPlaceholder = 'Description (required)';
+const defaultSubmitButton = 'Send Feedback';
+const defaultCancelButton = 'Cancel';
+const defaultErrorTitle = 'Error';
+const defaultFormError = 'Please fill out all required fields.';
+const defaultEmailError = 'Please enter a valid email address.';
+
 /**
  * @beta
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
@@ -30,14 +40,14 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     const trimmedDescription = description?.trim();
 
     if (!trimmedName || !trimmedEmail || !trimmedDescription) {
-      const errorMessage = text?.formError || 'Please fill out all required fields.';
-      Alert.alert(text?.errorTitle || 'Error', errorMessage);
+      const errorMessage = text?.formError || defaultFormError;
+      Alert.alert(text?.errorTitle || defaultErrorTitle, errorMessage);
       return;
     }
 
     if (!this._isValidEmail(trimmedEmail)) {
-      const errorMessage = text?.emailError || 'Please enter a valid email address.';
-      Alert.alert(text?.errorTitle || 'Error', errorMessage);
+      const errorMessage = text?.emailError || defaultEmailError;
+      Alert.alert(text?.errorTitle || defaultErrorTitle, errorMessage);
       return;
     }
 
@@ -60,18 +70,18 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     return (
     <View style={styles?.container || defaultStyles.container}>
-      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || 'Feedback Form'}</Text>
+      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || defaultFormTitle}</Text>
 
       <TextInput
         style={styles?.input || defaultStyles.input}
-        placeholder={text?.namePlaceholder || 'Name'}
+        placeholder={text?.namePlaceholder || defaultNamePlaceholder}
         value={name}
         onChangeText={(value) => this.setState({ name: value })}
         />
 
       <TextInput
         style={styles?.input || defaultStyles.input}
-        placeholder={text?.emailPlaceholder || 'Email'}
+        placeholder={text?.emailPlaceholder || defaultEmailPlaceholder}
         keyboardType={'email-address' as KeyboardTypeOptions}
         value={email}
         onChangeText={(value) => this.setState({ email: value })}
@@ -79,18 +89,18 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
       <TextInput
         style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
-        placeholder={text?.descriptionPlaceholder || 'Description (required)'}
+        placeholder={text?.descriptionPlaceholder || defaultDescriptionPlaceholder}
         value={description}
         onChangeText={(value) => this.setState({ description: value })}
         multiline
       />
 
       <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={this.handleFeedbackSubmit}>
-        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || 'Send Feedback'}</Text>
+        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || defaultSubmitButton}</Text>
       </TouchableOpacity>
 
       <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
-        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || 'Cancel'}</Text>
+        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || defaultCancelButton}</Text>
       </TouchableOpacity>
     </View>
     );

From a1693629c72960e611fe49500e1e1a2d79f389e3 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 11:20:06 +0200
Subject: [PATCH 30/73] Moves constant to a separate file and aligns naming
 with JS

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 40 +++++++++----------
 packages/core/src/js/feedback/constants.ts    |  9 +++++
 2 files changed, 29 insertions(+), 20 deletions(-)
 create mode 100644 packages/core/src/js/feedback/constants.ts

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 9270227404..b110844733 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -4,19 +4,19 @@ import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
+import {
+  CANCEL_BUTTON_LABEL,
+  EMAIL_ERROR,
+  EMAIL_PLACEHOLDER,
+  ERROR_TITLE,
+  FORM_ERROR,
+  FORM_TITLE,
+  MESSAGE_PLACEHOLDER,
+  NAME_PLACEHOLDER,
+  SUBMIT_BUTTON_LABEL} from './constants';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
 
-const defaultFormTitle = 'Feedback Form';
-const defaultNamePlaceholder ='Name';
-const defaultEmailPlaceholder = 'Email';
-const defaultDescriptionPlaceholder = 'Description (required)';
-const defaultSubmitButton = 'Send Feedback';
-const defaultCancelButton = 'Cancel';
-const defaultErrorTitle = 'Error';
-const defaultFormError = 'Please fill out all required fields.';
-const defaultEmailError = 'Please enter a valid email address.';
-
 /**
  * @beta
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
@@ -40,14 +40,14 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     const trimmedDescription = description?.trim();
 
     if (!trimmedName || !trimmedEmail || !trimmedDescription) {
-      const errorMessage = text?.formError || defaultFormError;
-      Alert.alert(text?.errorTitle || defaultErrorTitle, errorMessage);
+      const errorMessage = text?.formError || FORM_ERROR;
+      Alert.alert(text?.errorTitle || ERROR_TITLE, errorMessage);
       return;
     }
 
     if (!this._isValidEmail(trimmedEmail)) {
-      const errorMessage = text?.emailError || defaultEmailError;
-      Alert.alert(text?.errorTitle || defaultErrorTitle, errorMessage);
+      const errorMessage = text?.emailError || EMAIL_ERROR;
+      Alert.alert(text?.errorTitle || ERROR_TITLE, errorMessage);
       return;
     }
 
@@ -70,18 +70,18 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     return (
     <View style={styles?.container || defaultStyles.container}>
-      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || defaultFormTitle}</Text>
+      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || FORM_TITLE}</Text>
 
       <TextInput
         style={styles?.input || defaultStyles.input}
-        placeholder={text?.namePlaceholder || defaultNamePlaceholder}
+        placeholder={text?.namePlaceholder || NAME_PLACEHOLDER}
         value={name}
         onChangeText={(value) => this.setState({ name: value })}
         />
 
       <TextInput
         style={styles?.input || defaultStyles.input}
-        placeholder={text?.emailPlaceholder || defaultEmailPlaceholder}
+        placeholder={text?.emailPlaceholder || EMAIL_PLACEHOLDER}
         keyboardType={'email-address' as KeyboardTypeOptions}
         value={email}
         onChangeText={(value) => this.setState({ email: value })}
@@ -89,18 +89,18 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
       <TextInput
         style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
-        placeholder={text?.descriptionPlaceholder || defaultDescriptionPlaceholder}
+        placeholder={text?.descriptionPlaceholder || MESSAGE_PLACEHOLDER}
         value={description}
         onChangeText={(value) => this.setState({ description: value })}
         multiline
       />
 
       <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={this.handleFeedbackSubmit}>
-        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || defaultSubmitButton}</Text>
+        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || SUBMIT_BUTTON_LABEL}</Text>
       </TouchableOpacity>
 
       <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
-        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || defaultCancelButton}</Text>
+        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || CANCEL_BUTTON_LABEL}</Text>
       </TouchableOpacity>
     </View>
     );
diff --git a/packages/core/src/js/feedback/constants.ts b/packages/core/src/js/feedback/constants.ts
new file mode 100644
index 0000000000..649cc1f86c
--- /dev/null
+++ b/packages/core/src/js/feedback/constants.ts
@@ -0,0 +1,9 @@
+export const FORM_TITLE = 'Report a Bug';
+export const NAME_PLACEHOLDER = 'Name';
+export const EMAIL_PLACEHOLDER = 'Email';
+export const MESSAGE_PLACEHOLDER = 'Description';
+export const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
+export const CANCEL_BUTTON_LABEL = 'Cancel';
+export const ERROR_TITLE = 'Error';
+export const FORM_ERROR = 'Please fill out all required fields.';
+export const EMAIL_ERROR = 'Please enter a valid email address.';

From 4b5df7a9312fd8f703f47d5df9ed1aadeb560385 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 12:21:25 +0200
Subject: [PATCH 31/73] Adds input text labels

---
 .../src/js/feedback/FeedbackForm.styles.ts    |  4 +++
 .../core/src/js/feedback/FeedbackForm.tsx     | 25 +++++++++++++++++--
 .../src/js/feedback/FeedbackForm.types.ts     |  5 ++++
 packages/core/src/js/feedback/LabelText.tsx   | 21 ++++++++++++++++
 packages/core/src/js/feedback/constants.ts    | 10 +++++---
 5 files changed, 60 insertions(+), 5 deletions(-)
 create mode 100644 packages/core/src/js/feedback/LabelText.tsx

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index dbc936c7f3..88dbfd52c8 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -12,6 +12,10 @@ const defaultStyles = StyleSheet.create({
     marginBottom: 20,
     textAlign: 'center',
   },
+  label: {
+    marginBottom: 4,
+    fontSize: 16,
+  },
   input: {
     height: 50,
     borderColor: '#ccc',
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index b110844733..ba6be6712a 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -7,15 +7,20 @@ import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 import {
   CANCEL_BUTTON_LABEL,
   EMAIL_ERROR,
+  EMAIL_LABEL,
   EMAIL_PLACEHOLDER,
   ERROR_TITLE,
   FORM_ERROR,
   FORM_TITLE,
+  IS_REQUIRED_LABEL,
+  MESSAGE_LABEL,
   MESSAGE_PLACEHOLDER,
+  NAME_LABEL,
   NAME_PLACEHOLDER,
   SUBMIT_BUTTON_LABEL} from './constants';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
+import LabelText from './LabelText';
 
 /**
  * @beta
@@ -72,13 +77,24 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     <View style={styles?.container || defaultStyles.container}>
       <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || FORM_TITLE}</Text>
 
+      <LabelText
+          label={text?.nameLabel || NAME_LABEL}
+          isRequired={true}
+          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
+          styles={styles?.label || defaultStyles.label}
+        />
       <TextInput
         style={styles?.input || defaultStyles.input}
         placeholder={text?.namePlaceholder || NAME_PLACEHOLDER}
         value={name}
         onChangeText={(value) => this.setState({ name: value })}
         />
-
+      <LabelText
+          label={text?.emailLabel || EMAIL_LABEL}
+          isRequired={true}
+          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
+          styles={styles?.label || defaultStyles.label}
+        />
       <TextInput
         style={styles?.input || defaultStyles.input}
         placeholder={text?.emailPlaceholder || EMAIL_PLACEHOLDER}
@@ -86,7 +102,12 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
         value={email}
         onChangeText={(value) => this.setState({ email: value })}
         />
-
+      <LabelText
+          label={text?.descriptionLabel || MESSAGE_LABEL}
+          isRequired={true}
+          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
+          styles={styles?.label || defaultStyles.label}
+        />
       <TextInput
         style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
         placeholder={text?.descriptionPlaceholder || MESSAGE_PLACEHOLDER}
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 7ece6590ac..0316573f18 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -8,9 +8,13 @@ export interface FeedbackFormProps {
 
 export interface FeedbackFormText {
   formTitle?: string;
+  nameLabel?: string;
   namePlaceholder?: string;
+  emailLabel?: string;
   emailPlaceholder?: string;
+  descriptionLabel?: string;
   descriptionPlaceholder?: string;
+  isRequiredLabel?: string;
   submitButton?: string;
   cancelButton?: string;
   errorTitle?: string;
@@ -21,6 +25,7 @@ export interface FeedbackFormText {
 export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
+  label?: TextStyle;
   input?: TextStyle;
   textArea?: ViewStyle;
   submitButton?: ViewStyle;
diff --git a/packages/core/src/js/feedback/LabelText.tsx b/packages/core/src/js/feedback/LabelText.tsx
new file mode 100644
index 0000000000..a03c69b207
--- /dev/null
+++ b/packages/core/src/js/feedback/LabelText.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import type { TextStyle } from 'react-native';
+import { Text } from 'react-native';
+
+interface LabelTextProps {
+  label: string;
+  isRequired: boolean;
+  isRequiredLabel: string;
+  styles: TextStyle;
+}
+
+const LabelText: React.FC<LabelTextProps> = ({ label, isRequired, isRequiredLabel, styles }) => {
+  return (
+    <Text style={styles}>
+      {label}
+      {isRequired && ` ${isRequiredLabel}`}
+    </Text>
+  );
+};
+
+export default LabelText;
diff --git a/packages/core/src/js/feedback/constants.ts b/packages/core/src/js/feedback/constants.ts
index 649cc1f86c..c30d30122f 100644
--- a/packages/core/src/js/feedback/constants.ts
+++ b/packages/core/src/js/feedback/constants.ts
@@ -1,7 +1,11 @@
 export const FORM_TITLE = 'Report a Bug';
-export const NAME_PLACEHOLDER = 'Name';
-export const EMAIL_PLACEHOLDER = 'Email';
-export const MESSAGE_PLACEHOLDER = 'Description';
+export const NAME_PLACEHOLDER = 'Your Name';
+export const NAME_LABEL = 'Name';
+export const EMAIL_PLACEHOLDER = 'your.email@example.org';
+export const EMAIL_LABEL = 'Email';
+export const MESSAGE_PLACEHOLDER = "What's the bug? What did you expect?";
+export const MESSAGE_LABEL = 'Description';
+export const IS_REQUIRED_LABEL = '(required)';
 export const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
 export const CANCEL_BUTTON_LABEL = 'Cancel';
 export const ERROR_TITLE = 'Error';

From 4fa81ce5ea53e0eaa171c15dcda7c6c27b17bc2a Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 16:09:21 +0200
Subject: [PATCH 32/73] Close screen before sending the feedback to minimise
 wait time

Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
---
 packages/core/src/js/feedback/FeedbackForm.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index ba6be6712a..d103675916 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -62,8 +62,8 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       email: trimmedEmail,
     };
 
-    captureFeedback(userFeedback);
     closeScreen();
+    captureFeedback(userFeedback);
   };
 
   /**

From 4fff82f0a66e7d34ca5191b33c9f29b70e5b7765 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 16:27:29 +0200
Subject: [PATCH 33/73] Rename file for consistency

---
 packages/core/src/js/feedback/FeedbackForm.tsx                  | 2 +-
 .../core/src/js/feedback/{FeedbackForm.types.ts => config.ts}   | 0
 packages/core/test/feedback/FeedbackForm.test.tsx               | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename packages/core/src/js/feedback/{FeedbackForm.types.ts => config.ts} (100%)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index d103675916..8585da1f46 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -19,7 +19,7 @@ import {
   NAME_PLACEHOLDER,
   SUBMIT_BUTTON_LABEL} from './constants';
 import defaultStyles from './FeedbackForm.styles';
-import type { FeedbackFormProps, FeedbackFormState } from './FeedbackForm.types';
+import type { FeedbackFormProps, FeedbackFormState } from './config';
 import LabelText from './LabelText';
 
 /**
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/config.ts
similarity index 100%
rename from packages/core/src/js/feedback/FeedbackForm.types.ts
rename to packages/core/src/js/feedback/config.ts
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 4f26fab428..8887318fb1 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Alert } from 'react-native';
 
 import { FeedbackForm } from '../../src/js/feedback/FeedbackForm';
-import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types';
+import type { FeedbackFormProps } from '../../src/js/feedback/config';
 
 const mockCloseScreen = jest.fn();
 

From 0258bf2c0ac778b1178da01c7b13320a2966225a Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 19:09:15 +0200
Subject: [PATCH 34/73] Flatten configuration hierarchy and clean up

---
 .../src/js/feedback/FeedbackForm.styles.ts    |  6 +-
 .../core/src/js/feedback/FeedbackForm.tsx     | 89 +++++++-----------
 .../src/js/feedback/FeedbackForm.types.ts     | 94 +++++++++++++++++++
 packages/core/src/js/feedback/LabelText.tsx   | 21 -----
 packages/core/src/js/feedback/config.ts       | 41 --------
 packages/core/src/js/feedback/constants.ts    | 45 ++++++---
 .../core/test/feedback/FeedbackForm.test.tsx  | 73 +++++++-------
 samples/react-native/src/App.tsx              |  2 +-
 8 files changed, 205 insertions(+), 166 deletions(-)
 create mode 100644 packages/core/src/js/feedback/FeedbackForm.types.ts
 delete mode 100644 packages/core/src/js/feedback/LabelText.tsx
 delete mode 100644 packages/core/src/js/feedback/config.ts

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index 88dbfd52c8..d54fb9cd83 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -1,6 +1,6 @@
-import { StyleSheet } from 'react-native';
+import type { FeedbackFormStyles } from './FeedbackForm.types';
 
-const defaultStyles = StyleSheet.create({
+const defaultStyles: FeedbackFormStyles = {
   container: {
     flex: 1,
     padding: 20,
@@ -49,6 +49,6 @@ const defaultStyles = StyleSheet.create({
     color: '#6a1b9a',
     fontSize: 16,
   },
-});
+};
 
 export default defaultStyles;
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 8585da1f46..3b68bab6b2 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -4,23 +4,9 @@ import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
-import {
-  CANCEL_BUTTON_LABEL,
-  EMAIL_ERROR,
-  EMAIL_LABEL,
-  EMAIL_PLACEHOLDER,
-  ERROR_TITLE,
-  FORM_ERROR,
-  FORM_TITLE,
-  IS_REQUIRED_LABEL,
-  MESSAGE_LABEL,
-  MESSAGE_PLACEHOLDER,
-  NAME_LABEL,
-  NAME_PLACEHOLDER,
-  SUBMIT_BUTTON_LABEL} from './constants';
+import { defaultConfiguration } from './constants';
 import defaultStyles from './FeedbackForm.styles';
-import type { FeedbackFormProps, FeedbackFormState } from './config';
-import LabelText from './LabelText';
+import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackTextConfiguration } from './FeedbackForm.types';
 
 /**
  * @beta
@@ -38,21 +24,20 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
-    const { closeScreen, text } = this.props;
+    const { closeScreen } = this.props;
+    const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
 
     const trimmedName = name?.trim();
     const trimmedEmail = email?.trim();
     const trimmedDescription = description?.trim();
 
     if (!trimmedName || !trimmedEmail || !trimmedDescription) {
-      const errorMessage = text?.formError || FORM_ERROR;
-      Alert.alert(text?.errorTitle || ERROR_TITLE, errorMessage);
+      Alert.alert(text.errorTitle, text.formError);
       return;
     }
 
     if (!this._isValidEmail(trimmedEmail)) {
-      const errorMessage = text?.emailError || EMAIL_ERROR;
-      Alert.alert(text?.errorTitle || ERROR_TITLE, errorMessage);
+      Alert.alert(text.errorTitle, text.emailError);
       return;
     }
 
@@ -70,58 +55,56 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
    * Renders the feedback form screen.
    */
   public render(): React.ReactNode {
-    const { closeScreen, text, styles } = this.props;
+    const { closeScreen } = this.props;
     const { name, email, description } = this.state;
+    const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
+    const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
 
     return (
-    <View style={styles?.container || defaultStyles.container}>
-      <Text style={styles?.title || defaultStyles.title}>{text?.formTitle || FORM_TITLE}</Text>
-
-      <LabelText
-          label={text?.nameLabel || NAME_LABEL}
-          isRequired={true}
-          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
-          styles={styles?.label || defaultStyles.label}
-        />
+    <View style={styles.container}>
+      <Text style={styles.title}>{text.formTitle}</Text>
+
+      <Text style={styles.label}>
+          {text.nameLabel}
+          {true && ` ${text.isRequiredLabel}`}
+      </Text>
       <TextInput
-        style={styles?.input || defaultStyles.input}
-        placeholder={text?.namePlaceholder || NAME_PLACEHOLDER}
+        style={styles.input}
+        placeholder={text.namePlaceholder}
         value={name}
         onChangeText={(value) => this.setState({ name: value })}
         />
-      <LabelText
-          label={text?.emailLabel || EMAIL_LABEL}
-          isRequired={true}
-          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
-          styles={styles?.label || defaultStyles.label}
-        />
+
+      <Text style={styles.label}>
+          {text.emailLabel}
+          {true && ` ${text.isRequiredLabel}`}
+      </Text>
       <TextInput
-        style={styles?.input || defaultStyles.input}
-        placeholder={text?.emailPlaceholder || EMAIL_PLACEHOLDER}
+        style={styles.input}
+        placeholder={text.emailPlaceholder}
         keyboardType={'email-address' as KeyboardTypeOptions}
         value={email}
         onChangeText={(value) => this.setState({ email: value })}
         />
-      <LabelText
-          label={text?.descriptionLabel || MESSAGE_LABEL}
-          isRequired={true}
-          isRequiredLabel={text?.isRequiredLabel || IS_REQUIRED_LABEL}
-          styles={styles?.label || defaultStyles.label}
-        />
+
+      <Text style={styles.label}>
+        {text.messageLabel}
+        {true && ` ${text.isRequiredLabel}`}
+      </Text>
       <TextInput
-        style={[styles?.input || defaultStyles.input, styles?.textArea || defaultStyles.textArea]}
-        placeholder={text?.descriptionPlaceholder || MESSAGE_PLACEHOLDER}
+        style={[styles.input, styles.textArea]}
+        placeholder={text.messagePlaceholder}
         value={description}
         onChangeText={(value) => this.setState({ description: value })}
         multiline
       />
 
-      <TouchableOpacity style={styles?.submitButton || defaultStyles.submitButton} onPress={this.handleFeedbackSubmit}>
-        <Text style={styles?.submitText || defaultStyles.submitText}>{text?.submitButton || SUBMIT_BUTTON_LABEL}</Text>
+      <TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
+        <Text style={styles.submitText}>{text.submitButtonLabel}</Text>
       </TouchableOpacity>
 
-      <TouchableOpacity style={styles?.cancelButton || defaultStyles.cancelButton} onPress={closeScreen}>
-        <Text style={styles?.cancelText || defaultStyles.cancelText}>{text?.cancelButton || CANCEL_BUTTON_LABEL}</Text>
+      <TouchableOpacity style={styles.cancelButton} onPress={closeScreen}>
+        <Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
       </TouchableOpacity>
     </View>
     );
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
new file mode 100644
index 0000000000..bfc958a4cf
--- /dev/null
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -0,0 +1,94 @@
+import type { TextStyle, ViewStyle } from 'react-native';
+
+export interface FeedbackFormProps extends FeedbackTextConfiguration {
+  closeScreen: () => void;
+  styles?: FeedbackFormStyles;
+}
+
+/**
+ * All of the different text labels that can be customized
+ */
+export interface FeedbackTextConfiguration {
+  /**
+   * The label for the Feedback form cancel button that closes dialog
+   */
+  cancelButtonLabel?: string;
+
+  /**
+   * The label for the Feedback form submit button that sends feedback
+   */
+  submitButtonLabel?: string;
+
+  /**
+   * The title of the Feedback form
+   */
+  formTitle?: string;
+
+  /**
+   * Label for the email input
+   */
+  emailLabel?: string;
+
+  /**
+   * Placeholder text for Feedback email input
+   */
+  emailPlaceholder?: string;
+
+  /**
+   * Label for the message input
+   */
+  messageLabel?: string;
+
+  /**
+   * Placeholder text for Feedback message input
+   */
+  messagePlaceholder?: string;
+
+  /**
+   * Label for the name input
+   */
+  nameLabel?: string;
+
+  /**
+   * Placeholder text for Feedback name input
+   */
+  namePlaceholder?: string;
+
+  /**
+   * Text which indicates that a field is required
+   */
+  isRequiredLabel?: string;
+
+  /**
+   * The title of the error dialog
+   */
+  errorTitle?: string;
+
+  /**
+   * The error message when the form is invalid
+   */
+  formError?: string;
+
+  /**
+   * The error message when the email is invalid
+   */
+  emailError?: string;
+}
+
+export interface FeedbackFormStyles {
+  container?: ViewStyle;
+  title?: TextStyle;
+  label?: TextStyle;
+  input?: TextStyle;
+  textArea?: TextStyle;
+  submitButton?: ViewStyle;
+  submitText?: TextStyle;
+  cancelButton?: ViewStyle;
+  cancelText?: TextStyle;
+}
+
+export interface FeedbackFormState {
+  name: string;
+  email: string;
+  description: string;
+}
diff --git a/packages/core/src/js/feedback/LabelText.tsx b/packages/core/src/js/feedback/LabelText.tsx
deleted file mode 100644
index a03c69b207..0000000000
--- a/packages/core/src/js/feedback/LabelText.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React from 'react';
-import type { TextStyle } from 'react-native';
-import { Text } from 'react-native';
-
-interface LabelTextProps {
-  label: string;
-  isRequired: boolean;
-  isRequiredLabel: string;
-  styles: TextStyle;
-}
-
-const LabelText: React.FC<LabelTextProps> = ({ label, isRequired, isRequiredLabel, styles }) => {
-  return (
-    <Text style={styles}>
-      {label}
-      {isRequired && ` ${isRequiredLabel}`}
-    </Text>
-  );
-};
-
-export default LabelText;
diff --git a/packages/core/src/js/feedback/config.ts b/packages/core/src/js/feedback/config.ts
deleted file mode 100644
index 0316573f18..0000000000
--- a/packages/core/src/js/feedback/config.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { TextStyle, ViewStyle } from 'react-native';
-
-export interface FeedbackFormProps {
-  closeScreen: () => void;
-  text: FeedbackFormText;
-  styles?: FeedbackFormStyles;
-}
-
-export interface FeedbackFormText {
-  formTitle?: string;
-  nameLabel?: string;
-  namePlaceholder?: string;
-  emailLabel?: string;
-  emailPlaceholder?: string;
-  descriptionLabel?: string;
-  descriptionPlaceholder?: string;
-  isRequiredLabel?: string;
-  submitButton?: string;
-  cancelButton?: string;
-  errorTitle?: string;
-  formError?: string;
-  emailError?: string;
-}
-
-export interface FeedbackFormStyles {
-  container?: ViewStyle;
-  title?: TextStyle;
-  label?: TextStyle;
-  input?: TextStyle;
-  textArea?: ViewStyle;
-  submitButton?: ViewStyle;
-  submitText?: TextStyle;
-  cancelButton?: ViewStyle;
-  cancelText?: TextStyle;
-}
-
-export interface FeedbackFormState {
-  name: string;
-  email: string;
-  description: string;
-}
diff --git a/packages/core/src/js/feedback/constants.ts b/packages/core/src/js/feedback/constants.ts
index c30d30122f..19df28885b 100644
--- a/packages/core/src/js/feedback/constants.ts
+++ b/packages/core/src/js/feedback/constants.ts
@@ -1,13 +1,32 @@
-export const FORM_TITLE = 'Report a Bug';
-export const NAME_PLACEHOLDER = 'Your Name';
-export const NAME_LABEL = 'Name';
-export const EMAIL_PLACEHOLDER = 'your.email@example.org';
-export const EMAIL_LABEL = 'Email';
-export const MESSAGE_PLACEHOLDER = "What's the bug? What did you expect?";
-export const MESSAGE_LABEL = 'Description';
-export const IS_REQUIRED_LABEL = '(required)';
-export const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
-export const CANCEL_BUTTON_LABEL = 'Cancel';
-export const ERROR_TITLE = 'Error';
-export const FORM_ERROR = 'Please fill out all required fields.';
-export const EMAIL_ERROR = 'Please enter a valid email address.';
+import type { FeedbackFormProps } from './FeedbackForm.types';
+
+const FORM_TITLE = 'Report a Bug';
+const NAME_PLACEHOLDER = 'Your Name';
+const NAME_LABEL = 'Name';
+const EMAIL_PLACEHOLDER = 'your.email@example.org';
+const EMAIL_LABEL = 'Email';
+const MESSAGE_PLACEHOLDER = "What's the bug? What did you expect?";
+const MESSAGE_LABEL = 'Description';
+const IS_REQUIRED_LABEL = '(required)';
+const SUBMIT_BUTTON_LABEL = 'Send Bug Report';
+const CANCEL_BUTTON_LABEL = 'Cancel';
+const ERROR_TITLE = 'Error';
+const FORM_ERROR = 'Please fill out all required fields.';
+const EMAIL_ERROR = 'Please enter a valid email address.';
+
+export const defaultConfiguration: Partial<FeedbackFormProps> = {
+  // FeedbackTextConfiguration
+  cancelButtonLabel: CANCEL_BUTTON_LABEL,
+  emailLabel: EMAIL_LABEL,
+  emailPlaceholder: EMAIL_PLACEHOLDER,
+  formTitle: FORM_TITLE,
+  isRequiredLabel: IS_REQUIRED_LABEL,
+  messageLabel: MESSAGE_LABEL,
+  messagePlaceholder: MESSAGE_PLACEHOLDER,
+  nameLabel: NAME_LABEL,
+  namePlaceholder: NAME_PLACEHOLDER,
+  submitButtonLabel: SUBMIT_BUTTON_LABEL,
+  errorTitle: ERROR_TITLE,
+  formError: FORM_ERROR,
+  emailError: EMAIL_ERROR,
+};
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 8887318fb1..b930c0afc0 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Alert } from 'react-native';
 
 import { FeedbackForm } from '../../src/js/feedback/FeedbackForm';
-import type { FeedbackFormProps } from '../../src/js/feedback/config';
+import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types';
 
 const mockCloseScreen = jest.fn();
 
@@ -16,17 +16,19 @@ jest.mock('@sentry/core', () => ({
 
 const defaultProps: FeedbackFormProps = {
   closeScreen: mockCloseScreen,
-  text: {
-    formTitle: 'Feedback Form',
-    namePlaceholder: 'Name',
-    emailPlaceholder: 'Email',
-    descriptionPlaceholder: 'Description',
-    submitButton: 'Submit',
-    cancelButton: 'Cancel',
-    errorTitle: 'Error',
-    formError: 'Please fill out all required fields.',
-    emailError: 'The email address is not valid.',
-  },
+  formTitle: 'Feedback Form',
+  nameLabel: 'Name',
+  namePlaceholder: 'Name Placeholder',
+  emailLabel: 'Email',
+  emailPlaceholder: 'Email Placeholder',
+  messageLabel: 'Description',
+  messagePlaceholder: 'Description Placeholder',
+  submitButtonLabel: 'Submit',
+  cancelButtonLabel: 'Cancel',
+  isRequiredLabel: '(required)',
+  errorTitle: 'Error',
+  formError: 'Please fill out all required fields.',
+  emailError: 'The email address is not valid.',
 };
 
 describe('FeedbackForm', () => {
@@ -37,46 +39,49 @@ describe('FeedbackForm', () => {
   it('renders correctly', () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    expect(getByText(defaultProps.text.formTitle)).toBeTruthy();
-    expect(getByPlaceholderText(defaultProps.text.namePlaceholder)).toBeTruthy();
-    expect(getByPlaceholderText(defaultProps.text.emailPlaceholder)).toBeTruthy();
-    expect(getByPlaceholderText(defaultProps.text.descriptionPlaceholder)).toBeTruthy();
-    expect(getByText(defaultProps.text.submitButton)).toBeTruthy();
-    expect(getByText(defaultProps.text.cancelButton)).toBeTruthy();
+    expect(getByText(defaultProps.formTitle)).toBeTruthy();
+    expect(getByText(`${defaultProps.nameLabel  } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.namePlaceholder)).toBeTruthy();
+    expect(getByText(`${defaultProps.emailLabel } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.emailPlaceholder)).toBeTruthy();
+    expect(getByText(`${defaultProps.messageLabel } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByPlaceholderText(defaultProps.messagePlaceholder)).toBeTruthy();
+    expect(getByText(defaultProps.submitButtonLabel)).toBeTruthy();
+    expect(getByText(defaultProps.cancelButtonLabel)).toBeTruthy();
   });
 
   it('shows an error message if required fields are empty', async () => {
     const { getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    fireEvent.press(getByText(defaultProps.text.submitButton));
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
 
     await waitFor(() => {
-      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.text.errorTitle, defaultProps.text.formError);
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.formError);
     });
   });
 
   it('shows an error message if the email is not valid', async () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'not-an-email');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'not-an-email');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
 
-    fireEvent.press(getByText(defaultProps.text.submitButton));
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
 
     await waitFor(() => {
-      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.text.errorTitle, defaultProps.text.emailError);
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.errorTitle, defaultProps.emailError);
     });
   });
 
   it('calls captureFeedback when the form is submitted successfully', async () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
 
-    fireEvent.press(getByText(defaultProps.text.submitButton));
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
 
     await waitFor(() => {
       expect(captureFeedback).toHaveBeenCalledWith({
@@ -90,11 +95,11 @@ describe('FeedbackForm', () => {
   it('calls closeScreen when the form is submitted successfully', async () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.namePlaceholder), 'John Doe');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.emailPlaceholder), 'john.doe@example.com');
-    fireEvent.changeText(getByPlaceholderText(defaultProps.text.descriptionPlaceholder), 'This is a feedback message.');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
 
-    fireEvent.press(getByText(defaultProps.text.submitButton));
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
 
     await waitFor(() => {
       expect(mockCloseScreen).toHaveBeenCalled();
@@ -104,7 +109,7 @@ describe('FeedbackForm', () => {
   it('calls closeScreen when the cancel button is pressed', () => {
     const { getByText } = render(<FeedbackForm {...defaultProps} />);
 
-    fireEvent.press(getByText(defaultProps.text.cancelButton));
+    fireEvent.press(getByText(defaultProps.cancelButtonLabel));
 
     expect(mockCloseScreen).toHaveBeenCalled();
   });
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index cd2656edcf..054a1ad6ce 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -169,7 +169,7 @@ const ErrorsTabNavigator = Sentry.withProfiler(
                       marginBottom: 10,
                     },
                   }}
-                  text={{namePlaceholder: 'Fullname'}}
+                  namePlaceholder={'Fullname'}
                 />
               )}
             </Stack.Screen>

From 458ebc2afd2a75b95be1b8a16973ee508e6b5a1a Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 19:44:51 +0200
Subject: [PATCH 35/73] Align required values with JS

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 14 +++++-----
 .../src/js/feedback/FeedbackForm.types.ts     | 27 ++++++++++++++++++-
 packages/core/src/js/feedback/constants.ts    |  6 +++++
 .../core/test/feedback/FeedbackForm.test.tsx  |  9 ++++---
 4 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 3b68bab6b2..2f9fde5501 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -6,7 +6,7 @@ import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
 import { defaultConfiguration } from './constants';
 import defaultStyles from './FeedbackForm.styles';
-import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackTextConfiguration } from './FeedbackForm.types';
+import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
 
 /**
  * @beta
@@ -25,18 +25,19 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
     const { closeScreen } = this.props;
+    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
     const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
 
     const trimmedName = name?.trim();
     const trimmedEmail = email?.trim();
     const trimmedDescription = description?.trim();
 
-    if (!trimmedName || !trimmedEmail || !trimmedDescription) {
+    if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
       Alert.alert(text.errorTitle, text.formError);
       return;
     }
 
-    if (!this._isValidEmail(trimmedEmail)) {
+    if (config.isEmailRequired && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
@@ -57,6 +58,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
   public render(): React.ReactNode {
     const { closeScreen } = this.props;
     const { name, email, description } = this.state;
+    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
     const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
     const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
 
@@ -66,7 +68,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
       <Text style={styles.label}>
           {text.nameLabel}
-          {true && ` ${text.isRequiredLabel}`}
+          {config.isNameRequired && ` ${text.isRequiredLabel}`}
       </Text>
       <TextInput
         style={styles.input}
@@ -77,7 +79,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
       <Text style={styles.label}>
           {text.emailLabel}
-          {true && ` ${text.isRequiredLabel}`}
+          {config.isEmailRequired && ` ${text.isRequiredLabel}`}
       </Text>
       <TextInput
         style={styles.input}
@@ -89,7 +91,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
       <Text style={styles.label}>
         {text.messageLabel}
-        {true && ` ${text.isRequiredLabel}`}
+        {` ${text.isRequiredLabel}`}
       </Text>
       <TextInput
         style={[styles.input, styles.textArea]}
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index bfc958a4cf..637f9c4274 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,10 +1,35 @@
 import type { TextStyle, ViewStyle } from 'react-native';
 
-export interface FeedbackFormProps extends FeedbackTextConfiguration {
+export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration {
   closeScreen: () => void;
   styles?: FeedbackFormStyles;
 }
 
+/**
+ * General feedback configuration
+ */
+export interface FeedbackGeneralConfiguration {
+  /**
+   * Should the email field be required?
+   */
+  isEmailRequired?: boolean;
+
+  /**
+   * Should the name field be required?
+   */
+  isNameRequired?: boolean;
+
+  /**
+   * Should the email input field be visible? Note: email will still be collected if set via `Sentry.setUser()`
+   */
+  showEmail?: boolean;
+
+  /**
+   * Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
+   */
+  showName?: boolean;
+}
+
 /**
  * All of the different text labels that can be customized
  */
diff --git a/packages/core/src/js/feedback/constants.ts b/packages/core/src/js/feedback/constants.ts
index 19df28885b..5272c7a86c 100644
--- a/packages/core/src/js/feedback/constants.ts
+++ b/packages/core/src/js/feedback/constants.ts
@@ -15,6 +15,12 @@ const FORM_ERROR = 'Please fill out all required fields.';
 const EMAIL_ERROR = 'Please enter a valid email address.';
 
 export const defaultConfiguration: Partial<FeedbackFormProps> = {
+  // FeedbackGeneralConfiguration
+  isEmailRequired: false,
+  isNameRequired: false,
+  showEmail: true,
+  showName: true,
+
   // FeedbackTextConfiguration
   cancelButtonLabel: CANCEL_BUTTON_LABEL,
   emailLabel: EMAIL_LABEL,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index b930c0afc0..8b309418fe 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -40,9 +40,9 @@ describe('FeedbackForm', () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     expect(getByText(defaultProps.formTitle)).toBeTruthy();
-    expect(getByText(`${defaultProps.nameLabel  } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByText(defaultProps.nameLabel)).toBeTruthy();
     expect(getByPlaceholderText(defaultProps.namePlaceholder)).toBeTruthy();
-    expect(getByText(`${defaultProps.emailLabel } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
+    expect(getByText(defaultProps.emailLabel)).toBeTruthy();
     expect(getByPlaceholderText(defaultProps.emailPlaceholder)).toBeTruthy();
     expect(getByText(`${defaultProps.messageLabel } ${  defaultProps.isRequiredLabel}`)).toBeTruthy();
     expect(getByPlaceholderText(defaultProps.messagePlaceholder)).toBeTruthy();
@@ -60,8 +60,9 @@ describe('FeedbackForm', () => {
     });
   });
 
-  it('shows an error message if the email is not valid', async () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
+  it('shows an error message if the email is not valid and the email is required', async () => {
+    const withEmailProps = {...defaultProps, ...{isEmailRequired: true}};
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...withEmailProps} />);
 
     fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
     fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'not-an-email');

From f0e1befe7700d8673883304f84a0f7c936c7b77c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 20:22:29 +0200
Subject: [PATCH 36/73] Use Sentry user email and name when set

---
 packages/core/src/js/feedback/FeedbackForm.tsx   |  8 +++++---
 .../core/src/js/feedback/FeedbackForm.types.ts   |  9 +++++++++
 .../js/feedback/{constants.ts => defaults.ts}    |  6 ++++++
 .../core/test/feedback/FeedbackForm.test.tsx     | 16 ++++++++++++++++
 4 files changed, 36 insertions(+), 3 deletions(-)
 rename packages/core/src/js/feedback/{constants.ts => defaults.ts} (87%)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 2f9fde5501..20dc8d37b4 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
-import { defaultConfiguration } from './constants';
+import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
 
@@ -15,9 +15,11 @@ import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackG
 export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
   public constructor(props: FeedbackFormProps) {
     super(props);
+
+    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
     this.state = {
-      name: '',
-      email: '',
+      name: config.useSentryUser.name,
+      email: config.useSentryUser.email,
       description: '',
     };
   }
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 637f9c4274..146cf3f534 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -28,6 +28,15 @@ export interface FeedbackGeneralConfiguration {
    * Should the name input field be visible? Note: name will still be collected if set via `Sentry.setUser()`
    */
   showName?: boolean;
+
+  /**
+   * Fill in email/name input fields with Sentry user context if it exists.
+   * The value of the email/name keys represent the properties of your user context.
+   */
+  useSentryUser?: {
+    email: string;
+    name: string;
+  };
 }
 
 /**
diff --git a/packages/core/src/js/feedback/constants.ts b/packages/core/src/js/feedback/defaults.ts
similarity index 87%
rename from packages/core/src/js/feedback/constants.ts
rename to packages/core/src/js/feedback/defaults.ts
index 5272c7a86c..ae210212d6 100644
--- a/packages/core/src/js/feedback/constants.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -1,3 +1,5 @@
+import { getCurrentScope } from '@sentry/core';
+
 import type { FeedbackFormProps } from './FeedbackForm.types';
 
 const FORM_TITLE = 'Report a Bug';
@@ -20,6 +22,10 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   isNameRequired: false,
   showEmail: true,
   showName: true,
+  useSentryUser: {
+    email: getCurrentScope().getUser().email || '',
+    name: getCurrentScope().getUser().name || '',
+  },
 
   // FeedbackTextConfiguration
   cancelButtonLabel: CANCEL_BUTTON_LABEL,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 8b309418fe..10b1f41c37 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -12,6 +12,12 @@ jest.spyOn(Alert, 'alert');
 
 jest.mock('@sentry/core', () => ({
   captureFeedback: jest.fn(),
+  getCurrentScope: jest.fn(() => ({
+    getUser: jest.fn(() => ({
+      email: 'test@example.com',
+      name: 'Test User',
+    })),
+  })),
 }));
 
 const defaultProps: FeedbackFormProps = {
@@ -50,6 +56,16 @@ describe('FeedbackForm', () => {
     expect(getByText(defaultProps.cancelButtonLabel)).toBeTruthy();
   });
 
+  it('name and email are prefilled when sentry user is set', () => {
+    const { getByPlaceholderText } = render(<FeedbackForm {...defaultProps} />);
+
+    const nameInput = getByPlaceholderText(defaultProps.namePlaceholder);
+    const emailInput = getByPlaceholderText(defaultProps.emailPlaceholder);
+
+    expect(nameInput.props.value).toBe('Test User');
+    expect(emailInput.props.value).toBe('test@example.com');
+  });
+
   it('shows an error message if required fields are empty', async () => {
     const { getByText } = render(<FeedbackForm {...defaultProps} />);
 

From b9235f2b07cc3bf0cadf9046e0373a15f5594c8b Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 10 Dec 2024 20:41:07 +0200
Subject: [PATCH 37/73] Simplifies email validation

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 20dc8d37b4..84b9f3c9cc 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -39,7 +39,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       return;
     }
 
-    if (config.isEmailRequired && !this._isValidEmail(trimmedEmail)) {
+    if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
@@ -115,7 +115,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
   }
 
   private _isValidEmail = (email: string): boolean => {
-    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
     return emailRegex.test(email);
   };
 }

From 39a67bd9ee34ef95abcb247021e32526e01821ff Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 11:34:21 +0200
Subject: [PATCH 38/73] Show success alert message

---
 packages/core/src/js/feedback/FeedbackForm.tsx    |  1 +
 .../core/src/js/feedback/FeedbackForm.types.ts    |  5 +++++
 packages/core/src/js/feedback/defaults.ts         |  2 ++
 packages/core/test/feedback/FeedbackForm.test.tsx | 15 +++++++++++++++
 4 files changed, 23 insertions(+)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 84b9f3c9cc..d99d1da13d 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -52,6 +52,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     closeScreen();
     captureFeedback(userFeedback);
+    Alert.alert(text.successMessageText);
   };
 
   /**
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 146cf3f534..8d2d076a4f 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -83,6 +83,11 @@ export interface FeedbackTextConfiguration {
    */
   nameLabel?: string;
 
+  /**
+   * Message after feedback was sent successfully
+   */
+  successMessageText?: string;
+
   /**
    * Placeholder text for Feedback name input
    */
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index ae210212d6..91d9a32d12 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -15,6 +15,7 @@ const CANCEL_BUTTON_LABEL = 'Cancel';
 const ERROR_TITLE = 'Error';
 const FORM_ERROR = 'Please fill out all required fields.';
 const EMAIL_ERROR = 'Please enter a valid email address.';
+const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
 
 export const defaultConfiguration: Partial<FeedbackFormProps> = {
   // FeedbackGeneralConfiguration
@@ -41,4 +42,5 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   errorTitle: ERROR_TITLE,
   formError: FORM_ERROR,
   emailError: EMAIL_ERROR,
+  successMessageText: SUCCESS_MESSAGE_TEXT,
 };
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 10b1f41c37..7809959000 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -35,6 +35,7 @@ const defaultProps: FeedbackFormProps = {
   errorTitle: 'Error',
   formError: 'Please fill out all required fields.',
   emailError: 'The email address is not valid.',
+  successMessageText: 'Feedback success',
 };
 
 describe('FeedbackForm', () => {
@@ -109,6 +110,20 @@ describe('FeedbackForm', () => {
     });
   });
 
+  it('shows success message when the form is submitted successfully', async () => {
+    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
+
+    fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.emailPlaceholder), 'john.doe@example.com');
+    fireEvent.changeText(getByPlaceholderText(defaultProps.messagePlaceholder), 'This is a feedback message.');
+
+    fireEvent.press(getByText(defaultProps.submitButtonLabel));
+
+    await waitFor(() => {
+      expect(Alert.alert).toHaveBeenCalledWith(defaultProps.successMessageText);
+    });
+  });
+
   it('calls closeScreen when the form is submitted successfully', async () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 

From 501a134fea6f6188abf4672e2aa8a67e640b05b1 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 12:26:08 +0200
Subject: [PATCH 39/73] Aligns naming with JS and unmounts the form by default

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 19 +++++++++++++++----
 .../src/js/feedback/FeedbackForm.types.ts     | 14 ++++++++++++--
 packages/core/src/js/feedback/defaults.ts     |  5 +++++
 .../core/test/feedback/FeedbackForm.test.tsx  | 12 ++++++------
 samples/react-native/src/App.tsx              |  2 +-
 5 files changed, 39 insertions(+), 13 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index d99d1da13d..bea617b3d9 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -18,6 +18,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
     this.state = {
+      isVisible: true,
       name: config.useSentryUser.name,
       email: config.useSentryUser.email,
       description: '',
@@ -26,7 +27,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
-    const { closeScreen } = this.props;
+    const { onFormClose } = { ...defaultConfiguration, ...this.props };
     const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
     const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
 
@@ -50,7 +51,9 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       email: trimmedEmail,
     };
 
-    closeScreen();
+    onFormClose();
+    this.setState({ isVisible: false });
+
     captureFeedback(userFeedback);
     Alert.alert(text.successMessageText);
   };
@@ -59,11 +62,19 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
    * Renders the feedback form screen.
    */
   public render(): React.ReactNode {
-    const { closeScreen } = this.props;
     const { name, email, description } = this.state;
+    const { onFormClose } = { ...defaultConfiguration, ...this.props };
     const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
     const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
     const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
+    const onCancel = (): void => {
+      onFormClose();
+      this.setState({ isVisible: false });
+    }
+
+    if (!this.state.isVisible) {
+      return null;
+    }
 
     return (
     <View style={styles.container}>
@@ -108,7 +119,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
         <Text style={styles.submitText}>{text.submitButtonLabel}</Text>
       </TouchableOpacity>
 
-      <TouchableOpacity style={styles.cancelButton} onPress={closeScreen}>
+      <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
         <Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
       </TouchableOpacity>
     </View>
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 8d2d076a4f..aac7a1e1be 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,7 +1,6 @@
 import type { TextStyle, ViewStyle } from 'react-native';
 
-export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration {
-  closeScreen: () => void;
+export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
   styles?: FeedbackFormStyles;
 }
 
@@ -114,6 +113,16 @@ export interface FeedbackTextConfiguration {
   emailError?: string;
 }
 
+/**
+ * The public callbacks available for the feedback integration
+ */
+export interface FeedbackCallbacks {
+  /**
+   * Callback when form is closed and not submitted
+   */
+  onFormClose?: () => void;
+}
+
 export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
@@ -127,6 +136,7 @@ export interface FeedbackFormStyles {
 }
 
 export interface FeedbackFormState {
+  isVisible: boolean;
   name: string;
   email: string;
   description: string;
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index 91d9a32d12..daca7c4f70 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -18,6 +18,11 @@ const EMAIL_ERROR = 'Please enter a valid email address.';
 const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
 
 export const defaultConfiguration: Partial<FeedbackFormProps> = {
+  // FeedbackCallbacks
+  onFormClose: () => {
+    // By default the form is just unmounted
+  },
+
   // FeedbackGeneralConfiguration
   isEmailRequired: false,
   isNameRequired: false,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 7809959000..de7ed90d04 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -6,7 +6,7 @@ import { Alert } from 'react-native';
 import { FeedbackForm } from '../../src/js/feedback/FeedbackForm';
 import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types';
 
-const mockCloseScreen = jest.fn();
+const mockOnFormClose = jest.fn();
 
 jest.spyOn(Alert, 'alert');
 
@@ -21,7 +21,7 @@ jest.mock('@sentry/core', () => ({
 }));
 
 const defaultProps: FeedbackFormProps = {
-  closeScreen: mockCloseScreen,
+  onFormClose: mockOnFormClose,
   formTitle: 'Feedback Form',
   nameLabel: 'Name',
   namePlaceholder: 'Name Placeholder',
@@ -124,7 +124,7 @@ describe('FeedbackForm', () => {
     });
   });
 
-  it('calls closeScreen when the form is submitted successfully', async () => {
+  it('calls onFormClose when the form is submitted successfully', async () => {
     const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.changeText(getByPlaceholderText(defaultProps.namePlaceholder), 'John Doe');
@@ -134,15 +134,15 @@ describe('FeedbackForm', () => {
     fireEvent.press(getByText(defaultProps.submitButtonLabel));
 
     await waitFor(() => {
-      expect(mockCloseScreen).toHaveBeenCalled();
+      expect(mockOnFormClose).toHaveBeenCalled();
     });
   });
 
-  it('calls closeScreen when the cancel button is pressed', () => {
+  it('calls onFormClose when the cancel button is pressed', () => {
     const { getByText } = render(<FeedbackForm {...defaultProps} />);
 
     fireEvent.press(getByText(defaultProps.cancelButtonLabel));
 
-    expect(mockCloseScreen).toHaveBeenCalled();
+    expect(mockOnFormClose).toHaveBeenCalled();
   });
 });
diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx
index 054a1ad6ce..0e3f0285a8 100644
--- a/samples/react-native/src/App.tsx
+++ b/samples/react-native/src/App.tsx
@@ -159,7 +159,7 @@ const ErrorsTabNavigator = Sentry.withProfiler(
               {(props) => (
                 <FeedbackForm
                   {...props}
-                  closeScreen={props.navigation.goBack}
+                  onFormClose={props.navigation.goBack}
                   styles={{
                     submitButton: {
                       backgroundColor: '#6a1b9a',

From 4b290a219787b50e5cc55c220b4fc25a98ee1dd5 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 12:38:04 +0200
Subject: [PATCH 40/73] Use the minimum config without props in the changelog

---
 CHANGELOG.md | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41a552cc1d..4b113ff7e3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,20 +41,7 @@
   ```jsx
   import { FeedbackForm } from "@sentry/react-native";
   ...
-  <FeedbackForm
-    {...props}
-    closeScreen={props.navigation.goBack}
-    styles={{
-      submitButton: {
-        backgroundColor: '#6a1b9a',
-        paddingVertical: 15,
-        borderRadius: 5,
-        alignItems: 'center',
-        marginBottom: 10,
-      },
-    }}
-    text={{namePlaceholder: 'Fullname'}}
-  />
+  <FeedbackForm/>
   ```
   Check [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/) for more configuration options.
 

From 7109deb3a233891f06c7ac89a87d069bb669139d Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 12:47:44 +0200
Subject: [PATCH 41/73] Adds development not for unimplemented function

---
 packages/core/src/js/feedback/defaults.ts | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index daca7c4f70..5b0360ec47 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -1,4 +1,5 @@
 import { getCurrentScope } from '@sentry/core';
+import { Alert } from 'react-native';
 
 import type { FeedbackFormProps } from './FeedbackForm.types';
 
@@ -20,7 +21,12 @@ const SUCCESS_MESSAGE_TEXT = 'Thank you for your report!';
 export const defaultConfiguration: Partial<FeedbackFormProps> = {
   // FeedbackCallbacks
   onFormClose: () => {
-    // By default the form is just unmounted
+    if (__DEV__) {
+      Alert.alert(
+        'Development note',
+        'onFormClose callback is not implemented. By default the form is just unmounted.',
+      );
+    }
   },
 
   // FeedbackGeneralConfiguration

From c80c5cbf99941e952dfeb6676bd30abaef99762c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 13:12:46 +0200
Subject: [PATCH 42/73] Show email and name conditionally

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 38 +++++++++++--------
 1 file changed, 23 insertions(+), 15 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index bea617b3d9..6bd8ca3cff 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -80,28 +80,36 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     <View style={styles.container}>
       <Text style={styles.title}>{text.formTitle}</Text>
 
-      <Text style={styles.label}>
+      {config.showName && (
+      <>
+        <Text style={styles.label}>
           {text.nameLabel}
           {config.isNameRequired && ` ${text.isRequiredLabel}`}
-      </Text>
-      <TextInput
-        style={styles.input}
-        placeholder={text.namePlaceholder}
-        value={name}
-        onChangeText={(value) => this.setState({ name: value })}
+        </Text>
+        <TextInput
+          style={styles.input}
+          placeholder={text.namePlaceholder}
+          value={name}
+          onChangeText={(value) => this.setState({ name: value })}
         />
+      </>
+      )}
 
-      <Text style={styles.label}>
+      {config.showEmail && (
+      <>
+        <Text style={styles.label}>
           {text.emailLabel}
           {config.isEmailRequired && ` ${text.isRequiredLabel}`}
-      </Text>
-      <TextInput
-        style={styles.input}
-        placeholder={text.emailPlaceholder}
-        keyboardType={'email-address' as KeyboardTypeOptions}
-        value={email}
-        onChangeText={(value) => this.setState({ email: value })}
+        </Text>
+        <TextInput
+          style={styles.input}
+          placeholder={text.emailPlaceholder}
+          keyboardType={'email-address' as KeyboardTypeOptions}
+          value={email}
+          onChangeText={(value) => this.setState({ email: value })}
         />
+      </>
+      )}
 
       <Text style={styles.label}>
         {text.messageLabel}

From 8c5675351db5dce4f3b0981d9005e748fc66da89 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 14:14:49 +0200
Subject: [PATCH 43/73] Adds sentry branding (png logo)

---
 .../src/js/feedback/FeedbackForm.styles.ts     | 11 ++++++++++-
 packages/core/src/js/feedback/FeedbackForm.tsx |  9 +++++++--
 .../core/src/js/feedback/FeedbackForm.types.ts | 18 +++++++++++++++++-
 packages/core/src/js/feedback/defaults.ts      |  1 +
 .../core/test/feedback/FeedbackForm.test.tsx   |  1 +
 5 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index d54fb9cd83..c3bb293051 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -10,7 +10,8 @@ const defaultStyles: FeedbackFormStyles = {
     fontSize: 24,
     fontWeight: 'bold',
     marginBottom: 20,
-    textAlign: 'center',
+    textAlign: 'left',
+    flex: 1,
   },
   label: {
     marginBottom: 4,
@@ -49,6 +50,14 @@ const defaultStyles: FeedbackFormStyles = {
     color: '#6a1b9a',
     fontSize: 16,
   },
+  titleContainer: {
+    flexDirection: 'row',
+    width: '100%',
+  },
+  sentryLogo: {
+    width: 40,
+    height: 40,
+  },
 };
 
 export default defaultStyles;
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 6bd8ca3cff..1259c19e6c 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -2,7 +2,7 @@ import { captureFeedback } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
-import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
+import { Alert, Image, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
 import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
@@ -78,7 +78,12 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     return (
     <View style={styles.container}>
-      <Text style={styles.title}>{text.formTitle}</Text>
+      <View style={styles.titleContainer}>
+          <Text style={styles.title}>{text.formTitle}</Text>
+          {config.showBranding && (
+            <Image source={require('../../../assets/sentrylogo.png')} style={styles.sentryLogo} />
+          )}
+      </View>
 
       {config.showName && (
       <>
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index aac7a1e1be..7a900f3d49 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,5 +1,8 @@
-import type { TextStyle, ViewStyle } from 'react-native';
+import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
 
+/**
+ * The props for the feedback form
+ */
 export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
   styles?: FeedbackFormStyles;
 }
@@ -8,6 +11,11 @@ export interface FeedbackFormProps extends FeedbackGeneralConfiguration, Feedbac
  * General feedback configuration
  */
 export interface FeedbackGeneralConfiguration {
+  /**
+   * Show the Sentry branding
+   */
+  showBranding?: boolean;
+
   /**
    * Should the email field be required?
    */
@@ -123,6 +131,9 @@ export interface FeedbackCallbacks {
   onFormClose?: () => void;
 }
 
+/**
+ * The styles for the feedback form
+ */
 export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
@@ -133,8 +144,13 @@ export interface FeedbackFormStyles {
   submitText?: TextStyle;
   cancelButton?: ViewStyle;
   cancelText?: TextStyle;
+  titleContainer?: ViewStyle;
+  sentryLogo?: ImageStyle;
 }
 
+/**
+ * The state of the feedback form
+ */
 export interface FeedbackFormState {
   isVisible: boolean;
   name: string;
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index 5b0360ec47..94a5036512 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -30,6 +30,7 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   },
 
   // FeedbackGeneralConfiguration
+  showBranding: true,
   isEmailRequired: false,
   isNameRequired: false,
   showEmail: true,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index de7ed90d04..a088216fcb 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -36,6 +36,7 @@ const defaultProps: FeedbackFormProps = {
   formError: 'Please fill out all required fields.',
   emailError: 'The email address is not valid.',
   successMessageText: 'Feedback success',
+  showBranding: false,
 };
 
 describe('FeedbackForm', () => {

From d6e9229430c70808d2f30279cc0f63e7daab0a82 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 14:20:57 +0200
Subject: [PATCH 44/73] Adds sentry logo resource

---
 packages/core/assets/sentrylogo.png | Bin 0 -> 3476 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 packages/core/assets/sentrylogo.png

diff --git a/packages/core/assets/sentrylogo.png b/packages/core/assets/sentrylogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..610c3e0ff0ae9cf4e8075bd17570270458c0c2a4
GIT binary patch
literal 3476
zcmcha`8(9#`^R7Nni<Aenq-MAFI$DYF(c(2HDeuF-qw-9NR2HjTe1`{OG3qvgd~Q>
zI>^42En8xwY)Mf<NQvRyqCWGzuJ51lxjsLf^SG}2oO7RZ-S_$7`Ao90HWS85V*vmV
zHYXd~@z~EBO&C7j%!(*<=K&FHXJ!ag4$FMvZL&5uCOL%BKHs+rl{q3BUmhYiXu(-E
zZRzR1n-(TD*jVl5?dSVl-#*HuZ0SsGtMu;g$b1}idG5gOY<DkkMtRZvPZ2EERN8Uq
z{Qr>+1GXXL7$h2%*ky#p;Cf)OSuq>*T*%7`k{Aw(J4)@F=P~y}&f!E5q~pw1vT{K%
z{-sqdpDhs<dha89+nxvqwD^ugSPX+C!#*3t%VK?GNZJ^Iz$X@7vb07Zx<0X-;|@YZ
zzQDSa0ab{2bVkPyheq{7DdXCDXcVUSo|iBa2Be<8_<Mgb4E(n*U%;=tJMn9K2G5b7
z=*jerXmk(BV`&cR|GBu+UKRcO+kmH$2B;1ZPY<l_(bf8^;HYbOo;)hqEiwd+h*k5F
z`BT*>9~IXTW=laO*+zx605K{b4rI;#`{eWMh@JJ%8<B6ZL&m%!B~lX}$p!dN3yQ`m
zb(DfAv71uAU#6aj&dL0^j$h;c!w6FiZ%Y9L#`VCIq4^_&-+phmN9~ASi7&V-33aah
z`Skg^$~i&Dw1b|1oSk6QIT>ezyrJ=pi~0P;<}m8nK4*h1ukx|vU?w0inprBxd5{+;
z8BtFqS?4;C3^P1uC!G*0zZgcsEhTDX_`@7Wzw(XJ$YBQ)P^J|7UQ_M%PJ7hPUuw4O
zcN0JU=>JJLRnm%KQl?HhGVu44%{nDF;+zg@PA1=)=y2k-urHs8Y$?((bYYuwo}|c)
zsmwT9a+V)Su_%LMCST8#LDlq$1NmdfflVKA)ly6AzT-Q1@M&j{mdwgO0<`9=cX$)W
zr=8~WqF}t(g%cXg`W79*Yc{;lqlkt28;N{tElPF<TM|sR#Axr-ouiA`GRU;u%VC$>
z<SlEwr6k0+KD#{k6?KEMyPnF&U*7Y+CvGl_HSpo~1-HrY2igQj(%vzauOXp%@hWi5
z-Z4I;Mh$a7Mmj(9#&-=tTcSakWHIGktpf@3$CLKl;KV);cMc}WnqUIMmB!^}^vF2M
zZjSD*4EFRKhzfBN95doP$$u%-jmAK7R0BL-_nbZ51X;A|RRXAnh)XB#mfUb*F&(WW
zw+xF{#LHa|>R<?EeO?CGsxor(J4X_>&Jd|p8{xmO(nXskpGgE%aqN%F1%i<=NMR#X
zzwCO}@_JKI_jaXCJJZpaJ%05Xx<AHLM`k)_fUy)w-A2mEDiB4)j0oR`9*UAk1r%{T
zQ&afO$n}BV>vjhW!MOWA`*J${QY9hV%M8Dj{b)sG<CEe`1~?SiBiYd9ZlNdPc!|BZ
zYRKw+4-ob0g>c5~xh{t^p#V~I`7TK)UN9mCRc|l3HPHzXF~kN{AAmNa)|8$vA5@my
zwwVrS12xuMSQ#H&$fgu&n3k6FT3}t{LP`>{<kkCtAdc4RL|`*21WoIX`iLjhblWc1
zJw%b=R{_0Gsue)dSFHYI2`GFr9aJV${SL4Hp0#$258nI=SH%3{a@>yxev{?Y5Jl5E
zZ6g)^6G!xdh6)f<px4ih!QJ`!q~^<^#xRpjxmESqj8DI1YZ7kTscyp^S|SLr9#5@)
zONf8e(>h}mSGVx52SV@pDk5JiMhcx&M%D{50`1;xp<s}Ik)Xo+!GirA((5%8ITqIx
z%Q*HrvhnCjs0Pt4SNX}xo&uxC)tc|Zj}k{Z!ZJsjb94{`A7xjq-`qT9(u+h1CHGtC
z3nn?`GY8H*Rl|!xK15fGlDDKT;(K3YTYIpI#>LR(n=*np3vSb9s(rqe<bs*K+S(V#
z&gFRhh7~P1%puijlp<vJ4|zoGy>n-Ez>vHF3Q`C-+P+qO=c&>?LS*FXJ^8hNQ_ceK
zr>Jf(Ce;h^(8TL$KX-k4uy?K;ZvWC=$uP8#XO(^V^#&?!8#4V;p|{Ad+!V1Y`w=Z5
zQ(}F>r>BnU{U>j{6cJOqu75(G?^n$gIw6B!*)l(3b@0lwFZG(`mp9v9f?cSoOyTV2
zw|C-eIt#|mY#D4^+o^-K#y)%-guhB3JE@OXuWdK0W@_a1ITxxUT7Rn|Jirx-=1@hw
zUbj-fIdpl{gYnyv905#(cKgemSMf!Z!MMCBC8=uX<3Vpf0VG+M9}WYk<=PK7FC#ZC
zX`RWGDmx7&<m<@K8wilHYd(7vOyXX!Q96~qLFIgsogL>|qcJ(Y**E)8Lkr3lU$uTJ
zetm~k7PG8c_7wK7`d*AcC7jLKuM20L-iey~`K_;!^Tm0wp!1nBY%!fxOBF?wTfJ%v
zL}<Si%<2e_keG{Z$ep@v1^$GMZgaBB`c;9_kHr0_*^GSedf+=K`t`zM{Y3}QgKpb0
zaQ<-ckF`-XykOEpVbY1huO*7l^$fN&gy%~FXON)xtH<dHp?#yVr@kl!RR4W{&tv5M
zg0W?J95bS=U-wR|cYUXw$;fGL|AJOlFTvCiO`oYmXgj9DCu8~8D&5S^b1{LMjtf$0
z#1!f2!%+D_8sk*#rC<^1x^cl{q7`zBXTP}80!&E(br-f_|LYq-k5b~^W0Qbk41K#<
z2->_U*D?-&`04g&d|dk{bGWsxt2>+Q)kvF6lhjD9Js(lvy~>^1))A-6FZp=gf>8pl
zgV$uOFmiXfX;ij7_<l50%Dr)<qn>^lWm=`{H@26pm`Ui}+_&vya2Q&!XxlBS15eJH
zY^@8~vFvKS+vJ~VD_QTtOpEvjxyU5*;r6vH9nU1VB`AQ!+$Z939qVOAfZd+|KjcAb
z6F*75;eGY>Z~}cVacSai@dFE1J1fQOa;PR<J~Z_n{OjM11Dj4N;RX`MNP-OX{Nrd2
zm$CX}tp~LWO#C>uN+B}6HxqQQ843te+`xz9v2_Yk=OtSE*hIPxw?P3+UqFP5{T{k^
znHJp<v?ndFL}f)mDszTkZ%=EVEl$yU^<&pt9jNNKh1T&_+TW5p6<wp}6gmeRh*FU<
zh}bnw9)W51hFa@Oyx^s5X0w-^bqtKy_8{n|_b5?wEe!}nUkOH}VJ#xW%0an=TBW!h
zGX3D4efLgF|J6a};v}5$8_F;`>_J5~BtxCu^sxCuwlJg{rVks(j?&SjntUhu8*2l6
zl6GwT)@kUF0C_%F!+mlZRa6(@pBGf3xFj2c-ZNBb`Oq+yfW>X*R^+KvQ+svzF*ySU
z(Zj$cwSBHH5I1w<*mf$U>$$aKYNKM1mdt$dFzw=Z{{RKVb7g>jbxTMEhC=nL7GDWx
zOgiCN8bq~vN@RgeG2Rh16(@XpUK{rPa5C0U_NC#ddOi1blNaD?PA)p^a~Mt|Xfc#X
z*C$7*<zpg{utVz5Cvt-OqL$QN0qOm4T6}j!DiFIoHG1u_01@Zyb+uYdwu3yP<%o5D
z(7Xu2TAK0Cvu*F4>~-QtCB!|SR5`QS$BPadgH1hLTZ6QX3w~x!4e5kUe)6a~|MJL^
z?hWVmP3ACgO4Rl4*%5k{=fn%=GB6g~yY=%X;Y9;H5K6vt21uekcu_C*no>q~hm{>u
zhx9NVLJ)6Z{Nirh{Bw;qpD{Ely!m9p*-IktEm^X}ac>}rFSiu(@f=>0{)_WU7_x+`
zMc#1K&kHaS9<&@P8f7k2|KLhF0!(r!i124X=ds8@bpYSsB5G4tDKWhjjP93JDU_YU
zLYCKRPzrW4HRo4EaICA3j$S+=SsG#~VG0<#*WjjW<7{9tV;|qGE3|WY^4QEvvUttG
zXr3-~syp}ss|Z(+T<LvLBQ;kJWp^tVh<BvdYxW)5`1R=2%V*KOZ0e2rWIkk9m{PC^
z0+V5m<a7aG=lN<`c5VWjX2r&mGduEPW!XKbY(Q^Iq_DKOy&MF&{dvVA)Ej{;p!tIk
zQ!TQkQ4Lxl4Lg{i61{znvmI+6Q@5)``m@Utfk3{ZAg&L{=o|cdeP!dm+gwd95SX^^
zo5nZm!^fy4DiU)6UsMVU&_gPNPZ5lSSsxLG)mxg&Ux+Sb&AB6*gWX@EcYa)R?}_%}
zTKssCMzP8Nr)Gy6LlJ`*uBp+l*R(B;V0uH-S%}X0@g;xsWK~W@Xahy{g6{!hP2tI(
zmmb2vr)O?>T^9r*o!zvxauR_Uz0*qfZva7Mm%z^xcR;X6{qn%1KN>}Dc>lUeokyp&
z^hZ-X1K{6N@6J<?jLJ7EXKTfA&{VQtdnHdAyER+%dhsN7jrc^A8_xl>1l_$kL?Oa-
v^~#+VJ0cwN@h_=+JTEdpas6LDVD_Sl@H65iiOK6cn+2GgSQ}Rwo{s$wBV{oh

literal 0
HcmV?d00001


From 529247542a10e501e18b76e53e2933c7772d2d06 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 15:37:23 +0200
Subject: [PATCH 45/73] Add assets in module exports

---
 packages/core/react-native.config.js | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/packages/core/react-native.config.js b/packages/core/react-native.config.js
index d307073e1a..fe07b4e15a 100644
--- a/packages/core/react-native.config.js
+++ b/packages/core/react-native.config.js
@@ -7,5 +7,6 @@ module.exports = {
         packageImportPath: 'import io.sentry.react.RNSentryPackage;'
       }
     }
-  }
+  },
+  assets: ['./assets/'],
 };

From efd809f6f03a92b659653faeaad6516d5d1a18fc Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 16:36:21 +0200
Subject: [PATCH 46/73] Revert "Add assets in module exports"

This reverts commit 529247542a10e501e18b76e53e2933c7772d2d06.
---
 packages/core/react-native.config.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/core/react-native.config.js b/packages/core/react-native.config.js
index fe07b4e15a..d307073e1a 100644
--- a/packages/core/react-native.config.js
+++ b/packages/core/react-native.config.js
@@ -7,6 +7,5 @@ module.exports = {
         packageImportPath: 'import io.sentry.react.RNSentryPackage;'
       }
     }
-  },
-  assets: ['./assets/'],
+  }
 };

From bc7ae659b1f825ada1c5e3ec2aea8ad7a6e7c233 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 16:36:35 +0200
Subject: [PATCH 47/73] Revert "Adds sentry logo resource"

This reverts commit d6e9229430c70808d2f30279cc0f63e7daab0a82.
---
 packages/core/assets/sentrylogo.png | Bin 3476 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 packages/core/assets/sentrylogo.png

diff --git a/packages/core/assets/sentrylogo.png b/packages/core/assets/sentrylogo.png
deleted file mode 100644
index 610c3e0ff0ae9cf4e8075bd17570270458c0c2a4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3476
zcmcha`8(9#`^R7Nni<Aenq-MAFI$DYF(c(2HDeuF-qw-9NR2HjTe1`{OG3qvgd~Q>
zI>^42En8xwY)Mf<NQvRyqCWGzuJ51lxjsLf^SG}2oO7RZ-S_$7`Ao90HWS85V*vmV
zHYXd~@z~EBO&C7j%!(*<=K&FHXJ!ag4$FMvZL&5uCOL%BKHs+rl{q3BUmhYiXu(-E
zZRzR1n-(TD*jVl5?dSVl-#*HuZ0SsGtMu;g$b1}idG5gOY<DkkMtRZvPZ2EERN8Uq
z{Qr>+1GXXL7$h2%*ky#p;Cf)OSuq>*T*%7`k{Aw(J4)@F=P~y}&f!E5q~pw1vT{K%
z{-sqdpDhs<dha89+nxvqwD^ugSPX+C!#*3t%VK?GNZJ^Iz$X@7vb07Zx<0X-;|@YZ
zzQDSa0ab{2bVkPyheq{7DdXCDXcVUSo|iBa2Be<8_<Mgb4E(n*U%;=tJMn9K2G5b7
z=*jerXmk(BV`&cR|GBu+UKRcO+kmH$2B;1ZPY<l_(bf8^;HYbOo;)hqEiwd+h*k5F
z`BT*>9~IXTW=laO*+zx605K{b4rI;#`{eWMh@JJ%8<B6ZL&m%!B~lX}$p!dN3yQ`m
zb(DfAv71uAU#6aj&dL0^j$h;c!w6FiZ%Y9L#`VCIq4^_&-+phmN9~ASi7&V-33aah
z`Skg^$~i&Dw1b|1oSk6QIT>ezyrJ=pi~0P;<}m8nK4*h1ukx|vU?w0inprBxd5{+;
z8BtFqS?4;C3^P1uC!G*0zZgcsEhTDX_`@7Wzw(XJ$YBQ)P^J|7UQ_M%PJ7hPUuw4O
zcN0JU=>JJLRnm%KQl?HhGVu44%{nDF;+zg@PA1=)=y2k-urHs8Y$?((bYYuwo}|c)
zsmwT9a+V)Su_%LMCST8#LDlq$1NmdfflVKA)ly6AzT-Q1@M&j{mdwgO0<`9=cX$)W
zr=8~WqF}t(g%cXg`W79*Yc{;lqlkt28;N{tElPF<TM|sR#Axr-ouiA`GRU;u%VC$>
z<SlEwr6k0+KD#{k6?KEMyPnF&U*7Y+CvGl_HSpo~1-HrY2igQj(%vzauOXp%@hWi5
z-Z4I;Mh$a7Mmj(9#&-=tTcSakWHIGktpf@3$CLKl;KV);cMc}WnqUIMmB!^}^vF2M
zZjSD*4EFRKhzfBN95doP$$u%-jmAK7R0BL-_nbZ51X;A|RRXAnh)XB#mfUb*F&(WW
zw+xF{#LHa|>R<?EeO?CGsxor(J4X_>&Jd|p8{xmO(nXskpGgE%aqN%F1%i<=NMR#X
zzwCO}@_JKI_jaXCJJZpaJ%05Xx<AHLM`k)_fUy)w-A2mEDiB4)j0oR`9*UAk1r%{T
zQ&afO$n}BV>vjhW!MOWA`*J${QY9hV%M8Dj{b)sG<CEe`1~?SiBiYd9ZlNdPc!|BZ
zYRKw+4-ob0g>c5~xh{t^p#V~I`7TK)UN9mCRc|l3HPHzXF~kN{AAmNa)|8$vA5@my
zwwVrS12xuMSQ#H&$fgu&n3k6FT3}t{LP`>{<kkCtAdc4RL|`*21WoIX`iLjhblWc1
zJw%b=R{_0Gsue)dSFHYI2`GFr9aJV${SL4Hp0#$258nI=SH%3{a@>yxev{?Y5Jl5E
zZ6g)^6G!xdh6)f<px4ih!QJ`!q~^<^#xRpjxmESqj8DI1YZ7kTscyp^S|SLr9#5@)
zONf8e(>h}mSGVx52SV@pDk5JiMhcx&M%D{50`1;xp<s}Ik)Xo+!GirA((5%8ITqIx
z%Q*HrvhnCjs0Pt4SNX}xo&uxC)tc|Zj}k{Z!ZJsjb94{`A7xjq-`qT9(u+h1CHGtC
z3nn?`GY8H*Rl|!xK15fGlDDKT;(K3YTYIpI#>LR(n=*np3vSb9s(rqe<bs*K+S(V#
z&gFRhh7~P1%puijlp<vJ4|zoGy>n-Ez>vHF3Q`C-+P+qO=c&>?LS*FXJ^8hNQ_ceK
zr>Jf(Ce;h^(8TL$KX-k4uy?K;ZvWC=$uP8#XO(^V^#&?!8#4V;p|{Ad+!V1Y`w=Z5
zQ(}F>r>BnU{U>j{6cJOqu75(G?^n$gIw6B!*)l(3b@0lwFZG(`mp9v9f?cSoOyTV2
zw|C-eIt#|mY#D4^+o^-K#y)%-guhB3JE@OXuWdK0W@_a1ITxxUT7Rn|Jirx-=1@hw
zUbj-fIdpl{gYnyv905#(cKgemSMf!Z!MMCBC8=uX<3Vpf0VG+M9}WYk<=PK7FC#ZC
zX`RWGDmx7&<m<@K8wilHYd(7vOyXX!Q96~qLFIgsogL>|qcJ(Y**E)8Lkr3lU$uTJ
zetm~k7PG8c_7wK7`d*AcC7jLKuM20L-iey~`K_;!^Tm0wp!1nBY%!fxOBF?wTfJ%v
zL}<Si%<2e_keG{Z$ep@v1^$GMZgaBB`c;9_kHr0_*^GSedf+=K`t`zM{Y3}QgKpb0
zaQ<-ckF`-XykOEpVbY1huO*7l^$fN&gy%~FXON)xtH<dHp?#yVr@kl!RR4W{&tv5M
zg0W?J95bS=U-wR|cYUXw$;fGL|AJOlFTvCiO`oYmXgj9DCu8~8D&5S^b1{LMjtf$0
z#1!f2!%+D_8sk*#rC<^1x^cl{q7`zBXTP}80!&E(br-f_|LYq-k5b~^W0Qbk41K#<
z2->_U*D?-&`04g&d|dk{bGWsxt2>+Q)kvF6lhjD9Js(lvy~>^1))A-6FZp=gf>8pl
zgV$uOFmiXfX;ij7_<l50%Dr)<qn>^lWm=`{H@26pm`Ui}+_&vya2Q&!XxlBS15eJH
zY^@8~vFvKS+vJ~VD_QTtOpEvjxyU5*;r6vH9nU1VB`AQ!+$Z939qVOAfZd+|KjcAb
z6F*75;eGY>Z~}cVacSai@dFE1J1fQOa;PR<J~Z_n{OjM11Dj4N;RX`MNP-OX{Nrd2
zm$CX}tp~LWO#C>uN+B}6HxqQQ843te+`xz9v2_Yk=OtSE*hIPxw?P3+UqFP5{T{k^
znHJp<v?ndFL}f)mDszTkZ%=EVEl$yU^<&pt9jNNKh1T&_+TW5p6<wp}6gmeRh*FU<
zh}bnw9)W51hFa@Oyx^s5X0w-^bqtKy_8{n|_b5?wEe!}nUkOH}VJ#xW%0an=TBW!h
zGX3D4efLgF|J6a};v}5$8_F;`>_J5~BtxCu^sxCuwlJg{rVks(j?&SjntUhu8*2l6
zl6GwT)@kUF0C_%F!+mlZRa6(@pBGf3xFj2c-ZNBb`Oq+yfW>X*R^+KvQ+svzF*ySU
z(Zj$cwSBHH5I1w<*mf$U>$$aKYNKM1mdt$dFzw=Z{{RKVb7g>jbxTMEhC=nL7GDWx
zOgiCN8bq~vN@RgeG2Rh16(@XpUK{rPa5C0U_NC#ddOi1blNaD?PA)p^a~Mt|Xfc#X
z*C$7*<zpg{utVz5Cvt-OqL$QN0qOm4T6}j!DiFIoHG1u_01@Zyb+uYdwu3yP<%o5D
z(7Xu2TAK0Cvu*F4>~-QtCB!|SR5`QS$BPadgH1hLTZ6QX3w~x!4e5kUe)6a~|MJL^
z?hWVmP3ACgO4Rl4*%5k{=fn%=GB6g~yY=%X;Y9;H5K6vt21uekcu_C*no>q~hm{>u
zhx9NVLJ)6Z{Nirh{Bw;qpD{Ely!m9p*-IktEm^X}ac>}rFSiu(@f=>0{)_WU7_x+`
zMc#1K&kHaS9<&@P8f7k2|KLhF0!(r!i124X=ds8@bpYSsB5G4tDKWhjjP93JDU_YU
zLYCKRPzrW4HRo4EaICA3j$S+=SsG#~VG0<#*WjjW<7{9tV;|qGE3|WY^4QEvvUttG
zXr3-~syp}ss|Z(+T<LvLBQ;kJWp^tVh<BvdYxW)5`1R=2%V*KOZ0e2rWIkk9m{PC^
z0+V5m<a7aG=lN<`c5VWjX2r&mGduEPW!XKbY(Q^Iq_DKOy&MF&{dvVA)Ej{;p!tIk
zQ!TQkQ4Lxl4Lg{i61{znvmI+6Q@5)``m@Utfk3{ZAg&L{=o|cdeP!dm+gwd95SX^^
zo5nZm!^fy4DiU)6UsMVU&_gPNPZ5lSSsxLG)mxg&Ux+Sb&AB6*gWX@EcYa)R?}_%}
zTKssCMzP8Nr)Gy6LlJ`*uBp+l*R(B;V0uH-S%}X0@g;xsWK~W@Xahy{g6{!hP2tI(
zmmb2vr)O?>T^9r*o!zvxauR_Uz0*qfZva7Mm%z^xcR;X6{qn%1KN>}Dc>lUeokyp&
z^hZ-X1K{6N@6J<?jLJ7EXKTfA&{VQtdnHdAyER+%dhsN7jrc^A8_xl>1l_$kL?Oa-
v^~#+VJ0cwN@h_=+JTEdpas6LDVD_Sl@H65iiOK6cn+2GgSQ}Rwo{s$wBV{oh


From 79ee5ba69c7e752556089754f96c31e6690b5f32 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 16:36:45 +0200
Subject: [PATCH 48/73] Revert "Adds sentry branding (png logo)"

This reverts commit 8c5675351db5dce4f3b0981d9005e748fc66da89.
---
 .../src/js/feedback/FeedbackForm.styles.ts     | 11 +----------
 packages/core/src/js/feedback/FeedbackForm.tsx |  9 ++-------
 .../core/src/js/feedback/FeedbackForm.types.ts | 18 +-----------------
 packages/core/src/js/feedback/defaults.ts      |  1 -
 .../core/test/feedback/FeedbackForm.test.tsx   |  1 -
 5 files changed, 4 insertions(+), 36 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index c3bb293051..d54fb9cd83 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -10,8 +10,7 @@ const defaultStyles: FeedbackFormStyles = {
     fontSize: 24,
     fontWeight: 'bold',
     marginBottom: 20,
-    textAlign: 'left',
-    flex: 1,
+    textAlign: 'center',
   },
   label: {
     marginBottom: 4,
@@ -50,14 +49,6 @@ const defaultStyles: FeedbackFormStyles = {
     color: '#6a1b9a',
     fontSize: 16,
   },
-  titleContainer: {
-    flexDirection: 'row',
-    width: '100%',
-  },
-  sentryLogo: {
-    width: 40,
-    height: 40,
-  },
 };
 
 export default defaultStyles;
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 1259c19e6c..6bd8ca3cff 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -2,7 +2,7 @@ import { captureFeedback } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
-import { Alert, Image, Text, TextInput, TouchableOpacity, View } from 'react-native';
+import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
 
 import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
@@ -78,12 +78,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
     return (
     <View style={styles.container}>
-      <View style={styles.titleContainer}>
-          <Text style={styles.title}>{text.formTitle}</Text>
-          {config.showBranding && (
-            <Image source={require('../../../assets/sentrylogo.png')} style={styles.sentryLogo} />
-          )}
-      </View>
+      <Text style={styles.title}>{text.formTitle}</Text>
 
       {config.showName && (
       <>
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 7a900f3d49..aac7a1e1be 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,8 +1,5 @@
-import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
+import type { TextStyle, ViewStyle } from 'react-native';
 
-/**
- * The props for the feedback form
- */
 export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
   styles?: FeedbackFormStyles;
 }
@@ -11,11 +8,6 @@ export interface FeedbackFormProps extends FeedbackGeneralConfiguration, Feedbac
  * General feedback configuration
  */
 export interface FeedbackGeneralConfiguration {
-  /**
-   * Show the Sentry branding
-   */
-  showBranding?: boolean;
-
   /**
    * Should the email field be required?
    */
@@ -131,9 +123,6 @@ export interface FeedbackCallbacks {
   onFormClose?: () => void;
 }
 
-/**
- * The styles for the feedback form
- */
 export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
@@ -144,13 +133,8 @@ export interface FeedbackFormStyles {
   submitText?: TextStyle;
   cancelButton?: ViewStyle;
   cancelText?: TextStyle;
-  titleContainer?: ViewStyle;
-  sentryLogo?: ImageStyle;
 }
 
-/**
- * The state of the feedback form
- */
 export interface FeedbackFormState {
   isVisible: boolean;
   name: string;
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index 94a5036512..5b0360ec47 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -30,7 +30,6 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   },
 
   // FeedbackGeneralConfiguration
-  showBranding: true,
   isEmailRequired: false,
   isNameRequired: false,
   showEmail: true,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index a088216fcb..de7ed90d04 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -36,7 +36,6 @@ const defaultProps: FeedbackFormProps = {
   formError: 'Please fill out all required fields.',
   emailError: 'The email address is not valid.',
   successMessageText: 'Feedback success',
-  showBranding: false,
 };
 
 describe('FeedbackForm', () => {

From ba13320d5ade1db589b223277ed3ffa813746f19 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 17:29:40 +0200
Subject: [PATCH 49/73] Add last event id

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 6bd8ca3cff..b79fe7935b 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -1,4 +1,4 @@
-import { captureFeedback } from '@sentry/core';
+import { captureFeedback, lastEventId } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
@@ -45,10 +45,12 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       return;
     }
 
+    const eventId = lastEventId();
     const userFeedback: SendFeedbackParams = {
       message: trimmedDescription,
       name: trimmedName,
       email: trimmedEmail,
+      associatedEventId: eventId,
     };
 
     onFormClose();

From 1f5fb5602d0211e08114fb0b753ef3b40012f169 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Wed, 11 Dec 2024 19:01:45 +0200
Subject: [PATCH 50/73] Mock lastEventId

---
 packages/core/test/feedback/FeedbackForm.test.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index de7ed90d04..272072f413 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -18,6 +18,7 @@ jest.mock('@sentry/core', () => ({
       name: 'Test User',
     })),
   })),
+  lastEventId: jest.fn(),
 }));
 
 const defaultProps: FeedbackFormProps = {

From 9831482ece840f6004f539c5ebfdf9d2f260b1b2 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 11:25:00 +0200
Subject: [PATCH 51/73] Adds beta note in the changelog

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5aa90212d5..3493f9e1df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,7 +35,7 @@
   });
   ```
 
-- Adds feedback form ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+- Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
 
   You can add the form component in your UI like:
   ```jsx

From 4097347e2b44db52c007a693aa9ee0efd004ca40 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 18:27:16 +0200
Subject: [PATCH 52/73] Updates changelog

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3493f9e1df..c799f7a294 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,7 +43,6 @@
   ...
   <FeedbackForm/>
   ```
-  Check [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/) for more configuration options.
 
 - Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))
 

From 30a7b1093eed42d56e410e414d58a1e7d22a4db3 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 19:40:54 +0200
Subject: [PATCH 53/73] Align colors with JS

---
 .../src/js/feedback/FeedbackForm.styles.ts    | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index d54fb9cd83..736540baf1 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -1,43 +1,52 @@
 import type { FeedbackFormStyles } from './FeedbackForm.types';
 
+const PURPLE = 'rgba(88, 74, 192, 1)';
+const FORGROUND_COLOR = '#2b2233';
+const BACKROUND_COLOR = '#fff';
+const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';
+
 const defaultStyles: FeedbackFormStyles = {
   container: {
     flex: 1,
     padding: 20,
-    backgroundColor: '#fff',
+    backgroundColor: BACKROUND_COLOR,
   },
   title: {
     fontSize: 24,
     fontWeight: 'bold',
     marginBottom: 20,
     textAlign: 'center',
+    color: FORGROUND_COLOR,
   },
   label: {
     marginBottom: 4,
     fontSize: 16,
+    color: FORGROUND_COLOR,
   },
   input: {
     height: 50,
-    borderColor: '#ccc',
+    borderColor: BORDER_COLOR,
     borderWidth: 1,
     borderRadius: 5,
     paddingHorizontal: 10,
     marginBottom: 15,
     fontSize: 16,
+    color: FORGROUND_COLOR,
   },
   textArea: {
     height: 100,
     textAlignVertical: 'top',
+    color: FORGROUND_COLOR,
   },
   submitButton: {
-    backgroundColor: '#6a1b9a',
+    backgroundColor: PURPLE,
     paddingVertical: 15,
     borderRadius: 5,
     alignItems: 'center',
     marginBottom: 10,
   },
   submitText: {
-    color: '#fff',
+    color: BACKROUND_COLOR,
     fontSize: 18,
     fontWeight: 'bold',
   },
@@ -46,7 +55,7 @@ const defaultStyles: FeedbackFormStyles = {
     alignItems: 'center',
   },
   cancelText: {
-    color: '#6a1b9a',
+    color: FORGROUND_COLOR,
     fontSize: 16,
   },
 };

From 3eccf25d4cc371b876b46c5c5e713dadb9426401 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 19:42:40 +0200
Subject: [PATCH 54/73] Update CHANGELOG.md

Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c799f7a294..db7131b12d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -46,7 +46,6 @@
 
 - Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))
 
-
 ### Fixes
 
 - Return `lastEventId` export from `@sentry/core` ([#4315](https://github.com/getsentry/sentry-react-native/pull/4315))

From bc96fce56b9aa295ab64e257060360504b9698b2 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 19:42:55 +0200
Subject: [PATCH 55/73] Update CHANGELOG.md

Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
---
 CHANGELOG.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index db7131b12d..fe235e16e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,7 +37,8 @@
 
 - Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
 
-  You can add the form component in your UI like:
+   To collect user feedback from inside your application add the `FeedbackFrom` component.
+
   ```jsx
   import { FeedbackForm } from "@sentry/react-native";
   ...

From 995a9ca6c5052bc46ff4bac189938cae393d37a0 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 19:44:32 +0200
Subject: [PATCH 56/73] Update CHANGELOG.md

Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe235e16e8..071b37d80e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,7 +35,7 @@
   });
   ```
 
-- Adds feedback form (beta) ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+- User Feedback From Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
 
    To collect user feedback from inside your application add the `FeedbackFrom` component.
 

From 3aacaf7faa98e034470f0affd6b829e8975b273c Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 19:46:02 +0200
Subject: [PATCH 57/73] Use regular fonts for both buttons

---
 packages/core/src/js/feedback/FeedbackForm.styles.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index 736540baf1..0ab35584d8 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -48,7 +48,6 @@ const defaultStyles: FeedbackFormStyles = {
   submitText: {
     color: BACKROUND_COLOR,
     fontSize: 18,
-    fontWeight: 'bold',
   },
   cancelButton: {
     paddingVertical: 15,

From 78e412ceaf038f42c98ff523a9fb9cc2a8e2904e Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:03:26 +0200
Subject: [PATCH 58/73] Handle keyboard properly

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 129 ++++++++++--------
 1 file changed, 74 insertions(+), 55 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index b79fe7935b..2269a7619e 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -2,7 +2,18 @@ import { captureFeedback, lastEventId } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
-import { Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
+import {
+  Alert,
+  Keyboard,
+  KeyboardAvoidingView,
+  SafeAreaView,
+  ScrollView,
+  Text,
+  TextInput,
+  TouchableOpacity,
+  TouchableWithoutFeedback,
+  View
+} from 'react-native';
 
 import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
@@ -79,60 +90,68 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     }
 
     return (
-    <View style={styles.container}>
-      <Text style={styles.title}>{text.formTitle}</Text>
-
-      {config.showName && (
-      <>
-        <Text style={styles.label}>
-          {text.nameLabel}
-          {config.isNameRequired && ` ${text.isRequiredLabel}`}
-        </Text>
-        <TextInput
-          style={styles.input}
-          placeholder={text.namePlaceholder}
-          value={name}
-          onChangeText={(value) => this.setState({ name: value })}
-        />
-      </>
-      )}
-
-      {config.showEmail && (
-      <>
-        <Text style={styles.label}>
-          {text.emailLabel}
-          {config.isEmailRequired && ` ${text.isRequiredLabel}`}
-        </Text>
-        <TextInput
-          style={styles.input}
-          placeholder={text.emailPlaceholder}
-          keyboardType={'email-address' as KeyboardTypeOptions}
-          value={email}
-          onChangeText={(value) => this.setState({ email: value })}
-        />
-      </>
-      )}
-
-      <Text style={styles.label}>
-        {text.messageLabel}
-        {` ${text.isRequiredLabel}`}
-      </Text>
-      <TextInput
-        style={[styles.input, styles.textArea]}
-        placeholder={text.messagePlaceholder}
-        value={description}
-        onChangeText={(value) => this.setState({ description: value })}
-        multiline
-      />
-
-      <TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
-        <Text style={styles.submitText}>{text.submitButtonLabel}</Text>
-      </TouchableOpacity>
-
-      <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
-        <Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
-      </TouchableOpacity>
-    </View>
+    <SafeAreaView style={styles.container}>
+      <KeyboardAvoidingView behavior={'padding'} style={styles.container}>
+        <ScrollView>
+          <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
+            <View style={styles.container}>
+              <Text style={styles.title}>{text.formTitle}</Text>
+
+              {config.showName && (
+              <>
+                <Text style={styles.label}>
+                  {text.nameLabel}
+                  {config.isNameRequired && ` ${text.isRequiredLabel}`}
+                </Text>
+                <TextInput
+                  style={styles.input}
+                  placeholder={text.namePlaceholder}
+                  value={name}
+                  onChangeText={(value) => this.setState({ name: value })}
+                />
+              </>
+              )}
+
+              {config.showEmail && (
+              <>
+                <Text style={styles.label}>
+                  {text.emailLabel}
+                  {config.isEmailRequired && ` ${text.isRequiredLabel}`}
+                </Text>
+                <TextInput
+                  style={styles.input}
+                  placeholder={text.emailPlaceholder}
+                  keyboardType={'email-address' as KeyboardTypeOptions}
+                  value={email}
+                  onChangeText={(value) => this.setState({ email: value })}
+                />
+              </>
+              )}
+
+              <Text style={styles.label}>
+                {text.messageLabel}
+                {` ${text.isRequiredLabel}`}
+              </Text>
+              <TextInput
+                style={[styles.input, styles.textArea]}
+                placeholder={text.messagePlaceholder}
+                value={description}
+                onChangeText={(value) => this.setState({ description: value })}
+                multiline
+              />
+
+              <TouchableOpacity style={styles.submitButton} onPress={this.handleFeedbackSubmit}>
+                <Text style={styles.submitText}>{text.submitButtonLabel}</Text>
+              </TouchableOpacity>
+
+              <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
+                <Text style={styles.cancelText}>{text.cancelButtonLabel}</Text>
+              </TouchableOpacity>
+            </View>
+          </TouchableWithoutFeedback>
+        </ScrollView>
+      </KeyboardAvoidingView>
+    </SafeAreaView>
     );
   }
 

From c45a5e65835f7aeed05bbd12a5747294b0c121c8 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:09:15 +0200
Subject: [PATCH 59/73] Adds an option on whether the email should be validated

---
 packages/core/src/js/feedback/FeedbackForm.tsx      | 2 +-
 packages/core/src/js/feedback/FeedbackForm.types.ts | 5 +++++
 packages/core/src/js/feedback/defaults.ts           | 1 +
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 2269a7619e..b482d3d6b6 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -51,7 +51,7 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       return;
     }
 
-    if ((config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
+    if (config.shouldValidateEmail && (config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index aac7a1e1be..84078d8a61 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -13,6 +13,11 @@ export interface FeedbackGeneralConfiguration {
    */
   isEmailRequired?: boolean;
 
+  /**
+   * Should the email field be validated?
+   */
+  shouldValidateEmail?: boolean;
+
   /**
    * Should the name field be required?
    */
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index 5b0360ec47..12f8dad554 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -31,6 +31,7 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
 
   // FeedbackGeneralConfiguration
   isEmailRequired: false,
+  shouldValidateEmail: true,
   isNameRequired: false,
   showEmail: true,
   showName: true,

From 6e39119fcf672de399ec7aa9030fd718914ce277 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:33:40 +0200
Subject: [PATCH 60/73] Merge properties only once

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 23 ++++++++++---------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index b482d3d6b6..2b3cba3449 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -24,34 +24,35 @@ import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackG
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
 export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
+  private _config: FeedbackFormProps;
+
   public constructor(props: FeedbackFormProps) {
     super(props);
 
-    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...props };
+    this._config = { ...defaultConfiguration, ...props };
     this.state = {
       isVisible: true,
-      name: config.useSentryUser.name,
-      email: config.useSentryUser.email,
+      name: this._config.useSentryUser.name,
+      email: this._config.useSentryUser.email,
       description: '',
     };
   }
 
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
-    const { onFormClose } = { ...defaultConfiguration, ...this.props };
-    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
-    const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
+    const { onFormClose } = this._config;
+    const text: FeedbackTextConfiguration = this._config;
 
     const trimmedName = name?.trim();
     const trimmedEmail = email?.trim();
     const trimmedDescription = description?.trim();
 
-    if ((config.isNameRequired && !trimmedName) || (config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
+    if ((this._config.isNameRequired && !trimmedName) || (this._config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
       Alert.alert(text.errorTitle, text.formError);
       return;
     }
 
-    if (config.shouldValidateEmail && (config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
+    if (this._config.shouldValidateEmail && (this._config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
@@ -76,9 +77,9 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
    */
   public render(): React.ReactNode {
     const { name, email, description } = this.state;
-    const { onFormClose } = { ...defaultConfiguration, ...this.props };
-    const config: FeedbackGeneralConfiguration = { ...defaultConfiguration, ...this.props };
-    const text: FeedbackTextConfiguration = { ...defaultConfiguration, ...this.props };
+    const { onFormClose } = this._config;
+    const config: FeedbackGeneralConfiguration = this._config;
+    const text: FeedbackTextConfiguration = this._config;
     const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
     const onCancel = (): void => {
       onFormClose();

From 57d99e9c91ee615cc83b7457b7e5e4996a594caa Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:40:25 +0200
Subject: [PATCH 61/73] Loads current user data on form construction

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 11 +++++++++--
 packages/core/src/js/feedback/defaults.ts      |  5 -----
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 2b3cba3449..5b683823ea 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -1,4 +1,4 @@
-import { captureFeedback, lastEventId } from '@sentry/core';
+import { captureFeedback, getCurrentScope, lastEventId } from '@sentry/core';
 import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
@@ -29,7 +29,14 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
   public constructor(props: FeedbackFormProps) {
     super(props);
 
-    this._config = { ...defaultConfiguration, ...props };
+    const currentUser = {
+      useSentryUser: {
+        email: getCurrentScope().getUser().email || '',
+        name: getCurrentScope().getUser().name || '',
+      }
+    }
+
+    this._config = { ...defaultConfiguration, ...currentUser, ...props };
     this.state = {
       isVisible: true,
       name: this._config.useSentryUser.name,
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index 12f8dad554..ae8a3e9578 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -1,4 +1,3 @@
-import { getCurrentScope } from '@sentry/core';
 import { Alert } from 'react-native';
 
 import type { FeedbackFormProps } from './FeedbackForm.types';
@@ -35,10 +34,6 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   isNameRequired: false,
   showEmail: true,
   showName: true,
-  useSentryUser: {
-    email: getCurrentScope().getUser().email || '',
-    name: getCurrentScope().getUser().name || '',
-  },
 
   // FeedbackTextConfiguration
   cancelButtonLabel: CANCEL_BUTTON_LABEL,

From 6fb8ab4938045ec6bacbe1e29d1557d096c335d5 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:45:49 +0200
Subject: [PATCH 62/73] Remove unneeded extra padding

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 5b683823ea..a93a141b9b 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -98,8 +98,8 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     }
 
     return (
-    <SafeAreaView style={styles.container}>
-      <KeyboardAvoidingView behavior={'padding'} style={styles.container}>
+    <SafeAreaView>
+      <KeyboardAvoidingView behavior={'padding'}>
         <ScrollView>
           <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
             <View style={styles.container}>

From fd2e317fa080e1df30ccbf016711c930956c6de4 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 13 Dec 2024 20:53:13 +0200
Subject: [PATCH 63/73] Fix background color issue

---
 packages/core/src/js/feedback/FeedbackForm.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index a93a141b9b..c978db83e3 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -98,8 +98,8 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     }
 
     return (
-    <SafeAreaView>
-      <KeyboardAvoidingView behavior={'padding'}>
+    <SafeAreaView style={[styles.container, { padding: 0 }]}>
+      <KeyboardAvoidingView behavior={'padding'} style={[styles.container, { padding: 0 }]}>
         <ScrollView>
           <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
             <View style={styles.container}>

From b793937ae7595bd066192ebde44bd4214c762d55 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Mon, 16 Dec 2024 15:57:55 +0200
Subject: [PATCH 64/73] Fixes changelog typo

---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 071b37d80e..d786abe5e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,9 +35,9 @@
   });
   ```
 
-- User Feedback From Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+- User Feedback Form Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
 
-   To collect user feedback from inside your application add the `FeedbackFrom` component.
+   To collect user feedback from inside your application add the `FeedbackForm` component.
 
   ```jsx
   import { FeedbackForm } from "@sentry/react-native";

From 2a8f13f23d560961d59edb5c5e333dbfe8ef43b1 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 17 Dec 2024 16:52:57 +0200
Subject: [PATCH 65/73] Updates styles background color

Co-authored-by: Krystof Woldrich <31292499+krystofwoldrich@users.noreply.github.com>
---
 packages/core/src/js/feedback/FeedbackForm.styles.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index 0ab35584d8..ff92690f28 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -2,7 +2,7 @@ import type { FeedbackFormStyles } from './FeedbackForm.types';
 
 const PURPLE = 'rgba(88, 74, 192, 1)';
 const FORGROUND_COLOR = '#2b2233';
-const BACKROUND_COLOR = '#fff';
+const BACKROUND_COLOR = '#ffffff';
 const BORDER_COLOR = 'rgba(41, 35, 47, 0.13)';
 
 const defaultStyles: FeedbackFormStyles = {

From 9a96e74fe1af8f17f7c7cd5a42029a71678dde6a Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 17 Dec 2024 17:13:32 +0200
Subject: [PATCH 66/73] Use defaultProps

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index c978db83e3..6508c54739 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -1,5 +1,5 @@
+import type { SendFeedbackParams } from '@sentry/core';
 import { captureFeedback, getCurrentScope, lastEventId } from '@sentry/core';
-import type { SendFeedbackParams } from '@sentry/types';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import {
@@ -24,7 +24,7 @@ import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackG
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
 export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
-  private _config: FeedbackFormProps;
+  public static defaultProps: Partial<FeedbackFormProps>;
 
   public constructor(props: FeedbackFormProps) {
     super(props);
@@ -36,30 +36,30 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
       }
     }
 
-    this._config = { ...defaultConfiguration, ...currentUser, ...props };
+    FeedbackForm.defaultProps = { ...defaultConfiguration, ...currentUser, ...props };
     this.state = {
       isVisible: true,
-      name: this._config.useSentryUser.name,
-      email: this._config.useSentryUser.email,
+      name: currentUser.useSentryUser.name,
+      email: currentUser.useSentryUser.email,
       description: '',
     };
   }
 
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
-    const { onFormClose } = this._config;
-    const text: FeedbackTextConfiguration = this._config;
+    const { onFormClose } = FeedbackForm.defaultProps;
+    const text: FeedbackTextConfiguration = FeedbackForm.defaultProps;
 
     const trimmedName = name?.trim();
     const trimmedEmail = email?.trim();
     const trimmedDescription = description?.trim();
 
-    if ((this._config.isNameRequired && !trimmedName) || (this._config.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
+    if ((FeedbackForm.defaultProps.isNameRequired && !trimmedName) || (FeedbackForm.defaultProps.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
       Alert.alert(text.errorTitle, text.formError);
       return;
     }
 
-    if (this._config.shouldValidateEmail && (this._config.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
+    if (FeedbackForm.defaultProps.shouldValidateEmail && (FeedbackForm.defaultProps.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
@@ -84,9 +84,9 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
    */
   public render(): React.ReactNode {
     const { name, email, description } = this.state;
-    const { onFormClose } = this._config;
-    const config: FeedbackGeneralConfiguration = this._config;
-    const text: FeedbackTextConfiguration = this._config;
+    const { onFormClose } = FeedbackForm.defaultProps;
+    const config: FeedbackGeneralConfiguration = FeedbackForm.defaultProps;
+    const text: FeedbackTextConfiguration = FeedbackForm.defaultProps;
     const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
     const onCancel = (): void => {
       onFormClose();

From 2be08a62075a7538776d4f64a14e076694df14b4 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 17 Dec 2024 18:17:25 +0200
Subject: [PATCH 67/73] Correct defaultProps

---
 .../core/src/js/feedback/FeedbackForm.tsx     | 23 ++++++++++---------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 6508c54739..6bd3404024 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -24,19 +24,20 @@ import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackG
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
  */
 export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFormState> {
-  public static defaultProps: Partial<FeedbackFormProps>;
+  public static defaultProps: Partial<FeedbackFormProps> = {
+    ...defaultConfiguration
+  }
 
   public constructor(props: FeedbackFormProps) {
     super(props);
 
     const currentUser = {
       useSentryUser: {
-        email: getCurrentScope().getUser().email || '',
-        name: getCurrentScope().getUser().name || '',
+        email: this.props?.useSentryUser?.email || getCurrentScope()?.getUser()?.email || '',
+        name: this.props?.useSentryUser?.name || getCurrentScope()?.getUser()?.name || '',
       }
     }
 
-    FeedbackForm.defaultProps = { ...defaultConfiguration, ...currentUser, ...props };
     this.state = {
       isVisible: true,
       name: currentUser.useSentryUser.name,
@@ -47,19 +48,19 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
 
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
-    const { onFormClose } = FeedbackForm.defaultProps;
-    const text: FeedbackTextConfiguration = FeedbackForm.defaultProps;
+    const { onFormClose } = this.props;
+    const text: FeedbackTextConfiguration = this.props;
 
     const trimmedName = name?.trim();
     const trimmedEmail = email?.trim();
     const trimmedDescription = description?.trim();
 
-    if ((FeedbackForm.defaultProps.isNameRequired && !trimmedName) || (FeedbackForm.defaultProps.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
+    if ((this.props.isNameRequired && !trimmedName) || (this.props.isEmailRequired && !trimmedEmail) || !trimmedDescription) {
       Alert.alert(text.errorTitle, text.formError);
       return;
     }
 
-    if (FeedbackForm.defaultProps.shouldValidateEmail && (FeedbackForm.defaultProps.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
+    if (this.props.shouldValidateEmail && (this.props.isEmailRequired || trimmedEmail.length > 0) && !this._isValidEmail(trimmedEmail)) {
       Alert.alert(text.errorTitle, text.emailError);
       return;
     }
@@ -84,9 +85,9 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
    */
   public render(): React.ReactNode {
     const { name, email, description } = this.state;
-    const { onFormClose } = FeedbackForm.defaultProps;
-    const config: FeedbackGeneralConfiguration = FeedbackForm.defaultProps;
-    const text: FeedbackTextConfiguration = FeedbackForm.defaultProps;
+    const { onFormClose } = this.props;
+    const config: FeedbackGeneralConfiguration = this.props;
+    const text: FeedbackTextConfiguration = this.props;
     const styles: FeedbackFormStyles = { ...defaultStyles, ...this.props.styles };
     const onCancel = (): void => {
       onFormClose();

From 0588552063ae0dfad37270989d22179c4edc5b36 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Tue, 17 Dec 2024 18:38:42 +0200
Subject: [PATCH 68/73] Adds test to verify when getUser is called

---
 .../core/test/feedback/FeedbackForm.test.tsx  | 20 +++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index 272072f413..bf8d963a57 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -7,16 +7,17 @@ import { FeedbackForm } from '../../src/js/feedback/FeedbackForm';
 import type { FeedbackFormProps } from '../../src/js/feedback/FeedbackForm.types';
 
 const mockOnFormClose = jest.fn();
+const mockGetUser = jest.fn(() => ({
+  email: 'test@example.com',
+  name: 'Test User',
+}));
 
 jest.spyOn(Alert, 'alert');
 
 jest.mock('@sentry/core', () => ({
   captureFeedback: jest.fn(),
   getCurrentScope: jest.fn(() => ({
-    getUser: jest.fn(() => ({
-      email: 'test@example.com',
-      name: 'Test User',
-    })),
+    getUser: mockGetUser,
   })),
   lastEventId: jest.fn(),
 }));
@@ -68,6 +69,17 @@ describe('FeedbackForm', () => {
     expect(emailInput.props.value).toBe('test@example.com');
   });
 
+  it('ensure getUser is called only after the component is rendered', () => {
+    // Ensure getUser is not called before render
+    expect(mockGetUser).not.toHaveBeenCalled();
+
+    // Render the component
+    render(<FeedbackForm />);
+
+    // After rendering, check that getUser was called twice (email and name)
+    expect(mockGetUser).toHaveBeenCalledTimes(2);
+  });
+
   it('shows an error message if required fields are empty', async () => {
     const { getByText } = render(<FeedbackForm {...defaultProps} />);
 

From 265e629d902c28499bc72f2c20ebd7f8a590b1a5 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 19 Dec 2024 10:28:50 +0200
Subject: [PATCH 69/73] (2.2) feat: Add Feedback Form UI Branding logo (#4357)

* Adds sentry branding logo as a base64 encoded png

---------

Co-authored-by: LucasZF <lucas-zimerman1@hotmail.com>
---
 .../src/js/feedback/FeedbackForm.styles.ts    | 11 +++++++++-
 .../core/src/js/feedback/FeedbackForm.tsx     | 13 +++++++++++-
 .../src/js/feedback/FeedbackForm.types.ts     | 20 ++++++++++++++++++-
 packages/core/src/js/feedback/branding.ts     |  5 +++++
 packages/core/src/js/feedback/defaults.ts     |  1 +
 .../core/test/feedback/FeedbackForm.test.tsx  |  9 ++++++++-
 6 files changed, 55 insertions(+), 4 deletions(-)
 create mode 100644 packages/core/src/js/feedback/branding.ts

diff --git a/packages/core/src/js/feedback/FeedbackForm.styles.ts b/packages/core/src/js/feedback/FeedbackForm.styles.ts
index ff92690f28..836f4e1629 100644
--- a/packages/core/src/js/feedback/FeedbackForm.styles.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.styles.ts
@@ -15,7 +15,8 @@ const defaultStyles: FeedbackFormStyles = {
     fontSize: 24,
     fontWeight: 'bold',
     marginBottom: 20,
-    textAlign: 'center',
+    textAlign: 'left',
+    flex: 1,
     color: FORGROUND_COLOR,
   },
   label: {
@@ -57,6 +58,14 @@ const defaultStyles: FeedbackFormStyles = {
     color: FORGROUND_COLOR,
     fontSize: 16,
   },
+  titleContainer: {
+    flexDirection: 'row',
+    width: '100%',
+  },
+  sentryLogo: {
+    width: 40,
+    height: 40,
+  },
 };
 
 export default defaultStyles;
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 6bd3404024..6e741b655d 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -4,6 +4,7 @@ import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import {
   Alert,
+  Image,
   Keyboard,
   KeyboardAvoidingView,
   SafeAreaView,
@@ -15,6 +16,7 @@ import {
   View
 } from 'react-native';
 
+import { sentryLogo } from './branding';
 import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
@@ -104,7 +106,16 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
         <ScrollView>
           <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
             <View style={styles.container}>
-              <Text style={styles.title}>{text.formTitle}</Text>
+              <View style={styles.titleContainer}>
+                <Text style={styles.title}>{text.formTitle}</Text>
+                {config.showBranding && (
+                  <Image
+                    source={{ uri: sentryLogo }}
+                    style={styles.sentryLogo}
+                    testID='sentry-logo'
+                  />
+                )}
+              </View>
 
               {config.showName && (
               <>
diff --git a/packages/core/src/js/feedback/FeedbackForm.types.ts b/packages/core/src/js/feedback/FeedbackForm.types.ts
index 84078d8a61..9805e166c1 100644
--- a/packages/core/src/js/feedback/FeedbackForm.types.ts
+++ b/packages/core/src/js/feedback/FeedbackForm.types.ts
@@ -1,5 +1,8 @@
-import type { TextStyle, ViewStyle } from 'react-native';
+import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
 
+/**
+ * The props for the feedback form
+ */
 export interface FeedbackFormProps extends FeedbackGeneralConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {
   styles?: FeedbackFormStyles;
 }
@@ -8,6 +11,13 @@ export interface FeedbackFormProps extends FeedbackGeneralConfiguration, Feedbac
  * General feedback configuration
  */
 export interface FeedbackGeneralConfiguration {
+  /**
+   * Show the Sentry branding
+   *
+   * @default true
+   */
+  showBranding?: boolean;
+
   /**
    * Should the email field be required?
    */
@@ -128,6 +138,9 @@ export interface FeedbackCallbacks {
   onFormClose?: () => void;
 }
 
+/**
+ * The styles for the feedback form
+ */
 export interface FeedbackFormStyles {
   container?: ViewStyle;
   title?: TextStyle;
@@ -138,8 +151,13 @@ export interface FeedbackFormStyles {
   submitText?: TextStyle;
   cancelButton?: ViewStyle;
   cancelText?: TextStyle;
+  titleContainer?: ViewStyle;
+  sentryLogo?: ImageStyle;
 }
 
+/**
+ * The state of the feedback form
+ */
 export interface FeedbackFormState {
   isVisible: boolean;
   name: string;
diff --git a/packages/core/src/js/feedback/branding.ts b/packages/core/src/js/feedback/branding.ts
new file mode 100644
index 0000000000..e69dd1c79f
--- /dev/null
+++ b/packages/core/src/js/feedback/branding.ts
@@ -0,0 +1,5 @@
+/**
+ * Base64 encoded PNG image of the Sentry logo (source https://sentry.io/branding/)
+ */
+export const sentryLogo =
+  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAAC4BAMAAABUVL5sAAAAD1BMVEUAAAA2Llo3LVk4LFg2LVmgcvrUAAAABHRSTlMAgL9ARyeO/QAAA7pJREFUeNrtWltynDAQFBIHIFUcgCQ6AIk5AAjd/0ypxFk3ouUCNJofl/rHy75a09MzI7Q2DQ0NDQ0NDQ0NDQ0N9eF+xPh9MKpwPv6FLss7R9yMImz8j8nowb9IglFDF6N+KCNIdqMFcGjpBbVUXdzHqKsXUhJGNb2g1vz6azRgX8XulUwMtVZjFsWkvFz10s0ooEO+9Vpxj/6rp9eI5VsdE6eJcDCxilqICiZWMHBSMkBVA2vq1SVrVyr6Ea5VMjHqjytTwcAgRVJ0DAy9VoWUTNT3tQysZGIYmGNTKHfK0q6oFkysYWCuHB0DaxX9yHLx5JKrhVC0TGwjWNRMvNAuW6PoR4TCepnqLLuiicEyKJq4g2C6Rc+hsInlgtHCF41OrD65INisObksbKy2/YJg9U08sGBTbRN3YaBQQu2i74/mcQil6vZr5C0dQpGbGKJvdOA1VDVxd5LHooPR5BJPdwql5vaLeq9FQZKJpa1kOoUSKpq45+m+ZK93aUpSwRyuafslUguCIZRZamKoBYQT80QmlqnF38p6bSIDcyg2q5fw/uo8dx0upZMLLdadQ1kgkNDEOOGiYYXjVKGJ8V00rEggcErurxAKCwQTi9RCAW7UFcUm5vPAOFNXFBY9DggQyp76jnIkOyBwaeqtyMScEu7w4JRNLnyQOjyvACcWpR145g6PV1fp9mvE0jMd3tWZXDR3/Ud2cSXZfmHhvNpEoFlgYsjFHX4AJc3kXXSTyEfDTrz94ptE1qvS9ouG1Ud2sQT5PVcHg3FL78FIYUpqxWK1yLzMxNzhHVaLzItMzB0eB/S4CDRHC+AzFTjhAiSSHx9tpgJXqnmhXi7VizM/F5v4V5oVqOIp81PpEW4Xt7PUA0kEe5WZ2PLt7ZopDg8Seue9GpxoU0WrHyFPgYlzmyrKPDxcpFeX3YRS5mGvxybmsC2tPhLJQxPzdsfliwMeLjAx9wcujoFIaEAX/KSYXz0s+9TE/E7LX0yF8lQvitl99sVjSgITl/yk6Lk48JjfGadnanHml8xjMvFTA+eL42CRwDKEZwbm4rBMyAmdH6UEz8HDTPj4d4ie1EJxJCQg56DXaxKOl0iGz0jcdebZluzhbFSA1yEZ2JzbHZKQe3I/EK4CErTHbwn84ZP+8Poxqrd/+I2cXJAw0v9VAkBiI3DhLryZEe6SXNeJk5HcHFu+Aom5wiIn2a7niZiE1WKMUhIOhNFJSQZzh0VG8tPcQufLSQI46sO9vcM0NDQ0NDQ0NDQ0NHxF/AFGJOBYBWrb5gAAAABJRU5ErkJggg==';
diff --git a/packages/core/src/js/feedback/defaults.ts b/packages/core/src/js/feedback/defaults.ts
index ae8a3e9578..f184c62634 100644
--- a/packages/core/src/js/feedback/defaults.ts
+++ b/packages/core/src/js/feedback/defaults.ts
@@ -29,6 +29,7 @@ export const defaultConfiguration: Partial<FeedbackFormProps> = {
   },
 
   // FeedbackGeneralConfiguration
+  showBranding: true,
   isEmailRequired: false,
   shouldValidateEmail: true,
   isNameRequired: false,
diff --git a/packages/core/test/feedback/FeedbackForm.test.tsx b/packages/core/test/feedback/FeedbackForm.test.tsx
index bf8d963a57..a97250441b 100644
--- a/packages/core/test/feedback/FeedbackForm.test.tsx
+++ b/packages/core/test/feedback/FeedbackForm.test.tsx
@@ -46,9 +46,10 @@ describe('FeedbackForm', () => {
   });
 
   it('renders correctly', () => {
-    const { getByPlaceholderText, getByText } = render(<FeedbackForm {...defaultProps} />);
+    const { getByPlaceholderText, getByText, getByTestId } = render(<FeedbackForm {...defaultProps} />);
 
     expect(getByText(defaultProps.formTitle)).toBeTruthy();
+    expect(getByTestId('sentry-logo')).toBeTruthy(); // default showBranding is true
     expect(getByText(defaultProps.nameLabel)).toBeTruthy();
     expect(getByPlaceholderText(defaultProps.namePlaceholder)).toBeTruthy();
     expect(getByText(defaultProps.emailLabel)).toBeTruthy();
@@ -59,6 +60,12 @@ describe('FeedbackForm', () => {
     expect(getByText(defaultProps.cancelButtonLabel)).toBeTruthy();
   });
 
+  it('does not render the sentry logo when showBranding is false', () => {
+    const { queryByTestId } = render(<FeedbackForm {...defaultProps} showBranding={false} />);
+
+    expect(queryByTestId('sentry-logo')).toBeNull();
+  });
+
   it('name and email are prefilled when sentry user is set', () => {
     const { getByPlaceholderText } = render(<FeedbackForm {...defaultProps} />);
 

From da0e3ea6e96eab2a0d2e24b22c2b65f6dd304024 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 19 Dec 2024 10:29:55 +0200
Subject: [PATCH 70/73] Autoinject feedback form (#4370)

---
 CHANGELOG.md                                  | 11 ++++++
 .../core/src/js/feedback/FeedbackForm.tsx     | 34 ++++++++++++++++++-
 packages/core/src/js/index.ts                 |  2 +-
 .../react-native/src/Screens/ErrorsScreen.tsx |  7 ++++
 4 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2219a90965..e22ae8e862 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,17 @@ To learn how to attach context data to the feedback visit [the documentation](ht
   ...
   <FeedbackForm/>
   ```
+  or auto-inject it by calling the `showFeedbackForm`:
+    ```jsx
+  import { showFeedbackForm } from '@sentry/react-native';
+  ...
+    <Button
+      title="Show feedback form"
+      onPress={() => {
+        showFeedbackForm(_props.navigation);
+      }}
+    />
+  ```
 
 - Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))
 - Add RN SDK package to `sdk.packages` on Android ([#4380](https://github.com/getsentry/sentry-react-native/pull/4380))
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 6e741b655d..94ce437e64 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -1,5 +1,5 @@
 import type { SendFeedbackParams } from '@sentry/core';
-import { captureFeedback, getCurrentScope, lastEventId } from '@sentry/core';
+import { captureFeedback, getCurrentScope, lastEventId, logger } from '@sentry/core';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import {
@@ -21,6 +21,31 @@ import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
 
+let feedbackFormHandler: (() => void) | null = null;
+
+const setFeedbackFormHandler = (handler: () => void): void => {
+  feedbackFormHandler = handler;
+};
+
+const clearFeedbackFormHandler = (): void => {
+  feedbackFormHandler = null;
+};
+
+type Navigation = {
+  navigate: (screen: string, params?: Record<string, unknown>) => void;
+};
+
+export const showFeedbackForm = (navigation: Navigation): void => {
+  setFeedbackFormHandler(() => {
+    navigation?.navigate?.('FeedbackForm');
+  });
+  if (feedbackFormHandler) {
+    feedbackFormHandler();
+  } else {
+    logger.error('FeedbackForm handler is not set. Please ensure it is initialized.');
+  }
+};
+
 /**
  * @beta
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
@@ -48,6 +73,13 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     };
   }
 
+  /**
+   * Clear the handler when the component unmounts
+   */
+  public componentWillUnmount(): void {
+    clearFeedbackFormHandler();
+  }
+
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
     const { onFormClose } = this.props;
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index 53abd065b8..790de9fbd3 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -85,4 +85,4 @@ export type { TimeToDisplayProps } from './tracing';
 
 export { Mask, Unmask } from './replay/CustomMask';
 
-export { FeedbackForm } from './feedback/FeedbackForm';
+export { FeedbackForm, showFeedbackForm } from './feedback/FeedbackForm';
diff --git a/samples/react-native/src/Screens/ErrorsScreen.tsx b/samples/react-native/src/Screens/ErrorsScreen.tsx
index 4788fa407a..57c3bc3f31 100644
--- a/samples/react-native/src/Screens/ErrorsScreen.tsx
+++ b/samples/react-native/src/Screens/ErrorsScreen.tsx
@@ -12,6 +12,7 @@ import {
 } from 'react-native';
 
 import * as Sentry from '@sentry/react-native';
+import { showFeedbackForm } from '@sentry/react-native';
 
 import { setScopeProperties } from '../setScopeProperties';
 import { StackNavigationProp } from '@react-navigation/stack';
@@ -226,6 +227,12 @@ const ErrorsScreen = (_props: Props) => {
             _props.navigation.navigate('FeedbackForm');
           }}
         />
+        <Button
+          title="Feedback form (autoinject)"
+          onPress={() => {
+            showFeedbackForm(_props.navigation);
+          }}
+        />
         <Button
           title="Send user feedback"
           onPress={() => {

From 5f9dec6b13d4f567ff779e9d02d0b97e401adaf2 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 19 Dec 2024 11:18:46 +0200
Subject: [PATCH 71/73] Align changelog entry

---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef70612a04..67e32ac967 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,11 +25,11 @@
   });
   ```
 
-To learn how to attach context data to the feedback visit [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/).
+  To learn how to attach context data to the feedback visit [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/).
 
 - User Feedback Form Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
 
-   To collect user feedback from inside your application add the `FeedbackForm` component.
+  To collect user feedback from inside your application add the `FeedbackForm` component.
 
   ```jsx
   import { FeedbackForm } from "@sentry/react-native";

From 9853630c6efd07354cf608d1a19774a1d7b0cd9b Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Thu, 9 Jan 2025 13:07:53 +0200
Subject: [PATCH 72/73] Update changelog

---
 CHANGELOG.md | 44 +++++++++++++++++++++++---------------------
 1 file changed, 23 insertions(+), 21 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94a0515a3b..4168f3ed7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,29 @@
 
 ## Unreleased
 
+### Features
+
+- User Feedback Form Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
+
+  To collect user feedback from inside your application add the `FeedbackForm` component.
+
+  ```jsx
+  import { FeedbackForm } from "@sentry/react-native";
+  ...
+  <FeedbackForm/>
+  ```
+  or auto-inject it by calling the `showFeedbackForm`:
+    ```jsx
+  import { showFeedbackForm } from '@sentry/react-native';
+  ...
+    <Button
+      title="Show feedback form"
+      onPress={() => {
+        showFeedbackForm(_props.navigation);
+      }}
+    />
+  ```
+
 ### Changes
 
 - Rename `navigation.processing` span to more expressive `Navigation dispatch to screen A mounted/navigation cancelled` ([#4423](https://github.com/getsentry/sentry-react-native/pull/4423))
@@ -62,27 +85,6 @@
 
   To learn how to attach context data to the feedback visit [the documentation](https://docs.sentry.io/platforms/react-native/user-feedback/).
 
-- User Feedback Form Component Beta ([#4320](https://github.com/getsentry/sentry-react-native/pull/4328))
-
-  To collect user feedback from inside your application add the `FeedbackForm` component.
-
-  ```jsx
-  import { FeedbackForm } from "@sentry/react-native";
-  ...
-  <FeedbackForm/>
-  ```
-  or auto-inject it by calling the `showFeedbackForm`:
-    ```jsx
-  import { showFeedbackForm } from '@sentry/react-native';
-  ...
-    <Button
-      title="Show feedback form"
-      onPress={() => {
-        showFeedbackForm(_props.navigation);
-      }}
-    />
-  ```
-
 - Export `Span` type from `@sentry/types` ([#4345](https://github.com/getsentry/sentry-react-native/pull/4345))
 - Add RN SDK package to `sdk.packages` on Android ([#4380](https://github.com/getsentry/sentry-react-native/pull/4380))
 

From e7a94844feea6b43a2d7c942c1d8bb5329099f76 Mon Sep 17 00:00:00 2001
From: Antonis Lilis <antonis.lilis@gmail.com>
Date: Fri, 10 Jan 2025 11:18:30 +0200
Subject: [PATCH 73/73] Revert "Autoinject feedback form (#4370)"

This reverts commit da0e3ea6e96eab2a0d2e24b22c2b65f6dd304024.
---
 CHANGELOG.md                                  | 11 ------
 .../core/src/js/feedback/FeedbackForm.tsx     | 34 +------------------
 packages/core/src/js/index.ts                 |  2 +-
 .../react-native/src/Screens/ErrorsScreen.tsx |  7 ----
 4 files changed, 2 insertions(+), 52 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4168f3ed7a..6e2e4599e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,17 +19,6 @@
   ...
   <FeedbackForm/>
   ```
-  or auto-inject it by calling the `showFeedbackForm`:
-    ```jsx
-  import { showFeedbackForm } from '@sentry/react-native';
-  ...
-    <Button
-      title="Show feedback form"
-      onPress={() => {
-        showFeedbackForm(_props.navigation);
-      }}
-    />
-  ```
 
 ### Changes
 
diff --git a/packages/core/src/js/feedback/FeedbackForm.tsx b/packages/core/src/js/feedback/FeedbackForm.tsx
index 94ce437e64..6e741b655d 100644
--- a/packages/core/src/js/feedback/FeedbackForm.tsx
+++ b/packages/core/src/js/feedback/FeedbackForm.tsx
@@ -1,5 +1,5 @@
 import type { SendFeedbackParams } from '@sentry/core';
-import { captureFeedback, getCurrentScope, lastEventId, logger } from '@sentry/core';
+import { captureFeedback, getCurrentScope, lastEventId } from '@sentry/core';
 import * as React from 'react';
 import type { KeyboardTypeOptions } from 'react-native';
 import {
@@ -21,31 +21,6 @@ import { defaultConfiguration } from './defaults';
 import defaultStyles from './FeedbackForm.styles';
 import type { FeedbackFormProps, FeedbackFormState, FeedbackFormStyles,FeedbackGeneralConfiguration, FeedbackTextConfiguration } from './FeedbackForm.types';
 
-let feedbackFormHandler: (() => void) | null = null;
-
-const setFeedbackFormHandler = (handler: () => void): void => {
-  feedbackFormHandler = handler;
-};
-
-const clearFeedbackFormHandler = (): void => {
-  feedbackFormHandler = null;
-};
-
-type Navigation = {
-  navigate: (screen: string, params?: Record<string, unknown>) => void;
-};
-
-export const showFeedbackForm = (navigation: Navigation): void => {
-  setFeedbackFormHandler(() => {
-    navigation?.navigate?.('FeedbackForm');
-  });
-  if (feedbackFormHandler) {
-    feedbackFormHandler();
-  } else {
-    logger.error('FeedbackForm handler is not set. Please ensure it is initialized.');
-  }
-};
-
 /**
  * @beta
  * Implements a feedback form screen that sends feedback to Sentry using Sentry.captureFeedback.
@@ -73,13 +48,6 @@ export class FeedbackForm extends React.Component<FeedbackFormProps, FeedbackFor
     };
   }
 
-  /**
-   * Clear the handler when the component unmounts
-   */
-  public componentWillUnmount(): void {
-    clearFeedbackFormHandler();
-  }
-
   public handleFeedbackSubmit: () => void = () => {
     const { name, email, description } = this.state;
     const { onFormClose } = this.props;
diff --git a/packages/core/src/js/index.ts b/packages/core/src/js/index.ts
index 790de9fbd3..53abd065b8 100644
--- a/packages/core/src/js/index.ts
+++ b/packages/core/src/js/index.ts
@@ -85,4 +85,4 @@ export type { TimeToDisplayProps } from './tracing';
 
 export { Mask, Unmask } from './replay/CustomMask';
 
-export { FeedbackForm, showFeedbackForm } from './feedback/FeedbackForm';
+export { FeedbackForm } from './feedback/FeedbackForm';
diff --git a/samples/react-native/src/Screens/ErrorsScreen.tsx b/samples/react-native/src/Screens/ErrorsScreen.tsx
index 57c3bc3f31..4788fa407a 100644
--- a/samples/react-native/src/Screens/ErrorsScreen.tsx
+++ b/samples/react-native/src/Screens/ErrorsScreen.tsx
@@ -12,7 +12,6 @@ import {
 } from 'react-native';
 
 import * as Sentry from '@sentry/react-native';
-import { showFeedbackForm } from '@sentry/react-native';
 
 import { setScopeProperties } from '../setScopeProperties';
 import { StackNavigationProp } from '@react-navigation/stack';
@@ -227,12 +226,6 @@ const ErrorsScreen = (_props: Props) => {
             _props.navigation.navigate('FeedbackForm');
           }}
         />
-        <Button
-          title="Feedback form (autoinject)"
-          onPress={() => {
-            showFeedbackForm(_props.navigation);
-          }}
-        />
         <Button
           title="Send user feedback"
           onPress={() => {