layout.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {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 {Step, StepProps} from 'sentry/components/onboarding/gettingStartedDoc/step';
  8. import {
  9. ProductSelection,
  10. ProductSolution,
  11. } from 'sentry/components/onboarding/productSelection';
  12. import {PlatformKey} from 'sentry/data/platformCategories';
  13. import {t} from 'sentry/locale';
  14. import ConfigStore from 'sentry/stores/configStore';
  15. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  16. import {space} from 'sentry/styles/space';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. const ProductSelectionAvailabilityHook = HookOrDefault({
  19. hookName: 'component:product-selection-availability',
  20. });
  21. type NextStep = {
  22. description: string;
  23. link: string;
  24. name: string;
  25. };
  26. export type LayoutProps = {
  27. steps: StepProps[];
  28. /**
  29. * An introduction displayed before the steps
  30. */
  31. introduction?: React.ReactNode;
  32. newOrg?: boolean;
  33. nextSteps?: NextStep[];
  34. platformKey?: PlatformKey;
  35. };
  36. export function Layout({
  37. steps,
  38. platformKey,
  39. nextSteps = [],
  40. newOrg,
  41. introduction,
  42. }: LayoutProps) {
  43. const organization = useOrganization();
  44. const {isSelfHosted} = useLegacyStore(ConfigStore);
  45. const isJavaScriptPlatform =
  46. platformKey === 'javascript' || !!platformKey?.match('^javascript-([A-Za-z]+)$');
  47. const displayProductSelection = !isSelfHosted && isJavaScriptPlatform;
  48. return (
  49. <Wrapper>
  50. {introduction && (
  51. <Fragment>
  52. <Introduction>{introduction}</Introduction>
  53. <Divider />
  54. </Fragment>
  55. )}
  56. {displayProductSelection && newOrg && (
  57. <ProductSelection
  58. defaultSelectedProducts={[
  59. ProductSolution.PERFORMANCE_MONITORING,
  60. ProductSolution.SESSION_REPLAY,
  61. ]}
  62. />
  63. )}
  64. {displayProductSelection && !newOrg && (
  65. <ProductSelectionAvailabilityHook organization={organization} />
  66. )}
  67. <Steps withTopSpacing={!displayProductSelection && newOrg}>
  68. {steps.map(step => (
  69. <Step key={step.title ?? step.type} {...step} />
  70. ))}
  71. </Steps>
  72. {nextSteps.length > 0 && (
  73. <Fragment>
  74. <Divider />
  75. <h4>{t('Next Steps')}</h4>
  76. <List symbol="bullet">
  77. {nextSteps.map(step => (
  78. <ListItem key={step.name}>
  79. <ExternalLink href={step.link}>{step.name}</ExternalLink>
  80. {': '}
  81. {step.description}
  82. </ListItem>
  83. ))}
  84. </List>
  85. </Fragment>
  86. )}
  87. </Wrapper>
  88. );
  89. }
  90. const Divider = styled('hr')`
  91. height: 1px;
  92. width: 100%;
  93. background: ${p => p.theme.border};
  94. border: none;
  95. `;
  96. const Steps = styled('div')<{withTopSpacing?: boolean}>`
  97. display: flex;
  98. flex-direction: column;
  99. gap: 1.5rem;
  100. ${p => p.withTopSpacing && `margin-top: ${space(3)}`}
  101. `;
  102. const Introduction = styled('div')`
  103. display: flex;
  104. flex-direction: column;
  105. gap: ${space(1)};
  106. `;
  107. const Wrapper = styled('div')`
  108. h4 {
  109. margin-bottom: 0.5em;
  110. }
  111. && {
  112. p {
  113. margin-bottom: 0;
  114. }
  115. h5 {
  116. margin-bottom: 0;
  117. }
  118. }
  119. `;