footer.tsx 3.8 KB

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