layout.tsx 3.4 KB

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