Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions static/app/actionCreators/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {SaveQueryModalProps} from 'sentry/components/modals/explore/saveQue
import type {ImportDashboardFromFileModalProps} from 'sentry/components/modals/importDashboardFromFileModal';
import type {InsightChartModalOptions} from 'sentry/components/modals/insightChartModal';
import type {InviteRow} from 'sentry/components/modals/inviteMembersModal/types';
import type {PrivateGamingSdkAccessModalProps} from 'sentry/components/modals/privateGamingSdkAccessModal';
import type {ReprocessEventModalOptions} from 'sentry/components/modals/reprocessEventModal';
import type {AddToDashboardModalProps} from 'sentry/components/modals/widgetBuilder/addToDashboardModal';
import type {OverwriteWidgetModalProps} from 'sentry/components/modals/widgetBuilder/overwriteWidgetModal';
Expand Down Expand Up @@ -447,3 +448,13 @@ export async function openTokenRegenerationConfirmationModal(options: ModalOptio

openModal(deps => <Modal {...deps} {...options} />);
}

export async function openPrivateGamingSdkAccessModal(
Copy link
Collaborator

Choose a reason for hiding this comment

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

this function is unused, so I’d expect knip to complain here (which it doesn’t). I’ll look into that, but can we make the function used somewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes it did not complain which is weird, but this function will be used in 3 different places. Examples: #96300 and #96415

options: PrivateGamingSdkAccessModalProps
) {
const {PrivateGamingSdkAccessModal} = await import(
'sentry/components/modals/privateGamingSdkAccessModal'
);

openModal(deps => <PrivateGamingSdkAccessModal {...deps} {...options} />);
}
174 changes: 174 additions & 0 deletions static/app/components/modals/privateGamingSdkAccessModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {Fragment, useState} from 'react';
import {captureFeedback} from '@sentry/react';

import {addSuccessMessage} from 'sentry/actionCreators/indicator';
import {type ModalRenderProps} from 'sentry/actionCreators/modal';
import {Button} from 'sentry/components/core/button';
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
import SelectField from 'sentry/components/forms/fields/selectField';
import TextField from 'sentry/components/forms/fields/textField';
import {t, tct} from 'sentry/locale';
import type {Organization} from 'sentry/types/organization';
import {useUser} from 'sentry/utils/useUser';

const PRIVATE_GAMING_SDK_OPTIONS = [
{value: 'playstation', label: 'PlayStation'},
{value: 'xbox', label: 'Xbox'},
{value: 'nintendo-switch', label: 'Nintendo Switch'},
] as const;

type GamingPlatform = (typeof PRIVATE_GAMING_SDK_OPTIONS)[number]['value'];

export interface PrivateGamingSdkAccessModalProps {
organization: Organization;
projectSlug: string;
sdkName: string;
gamingPlatform?: GamingPlatform;
onSubmit?: () => void;
}

export function PrivateGamingSdkAccessModal({
Header,
Body,
Footer,
closeModal,
organization,
projectSlug,
sdkName,
gamingPlatform,
onSubmit,
}: PrivateGamingSdkAccessModalProps & ModalRenderProps) {
const user = useUser();
const [isSubmitting, setIsSubmitting] = useState(false);
const [githubProfile, setGithubProfile] = useState('');
const [gamingPlatforms, setGamingPlatforms] = useState<string[]>(
gamingPlatform ? [gamingPlatform] : []
);

const isFormValid = !!githubProfile.trim() && gamingPlatforms.length > 0;

function handleSubmit() {
if (!isFormValid) {
return;
}

setIsSubmitting(true);

onSubmit?.();

const messageBody = [
`User: ${user.name}`,
`Email: ${user.email}`,
gamingPlatforms.length === 1
? `Platform: ${gamingPlatforms[0]}`
: `Platforms: ${gamingPlatforms
.map(
(platform: string) =>
PRIVATE_GAMING_SDK_OPTIONS.find(option => option.value === platform)
?.label || platform
)
.join(', ')}`,
`Org Slug: ${organization.slug}`,
`Project: ${projectSlug}`,
`GitHub Profile: ${githubProfile}`,
].join('\n');

const source = `${sdkName.toLowerCase()}-sdk-access`;

// Use captureFeedback with proper user context instead of tags
captureFeedback(
{
message: messageBody,
name: user.name,
email: user.email,
source,
tags: {
feature: source,
},
},
{
captureContext: {
user: {
id: user.id,
email: user.email,
username: user.username,
name: user.name,
},
},
}
);

addSuccessMessage(
tct('Your [sdkName] SDK access request has been submitted.', {
sdkName,
})
);
setIsSubmitting(false);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I’m unsure how this should work. This handleSubmit function is a synchronous callback, so it first calls setIsSubmitting(true) followed by setIsSubmitting(false), so I don’t see where the true state would ever be rendered 🤔 .

This is part of the reason why merging something that has no usage is a bit counter productive imo because we can’t know how it works / what it does.

I’m guessing onSubmit is going to maybe trigger a mutation on the call-side? Would it be better to trigger that mutation from inside here, which would also give us mutation state like isPending and callbacks to add the success messages properly?

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree - it would be great to use isPending and show a success message only if everything went well. But we're using captureFeedback, which doesn't wait for the result. It just tries to send the feedback and returns immediately. You can read more about it here.

This modal will be removed in a future phase, but for now, we’re keeping it simple by reusing what we already have.

closeModal();
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Feedback Formatting and Submission State Issues

The PrivateGamingSdkAccessModal has two issues:

  1. The feedback message sent via captureFeedback inconsistently formats gaming platform names: single platforms use raw values (e.g., 'playstation'), while multiple platforms use display labels (e.g., 'PlayStation'), resulting in inconsistent capitalization.
  2. If the captureFeedback call fails, the isSubmitting state is not reset and the modal remains open, leaving the UI in a perpetual submitting state.
Locations (1)

Fix in CursorFix in Web

Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Feedback Submission Timing and Formatting Issues

The captureFeedback function is asynchronous but not awaited, leading to premature isSubmitting state reset, early success message display, and modal closure before feedback is sent. Additionally, the feedback message inconsistently formats gaming platform names, using raw values for single selections and human-readable labels for multiple selections.

Locations (1)

Fix in CursorFix in Web

}

return (
<Fragment>
<Header closeButton>
<h3>
{tct('Request [sdkName] SDK Access', {
sdkName,
})}
</h3>
</Header>
<Body>
<p>
{gamingPlatform
? tct(
'Request access to our [sdkName] SDK. Please provide your GitHub profile.',
{
sdkName,
}
)
: tct(
'Request access to our [sdkName] SDK. Please provide your GitHub profile and the gaming platforms you work with.',
{
sdkName,
}
)}
</p>
<TextField
name="githubProfile"
label={t('Link to your GitHub profile')}
placeholder="https://github.com/username"
value={githubProfile}
onChange={setGithubProfile}
required
stacked
inline={false}
/>
{!gamingPlatform && (
<SelectField
name="gamingPlatforms"
label={t('Select Gaming Platform')}
placeholder={t('Select one or more gaming platforms')}
options={PRIVATE_GAMING_SDK_OPTIONS}
value={gamingPlatforms}
onChange={setGamingPlatforms}
multiple
required
stacked
inline={false}
/>
)}
</Body>
<Footer>
<ButtonBar>
<Button onClick={closeModal}>{t('Cancel')}</Button>
<Button
priority="primary"
onClick={handleSubmit}
disabled={!isFormValid || isSubmitting}
>
{isSubmitting ? t('Submitting\u2026') : t('Submit Request')}
</Button>
</ButtonBar>
</Footer>
</Fragment>
);
}
Loading