footer.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import Hook from 'sentry/components/hook';
  4. import ExternalLink from 'sentry/components/links/externalLink';
  5. import {IconSentry} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import ConfigStore from 'sentry/stores/configStore';
  8. import space from 'sentry/styles/space';
  9. import getDynamicText from 'sentry/utils/getDynamicText';
  10. type Props = {
  11. className?: string;
  12. };
  13. function BaseFooter({className}: Props) {
  14. const config = ConfigStore.getConfig();
  15. return (
  16. <footer className={className}>
  17. <LeftLinks>
  18. {config.isSelfHosted && (
  19. <Fragment>
  20. {'Sentry '}
  21. {getDynamicText({
  22. fixed: 'Acceptance Test',
  23. value: config.version.current,
  24. })}
  25. <Build>
  26. {getDynamicText({
  27. fixed: 'test',
  28. value: config.version.build.substring(0, 7),
  29. })}
  30. </Build>
  31. </Fragment>
  32. )}
  33. {config.privacyUrl && (
  34. <FooterLink href={config.privacyUrl}>{t('Privacy Policy')}</FooterLink>
  35. )}
  36. {config.termsUrl && (
  37. <FooterLink href={config.termsUrl}>{t('Terms of Use')}</FooterLink>
  38. )}
  39. </LeftLinks>
  40. <LogoLink />
  41. <RightLinks>
  42. {!config.isSelfHosted && (
  43. <FooterLink href="https://status.sentry.io/">{t('Service Status')}</FooterLink>
  44. )}
  45. <FooterLink href="/api/">{t('API')}</FooterLink>
  46. <FooterLink href="/docs/">{t('Docs')}</FooterLink>
  47. <FooterLink href="https://github.com/getsentry/sentry">
  48. {t('Contribute')}
  49. </FooterLink>
  50. {config.isSelfHosted && !config.demoMode && (
  51. <FooterLink href="/out/">{t('Migrate to SaaS')}</FooterLink>
  52. )}
  53. </RightLinks>
  54. <Hook name="footer" />
  55. </footer>
  56. );
  57. }
  58. const LeftLinks = styled('div')`
  59. display: grid;
  60. grid-auto-flow: column;
  61. grid-auto-columns: max-content;
  62. align-items: center;
  63. justify-self: flex-start;
  64. gap: ${space(2)};
  65. `;
  66. const RightLinks = styled('div')`
  67. display: grid;
  68. grid-auto-flow: column;
  69. grid-auto-columns: max-content;
  70. align-items: center;
  71. justify-self: flex-end;
  72. gap: ${space(2)};
  73. `;
  74. const FooterLink = styled(ExternalLink)`
  75. color: ${p => p.theme.subText};
  76. &.focus-visible {
  77. outline: none;
  78. box-shadow: ${p => p.theme.blue300} 0 2px 0;
  79. }
  80. `;
  81. const LogoLink = styled(props => (
  82. <ExternalLink href="https://sentry.io/welcome/" tabIndex={-1} {...props}>
  83. <IconSentry size="lg" />
  84. </ExternalLink>
  85. ))`
  86. display: flex;
  87. align-items: center;
  88. margin: 0 auto;
  89. color: ${p => p.theme.subText};
  90. `;
  91. const Build = styled('span')`
  92. font-size: ${p => p.theme.fontSizeRelativeSmall};
  93. color: ${p => p.theme.subText};
  94. font-weight: bold;
  95. margin-left: ${space(1)};
  96. `;
  97. const Footer = styled(BaseFooter)`
  98. display: grid;
  99. grid-template-columns: 1fr 1fr 1fr;
  100. color: ${p => p.theme.subText};
  101. font-size: ${p => p.theme.fontSizeMedium};
  102. border-top: 1px solid ${p => p.theme.border};
  103. align-content: center;
  104. padding: ${space(2)} ${space(4)};
  105. margin-top: auto; /* pushes footer to the bottom of the page when loading */
  106. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  107. padding: ${space(2)};
  108. }
  109. @media (max-width: ${p => p.theme.breakpoints.small}) {
  110. display: none;
  111. }
  112. `;
  113. export default Footer;