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={() => {