layout.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import type {ComponentProps} from 'react';
  2. import {Fragment} from 'react';
  3. import styled from '@emotion/styled';
  4. import HookOrDefault from 'sentry/components/hookOrDefault';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import List from 'sentry/components/list';
  7. import ListItem from 'sentry/components/list/listItem';
  8. import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
  9. import type {StepProps} from 'sentry/components/onboarding/gettingStartedDoc/step';
  10. import {Step} from 'sentry/components/onboarding/gettingStartedDoc/step';
  11. import type {NextStep} from 'sentry/components/onboarding/gettingStartedDoc/types';
  12. import {PlatformOptionsControl} from 'sentry/components/onboarding/platformOptionsControl';
  13. import {ProductSelection} from 'sentry/components/onboarding/productSelection';
  14. import {t} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import type {PlatformKey} from 'sentry/types';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. const ProductSelectionAvailabilityHook = HookOrDefault({
  19. hookName: 'component:product-selection-availability',
  20. defaultComponent: ProductSelection,
  21. });
  22. export type LayoutProps = {
  23. projectSlug: string;
  24. steps: StepProps[];
  25. /**
  26. * An introduction displayed before the steps
  27. */
  28. introduction?: React.ReactNode;
  29. newOrg?: boolean;
  30. nextSteps?: NextStep[];
  31. platformKey?: PlatformKey;
  32. platformOptions?: ComponentProps<typeof PlatformOptionsControl>['platformOptions'];
  33. };
  34. export function Layout({
  35. steps,
  36. platformKey,
  37. newOrg,
  38. nextSteps = [],
  39. platformOptions,
  40. introduction,
  41. projectSlug,
  42. }: LayoutProps) {
  43. const organization = useOrganization();
  44. return (
  45. <AuthTokenGeneratorProvider projectSlug={projectSlug}>
  46. <Wrapper>
  47. <Header>
  48. {introduction && <Introduction>{introduction}</Introduction>}
  49. <ProductSelectionAvailabilityHook
  50. organization={organization}
  51. platform={platformKey}
  52. />
  53. {platformOptions ? (
  54. <PlatformOptionsControl platformOptions={platformOptions} />
  55. ) : null}
  56. </Header>
  57. <Divider withBottomMargin={newOrg} />
  58. <Steps>
  59. {steps.map(step => (
  60. <Step key={step.title ?? step.type} {...step} />
  61. ))}
  62. </Steps>
  63. {nextSteps.length > 0 && (
  64. <Fragment>
  65. <Divider />
  66. <h4>{t('Next Steps')}</h4>
  67. <List symbol="bullet">
  68. {nextSteps.map(step => (
  69. <ListItem key={step.name}>
  70. <ExternalLink href={step.link}>{step.name}</ExternalLink>
  71. {': '}
  72. {step.description}
  73. </ListItem>
  74. ))}
  75. </List>
  76. </Fragment>
  77. )}
  78. </Wrapper>
  79. </AuthTokenGeneratorProvider>
  80. );
  81. }
  82. const Header = styled('div')`
  83. display: flex;
  84. flex-direction: column;
  85. gap: ${space(2)};
  86. `;
  87. const Divider = styled('hr')<{withBottomMargin?: boolean}>`
  88. height: 1px;
  89. width: 100%;
  90. background: ${p => p.theme.border};
  91. border: none;
  92. ${p => p.withBottomMargin && `margin-bottom: ${space(3)}`}
  93. `;
  94. const Steps = styled('div')`
  95. display: flex;
  96. flex-direction: column;
  97. gap: 1.5rem;
  98. `;
  99. const Introduction = styled('div')`
  100. display: flex;
  101. flex-direction: column;
  102. gap: ${space(1)};
  103. `;
  104. const Wrapper = styled('div')`
  105. h4 {
  106. margin-bottom: 0.5em;
  107. }
  108. && {
  109. p {
  110. margin-bottom: 0;
  111. }
  112. h5 {
  113. margin-bottom: 0;
  114. }
  115. }
  116. `;