{"version":3,"sources":["webpack:///./src/components/pages/contribution/listen/listen.tsx"],"names":["VoteButton","_a","kind","props","React","type","className","getTrackClass","join","ThumbsUpIcon","ThumbsDownIcon","Localized","id","initialState","clips","hasPlayed","hasPlayedSome","isPlaying","isSubmitted","ListenPage","constructor","audioRef","state","play","this","stop","current","setState","clearInterval","playedSomeInterval","setInterval","audio","pause","currentTime","trackListening","locale","vote","isValid","showFirstContributionToast","hasEarnedSessionToast","addAchievement","api","showFirstStreakToast","challengeEnded","clipIndex","getClipIndex","sessionStorage","setItem","JSON","stringify","parse","getItem","removeItem","setInviteContributeAchievement","SET_COUNT","map","clip","i","Object","voteYes","voteNo","handleSkip","removeClip","slice","reset","length","componentWillUnmount","findIndex","render","activeClip","src","audioSrc","preload","onEnded","ref","ContributionPage","activeIndex","errorContent","isLoading","LinkButton","rounded","to","URLS","SPEAK","MicIcon","instruction","elems","playIcon","OldPlayIcon","onReset","onSkip","primaryButtons","onClick","disabled","PlayButton","trackClass","pills","isVoted","isActive","Pill","status","VolumeIcon","CheckIcon","CrossIcon","reportModalProps","reasons","sentences","sentence","shortcuts","key","label","action","mapDispatchToProps","Clips","actions","remove","Notifications","connect","selectors","localeClips"],"mappings":"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","file":"15.js?id=71afccca260adb901b8a","sourcesContent":["import { Localized } from '@fluent/react';\nimport * as React from 'react';\nimport { connect } from 'react-redux';\nimport { Clip as ClipType } from 'common';\nimport { trackListening, getTrackClass } from '../../../../services/tracker';\nimport { Clips } from '../../../../stores/clips';\nimport { Locale } from '../../../../stores/locale';\nimport StateTree from '../../../../stores/tree';\nimport API from '../../../../services/api';\nimport URLS from '../../../../urls';\nimport {\n  CheckIcon,\n  CrossIcon,\n  MicIcon,\n  OldPlayIcon,\n  ThumbsDownIcon,\n  ThumbsUpIcon,\n  VolumeIcon,\n} from '../../../ui/icons';\nimport { LinkButton } from '../../../ui/ui';\nimport ContributionPage, {\n  ContributionPillProps,\n  SET_COUNT,\n} from '../contribution';\nimport { Notifications } from '../../../../stores/notifications';\nimport { PlayButton } from '../../../primary-buttons/primary-buttons';\nimport Pill from '../pill';\n\nimport './listen.css';\nimport { User } from '@sentry/types';\n\nconst VOTE_NO_PLAY_MS = 3000; // Threshold when to allow voting no\n\nconst VoteButton = ({\n  kind,\n  ...props\n}: { kind: 'yes' | 'no' } & React.ButtonHTMLAttributes<any>) => (\n  <button\n    type=\"button\"\n    className={['vote-button', kind, getTrackClass('fs', `vote-${kind}`)].join(\n      ' '\n    )}\n    {...props}>\n    {kind === 'yes' && <ThumbsUpIcon />}\n    {kind === 'no' && <ThumbsDownIcon />}\n    <Localized id={'vote-' + kind}>\n      <span />\n    </Localized>\n  </button>\n);\n\ninterface PropsFromState {\n  api: API;\n  clips: ClipType[];\n  isLoading: boolean;\n  locale: Locale.State;\n  showFirstContributionToast: boolean;\n  hasEarnedSessionToast: boolean;\n  showFirstStreakToast: boolean;\n  challengeEnded: boolean;\n}\n\ninterface PropsFromDispatch {\n  removeClip: typeof Clips.actions.remove;\n  vote: typeof Clips.actions.vote;\n  addAchievement: typeof Notifications.actions.addAchievement;\n}\n\ninterface Props extends PropsFromState, PropsFromDispatch {}\n\ninterface State {\n  clips: (ClipType & { isValid?: boolean })[];\n  hasPlayed: boolean;\n  hasPlayedSome: boolean;\n  isPlaying: boolean;\n  isSubmitted: boolean;\n}\n\nconst initialState: State = {\n  clips: [],\n  hasPlayed: false,\n  hasPlayedSome: false,\n  isPlaying: false,\n  isSubmitted: false,\n};\n\nclass ListenPage extends React.Component<Props, State> {\n  audioRef = React.createRef<HTMLAudioElement>();\n  playedSomeInterval: any;\n\n  state: State = initialState;\n\n  static getDerivedStateFromProps(props: Props, state: State) {\n    if (state.clips.length > 0) return null;\n\n    if (props.clips.length > 0) {\n      return {\n        clips: props.clips\n          .slice(0, SET_COUNT)\n          .map(clip => ({ ...clip, isValid: null })),\n      };\n    }\n\n    return null;\n  }\n\n  componentWillUnmount() {\n    clearInterval(this.playedSomeInterval);\n    // this.audioPlayer.close();\n  }\n\n  private getClipIndex() {\n    return this.state.clips.findIndex(clip => clip.isValid === null);\n  }\n\n  private play = () => {\n    if (this.state.isPlaying) {\n      this.stop();\n      return;\n    }\n\n    this.audioRef.current.play();\n    this.setState({ isPlaying: true });\n    clearInterval(this.playedSomeInterval);\n    this.playedSomeInterval = setInterval(\n      () => this.setState({ hasPlayedSome: true }),\n      VOTE_NO_PLAY_MS\n    );\n  };\n\n  private stop = () => {\n    const audio = this.audioRef.current;\n    audio.pause();\n    audio.currentTime = 0;\n    clearInterval(this.playedSomeInterval);\n    this.setState({ isPlaying: false });\n  };\n\n  private hasPlayed = () => {\n    this.setState({ hasPlayed: true, isPlaying: false });\n    trackListening('listen', this.props.locale);\n  };\n\n  private vote = (isValid: boolean) => {\n    const { clips } = this.state;\n\n    const {\n      showFirstContributionToast,\n      hasEarnedSessionToast,\n      addAchievement,\n      api,\n      showFirstStreakToast,\n      challengeEnded,\n    } = this.props;\n    const clipIndex = this.getClipIndex();\n\n    this.stop();\n    this.props.vote(isValid, this.state.clips[this.getClipIndex()].id);\n\n    sessionStorage.setItem('challengeEnded', JSON.stringify(challengeEnded));\n    sessionStorage.setItem('hasContributed', 'true');\n\n    if (showFirstContributionToast) {\n      addAchievement(\n        50,\n        \"You're on your way! Congrats on your first contribution.\",\n        'success'\n      );\n    }\n    if (showFirstStreakToast) {\n      addAchievement(\n        50,\n        'You completed a three-day streak! Keep it up.',\n        'success'\n      );\n    }\n    if (\n      !JSON.parse(sessionStorage.getItem('challengeEnded')) &&\n      JSON.parse(sessionStorage.getItem('hasShared')) &&\n      !hasEarnedSessionToast\n    ) {\n      addAchievement(\n        50,\n        \"You're on a roll! You sent an invite and contributed in the same session.\",\n        'success'\n      );\n      sessionStorage.removeItem('hasShared');\n      // Tell back-end user get unexpected achievement: invite + contribute in the same session\n      // Each user can only get once.\n      api.setInviteContributeAchievement();\n    }\n    this.setState({\n      hasPlayed: false,\n      hasPlayedSome: false,\n      isPlaying: false,\n      isSubmitted: clipIndex === SET_COUNT - 1,\n      clips: clips.map((clip, i) =>\n        i === clipIndex ? { ...clip, isValid } : clip\n      ),\n    });\n  };\n\n  private voteYes = () => {\n    if (!this.state.hasPlayed) {\n      return;\n    }\n    this.vote(true);\n    trackListening('vote-yes', this.props.locale);\n  };\n\n  private voteNo = () => {\n    const { hasPlayed, hasPlayedSome } = this.state;\n    if (!hasPlayed && !hasPlayedSome) {\n      return;\n    }\n    this.vote(false);\n    trackListening('vote-no', this.props.locale);\n  };\n\n  private handleSkip = () => {\n    const { removeClip } = this.props;\n    const { clips } = this.state;\n    this.stop();\n    removeClip(clips[this.getClipIndex()].id);\n    this.setState({\n      clips: clips.map((clip, i) =>\n        this.getClipIndex() === i\n          ? { ...this.props.clips.slice(SET_COUNT)[0], isValid: null }\n          : clip\n      ),\n      hasPlayed: false,\n      hasPlayedSome: false,\n    });\n  };\n\n  private reset = () => this.setState(initialState);\n\n  render() {\n    const {\n      clips,\n      hasPlayed,\n      hasPlayedSome,\n      isPlaying,\n      isSubmitted,\n    } = this.state;\n    const clipIndex = this.getClipIndex();\n    const activeClip = clips[clipIndex];\n    return (\n      <>\n        <audio\n          {...(activeClip && { src: activeClip.audioSrc })}\n          preload=\"auto\"\n          onEnded={this.hasPlayed}\n          ref={this.audioRef}\n        />\n        <ContributionPage\n          activeIndex={clipIndex}\n          errorContent={\n            !this.props.isLoading &&\n            (clips.length === 0 || !activeClip) && (\n              <div className=\"empty-container\">\n                <div className=\"error-card card-dimensions\">\n                  <Localized id=\"listen-empty-state\">\n                    <span />\n                  </Localized>\n                  <LinkButton\n                    rounded\n                    to={URLS.SPEAK}\n                    className=\"record-instead\">\n                    <MicIcon />{' '}\n                    <Localized id=\"record-button-label\">\n                      <span />\n                    </Localized>\n                  </LinkButton>\n                </div>\n              </div>\n            )\n          }\n          instruction={props =>\n            activeClip &&\n            !isPlaying &&\n            !hasPlayedSome &&\n            !hasPlayed && (\n              <Localized\n                id={\n                  clipIndex === SET_COUNT - 1\n                    ? 'listen-last-time-instruction'\n                    : [\n                        'listen-instruction',\n                        'listen-again-instruction',\n                        'listen-3rd-time-instruction',\n                      ][clipIndex] || 'listen-again-instruction'\n                }\n                elems={{ playIcon: <OldPlayIcon /> }}\n                {...props}\n              />\n            )\n          }\n          isPlaying={isPlaying}\n          isSubmitted={isSubmitted}\n          onReset={this.reset}\n          onSkip={this.handleSkip}\n          primaryButtons={\n            <>\n              <VoteButton\n                kind=\"yes\"\n                onClick={this.voteYes}\n                disabled={!hasPlayed}\n              />\n              <PlayButton\n                isPlaying={isPlaying}\n                onClick={this.play}\n                trackClass=\"play-clip\"\n              />\n              <VoteButton\n                kind=\"no\"\n                onClick={this.voteNo}\n                disabled={!hasPlayed && !hasPlayedSome}\n              />\n            </>\n          }\n          pills={clips.map(\n            ({ isValid }, i) => (props: ContributionPillProps) => {\n              const isVoted = isValid !== null;\n              const isActive = clipIndex === i;\n              return (\n                <Pill\n                  className={isVoted ? (isValid ? 'valid' : 'invalid') : ''}\n                  onClick={null}\n                  status={isActive ? 'active' : isVoted ? 'done' : 'pending'}\n                  {...props}>\n                  {isActive ? (\n                    <VolumeIcon />\n                  ) : isVoted ? (\n                    isValid ? (\n                      <CheckIcon />\n                    ) : (\n                      <CrossIcon />\n                    )\n                  ) : null}\n                </Pill>\n              );\n            }\n          )}\n          reportModalProps={{\n            reasons: [\n              'offensive-speech',\n              'grammar-or-spelling',\n              'different-language',\n            ],\n            kind: 'clip',\n            id: activeClip ? activeClip.id : null,\n          }}\n          sentences={clips.map(clip => clip.sentence)}\n          shortcuts={[\n            {\n              key: 'shortcut-play-toggle',\n              label: 'shortcut-play-toggle-label',\n              action: this.play,\n            },\n            {\n              key: 'shortcut-vote-yes',\n              label: 'vote-yes',\n              action: this.voteYes,\n            },\n            {\n              key: 'shortcut-vote-no',\n              label: 'vote-no',\n              action: this.voteNo,\n            },\n          ]}\n          type=\"listen\"\n        />\n      </>\n    );\n  }\n}\n\nconst mapStateToProps = (state: StateTree) => {\n  const {\n    clips,\n    isLoading,\n    showFirstContributionToast,\n    hasEarnedSessionToast,\n    showFirstStreakToast,\n    challengeEnded,\n  } = Clips.selectors.localeClips(state);\n  const { api } = state;\n  return {\n    clips,\n    isLoading,\n    showFirstContributionToast,\n    hasEarnedSessionToast,\n    showFirstStreakToast,\n    challengeEnded,\n    api,\n    locale: state.locale,\n  };\n};\n\nconst mapDispatchToProps = {\n  removeClip: Clips.actions.remove,\n  vote: Clips.actions.vote,\n  addAchievement: Notifications.actions.addAchievement,\n};\n\nexport default connect<PropsFromState, any>(\n  mapStateToProps,\n  mapDispatchToProps\n)(ListenPage);\n"],"sourceRoot":""}