import {forwardRef} from 'react'; import styled from '@emotion/styled'; import {motion} from 'framer-motion'; import moment from 'moment'; import {navigateTo} from 'sentry/actionCreators/navigation'; import Avatar from 'sentry/components/avatar'; import Button from 'sentry/components/button'; import Card from 'sentry/components/card'; import LetterAvatar from 'sentry/components/letterAvatar'; import Tooltip from 'sentry/components/tooltip'; import {IconCheckmark, IconClose, IconLock, IconSync} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import space from 'sentry/styles/space'; import {AvatarUser, OnboardingTask, OnboardingTaskKey, Organization} from 'sentry/types'; import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent'; import testableTransition from 'sentry/utils/testableTransition'; import {useRouteContext} from 'sentry/utils/useRouteContext'; import withOrganization from 'sentry/utils/withOrganization'; import SkipConfirm from './skipConfirm'; import {taskIsDone} from './utils'; const recordAnalytics = ( task: OnboardingTask, organization: Organization, action: string ) => trackAdvancedAnalyticsEvent('onboarding.wizard_clicked', { organization, todo_id: task.task, todo_title: task.title, action, }); type Props = { forwardedRef: React.Ref; /** * Fired when a task is completed. This will typically happen if there is a * supplemental component with the ability to complete a task */ onMarkComplete: (taskKey: OnboardingTaskKey) => void; /** * Fired when the task has been skipped */ onSkip: (taskKey: OnboardingTaskKey) => void; organization: Organization; /** * Task to render */ task: OnboardingTask; }; function Task(props: Props) { const {task, onSkip, onMarkComplete, forwardedRef, organization} = props; const routeContext = useRouteContext(); const {router} = routeContext; const handleSkip = () => { recordAnalytics(task, organization, 'skipped'); onSkip(task.task); }; const handleClick = (e: React.MouseEvent) => { recordAnalytics(task, organization, 'clickthrough'); e.stopPropagation(); if (task.actionType === 'external') { window.open(task.location, '_blank'); } if (task.actionType === 'action') { task.action(routeContext); } if (task.actionType === 'app') { const url = new URL(task.location, window.location.origin); url.searchParams.append('referrer', 'onboarding_task'); navigateTo(url.toString(), router); } }; if (taskIsDone(task) && task.completionSeen) { const completedOn = moment(task.dateCompleted); return ( {task.status === 'complete' && } {task.status === 'skipped' && } {task.title} {completedOn.fromNow()} {task.user ? ( ) : ( )} ); } const IncompleteMarker = task.requisiteTasks.length > 0 && ( ); const {SupplementComponent} = task; const supplement = SupplementComponent && ( onMarkComplete(task.task)} /> ); const skipAction = task.skippable && ( {({skip}) => ( } onClick={skip} /> )} ); return ( {IncompleteMarker} {task.title} {`${task.description}`} {task.requisiteTasks.length === 0 && ( {skipAction} {supplement} {task.status === 'pending' ? ( ) : ( )} )} ); } const TaskCard = styled(Card)` position: relative; padding: ${space(2)} ${space(3)}; `; const IncompleteTitle = styled('div')` display: grid; grid-template-columns: max-content 1fr; gap: ${space(1)}; align-items: center; font-weight: 600; `; const CompleteTitle = styled(IncompleteTitle)` grid-template-columns: min-content 1fr max-content min-content; `; const Description = styled('p')` font-size: ${p => p.theme.fontSizeSmall}; color: ${p => p.theme.subText}; margin: ${space(0.5)} 0 0 0; `; const ActionBar = styled('div')` display: flex; justify-content: flex-end; align-items: flex-end; margin-top: ${space(1.5)}; `; type InProgressIndicatorProps = React.HTMLAttributes & { user?: AvatarUser | null; }; const InProgressIndicator = styled(({user, ...props}: InProgressIndicatorProps) => (
{t('Task in progress...')}
))` font-size: ${p => p.theme.fontSizeMedium}; font-weight: bold; color: ${p => p.theme.pink300}; display: grid; grid-template-columns: max-content max-content; align-items: center; gap: ${space(1)}; `; const CloseButton = styled(Button)` position: absolute; right: ${space(1.5)}; top: ${space(1.5)}; color: ${p => p.theme.gray300}; `; const transition = testableTransition(); const StatusIndicator = styled(motion.div)` display: flex; `; StatusIndicator.defaultProps = { variants: { initial: {opacity: 0, x: 10}, animate: {opacity: 1, x: 0}, }, transition, }; const CompleteIndicator = styled(IconCheckmark)``; CompleteIndicator.defaultProps = { isCircled: true, color: 'green300', }; const SkippedIndicator = styled(IconClose)``; SkippedIndicator.defaultProps = { isCircled: true, color: 'pink300', }; const completedItemAnimation = { initial: {opacity: 0, x: -10}, animate: {opacity: 1, x: 0}, }; const DateCompleted = styled(motion.div)` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeSmall}; font-weight: 300; `; DateCompleted.defaultProps = { variants: completedItemAnimation, transition, }; const TaskUserAvatar = motion(Avatar); TaskUserAvatar.defaultProps = { variants: completedItemAnimation, transition, }; const TaskBlankAvatar = styled(motion(LetterAvatar))` position: unset; `; TaskBlankAvatar.defaultProps = { variants: completedItemAnimation, transition, }; const WrappedTask = withOrganization(Task); export default forwardRef< HTMLDivElement, Omit, 'forwardedRef'> >((props, ref) => );