Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(sign-up): Add external providers options #83821

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
77 changes: 77 additions & 0 deletions static/app/views/auth/externalProviderOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import styled from '@emotion/styled';

import {LinkButton} from 'sentry/components/button';
import {IconGithub} from 'sentry/icons/iconGithub';
import {IconGoogle} from 'sentry/icons/iconGoogle';
import {IconVsts} from 'sentry/icons/iconVsts';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';

interface ExternalProviderOptionsProps {
type: 'signin' | 'signup';
azureDevOpsLink?: string;
githubLink?: string;
googleLink?: string;
}

// TODO(epurkhiser): The abstraction here would be much nicer if we just
// exposed a configuration object telling us what auth providers there are.
export function ExternalProviderOptions({
type,
googleLink,
githubLink,
azureDevOpsLink,
}: ExternalProviderOptionsProps) {
return (
<ProviderWrapper>
<ProviderWrapper>
<ProviderHeading>
{type === 'signin'
? t('External Account Login')
: t('External Account Register')}
</ProviderHeading>
{googleLink && (
<LinkButton size="sm" icon={<IconGoogle />} href={googleLink}>
{type === 'signin' ? t('Sign in with Google') : t('Sign up with Google')}
</LinkButton>
)}
{githubLink && (
<LinkButton size="sm" icon={<IconGithub />} href={githubLink}>
{type === 'signin' ? t('Sign in with Github') : t('Sign up with Github')}
</LinkButton>
)}
{azureDevOpsLink && (
<LinkButton size="sm" icon={<IconVsts />} href={azureDevOpsLink}>
{type === 'signin'
? t('Sign in with Azure DevOps')
: t('Sign up with Azure DevOps')}
</LinkButton>
)}
</ProviderWrapper>
</ProviderWrapper>
);
}

const ProviderWrapper = styled('div')`
position: relative;
display: grid;
grid-auto-rows: max-content;
gap: ${space(1.5)};

&:before {
position: absolute;
display: block;
content: '';
top: 0;
bottom: 0;
left: -30px;
border-left: 1px solid ${p => p.theme.border};
}
`;

const ProviderHeading = styled('div')`
margin: 0;
font-size: 15px;
font-weight: ${p => p.theme.fontWeightBold};
line-height: 24px;
`;
69 changes: 8 additions & 61 deletions static/app/views/auth/loginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,15 @@ import {useState} from 'react';
import styled from '@emotion/styled';

import {Alert} from 'sentry/components/alert';
import {LinkButton} from 'sentry/components/button';
import SecretField from 'sentry/components/forms/fields/secretField';
import TextField from 'sentry/components/forms/fields/textField';
import Form from 'sentry/components/forms/form';
import Link from 'sentry/components/links/link';
import {IconGithub, IconGoogle, IconVsts} from 'sentry/icons';
import {t} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import {space} from 'sentry/styles/space';
import type {AuthConfig} from 'sentry/types/auth';
import {browserHistory} from 'sentry/utils/browserHistory';

type LoginProvidersProps = Partial<
Pick<AuthConfig, 'vstsLoginLink' | 'githubLoginLink' | 'googleLoginLink'>
>;

// TODO(epurkhiser): The abstraction here would be much nicer if we just
// exposed a configuration object telling us what auth providers there are.
function LoginProviders({
vstsLoginLink,
githubLoginLink,
googleLoginLink,
}: LoginProvidersProps) {
return (
<ProviderWrapper>
<ProviderHeading>{t('External Account Login')}</ProviderHeading>
{googleLoginLink && (
<LinkButton size="sm" icon={<IconGoogle />} href={googleLoginLink}>
{t('Sign in with Google')}
</LinkButton>
)}
{githubLoginLink && (
<LinkButton size="sm" icon={<IconGithub />} href={githubLoginLink}>
{t('Sign in with GitHub')}
</LinkButton>
)}
{vstsLoginLink && (
<LinkButton size="sm" icon={<IconVsts />} href={vstsLoginLink}>
{t('Sign in with Azure DevOps')}
</LinkButton>
)}
</ProviderWrapper>
);
}
import {ExternalProviderOptions} from 'sentry/views/auth/externalProviderOptions';

type Props = {
authConfig: AuthConfig;
Expand Down Expand Up @@ -110,7 +75,13 @@ function LoginForm({authConfig}: Props) {
required
/>
</Form>
{hasLoginProvider && <LoginProviders {...{vstsLoginLink, githubLoginLink}} />}
{hasLoginProvider && (
<ExternalProviderOptions
type="signin"
githubLink={vstsLoginLink}
azureDevOpsLink={vstsLoginLink}
/>
)}
</FormWrapper>
);
}
Expand All @@ -121,30 +92,6 @@ const FormWrapper = styled('div')<{hasLoginProvider: boolean}>`
grid-template-columns: ${p => (p.hasLoginProvider ? '1fr 0.8fr' : '1fr')};
`;

const ProviderHeading = styled('div')`
margin: 0;
font-size: 15px;
font-weight: ${p => p.theme.fontWeightBold};
line-height: 24px;
`;

const ProviderWrapper = styled('div')`
position: relative;
display: grid;
grid-auto-rows: max-content;
gap: ${space(1.5)};

&:before {
position: absolute;
display: block;
content: '';
top: 0;
bottom: 0;
left: -30px;
border-left: 1px solid ${p => p.theme.border};
}
`;

const LostPasswordLink = styled(Link)`
color: ${p => p.theme.gray300};
font-size: ${p => p.theme.fontSizeMedium};
Expand Down
157 changes: 98 additions & 59 deletions static/app/views/auth/registerForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ import {t, tct} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import type {AuthConfig} from 'sentry/types/auth';
import {browserHistory} from 'sentry/utils/browserHistory';
import {ExternalProviderOptions} from 'sentry/views/auth/externalProviderOptions';

type GetSignUpProviderUrlsOutput = Record<'google' | 'github' | 'vsts', string>;

function getSignUpProviderUrls(): GetSignUpProviderUrlsOutput {
const baseUrl = ConfigStore.get('links').sentryUrl;
const providers = ['google', 'github', 'vsts'] as const;

const urls = Object.fromEntries(
providers.map(provider => [
provider,
String(new URL(`/identity/login/${provider}/`, baseUrl)),
])
);

return urls as GetSignUpProviderUrlsOutput;
}

type Props = {
authConfig: AuthConfig;
Expand All @@ -21,71 +38,87 @@ function RegisterForm({authConfig}: Props) {

const [error, setError] = useState('');

const signUpProviderUrls = getSignUpProviderUrls();

const orgSlug = ConfigStore.getState().customerDomain?.subdomain;
// TODO(Telemetry): Remove the condition below after checking that everything is working fine
const displayExternalProviders = orgSlug === 'pri-sentry';
Copy link
Member Author

Choose a reason for hiding this comment

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

Since the invited user might not be logged in yet, we cannot use our feature flag tool in this case. To test it before making it public, I’m adding checking against an org that I use specifically for testing purposes


return (
<Form
apiMethod="POST"
apiEndpoint="/auth/register/"
initialData={{subscribe: true}}
submitLabel={t('Continue')}
onSubmitSuccess={response => {
ConfigStore.set('user', response.user);
browserHistory.push({pathname: response.nextUri});
}}
onSubmitError={response => {
setError(response.responseJSON.detail);
}}
extraButton={
<PrivacyPolicyLink href="https://sentry.io/privacy/">
{t('Privacy Policy')}
</PrivacyPolicyLink>
}
>
{error && <Alert type="error">{error}</Alert>}
<TextField
name="name"
placeholder={t('Jane Bloggs')}
label={t('Name')}
stacked
inline={false}
required
/>
<TextField
name="username"
placeholder={t('[email protected]')}
label={t('Email')}
stacked
inline={false}
required
/>
<SecretField
name="password"
placeholder={t('something super secret')}
label={t('Password')}
stacked
inline={false}
required
/>
{hasNewsletter && (
<RadioBooleanField
name="subscribe"
choices={[
['true', t('Yes, I would like to receive updates via email')],
['flase', t("No, I'd prefer not to receive these updates")],
]}
help={tct(
`We'd love to keep you updated via email with product and feature
<FormWrapper hasRegisterProvider={displayExternalProviders}>
<Form
apiMethod="POST"
apiEndpoint="/auth/register/"
initialData={{subscribe: true}}
submitLabel={t('Continue')}
onSubmitSuccess={response => {
ConfigStore.set('user', response.user);
browserHistory.push({pathname: response.nextUri});
}}
onSubmitError={response => {
setError(response.responseJSON.detail);
}}
extraButton={
<PrivacyPolicyLink href="https://sentry.io/privacy/">
{t('Privacy Policy')}
</PrivacyPolicyLink>
}
>
{error && <Alert type="error">{error}</Alert>}
<TextField
name="name"
placeholder={t('Jane Bloggs')}
label={t('Name')}
stacked
inline={false}
required
/>
<TextField
name="username"
placeholder={t('[email protected]')}
label={t('Email')}
stacked
inline={false}
required
/>
<SecretField
name="password"
placeholder={t('something super secret')}
label={t('Password')}
stacked
inline={false}
required
/>
{hasNewsletter && (
<RadioBooleanField
name="subscribe"
choices={[
['true', t('Yes, I would like to receive updates via email')],
['flase', t("No, I'd prefer not to receive these updates")],
]}
help={tct(
`We'd love to keep you updated via email with product and feature
announcements, promotions, educational materials, and events. Our
updates focus on relevant information, and we'll never sell your data
to third parties. See our [link] for more details.`,
{
link: <a href="https://sentry.io/privacy/">Privacy Policy</a>,
}
)}
stacked
inline={false}
{
link: <a href="https://sentry.io/privacy/">Privacy Policy</a>,
}
)}
stacked
inline={false}
/>
)}
</Form>
{displayExternalProviders && (
<ExternalProviderOptions
type="signup"
googleLink={signUpProviderUrls.google}
githubLink={signUpProviderUrls.github}
azureDevOpsLink={signUpProviderUrls.vsts}
/>
)}
</Form>
</FormWrapper>
);
}

Expand All @@ -97,4 +130,10 @@ const PrivacyPolicyLink = styled(ExternalLink)`
}
`;

const FormWrapper = styled('div')<{hasRegisterProvider: boolean}>`
display: grid;
gap: 60px;
grid-template-columns: ${p => (p.hasRegisterProvider ? '1fr 0.8fr' : '1fr')};
`;

export default RegisterForm;
Loading