• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CaptainFact / captain-fact-frontend / 12937903925

23 Jan 2025 08:56PM UTC coverage: 5.504% (-1.4%) from 6.953%
12937903925

push

github

web-flow
chore: Migrate to tailwind (#1355)

43 of 1647 branches covered (2.61%)

Branch coverage included in aggregate %.

6 of 306 new or added lines in 94 files covered. (1.96%)

94 existing lines in 39 files now uncovered.

192 of 2623 relevant lines covered (7.32%)

0.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/components/Videos/AddVideoForm.jsx
1
import { Formik } from 'formik'
2
import { Eye, EyeOff } from 'lucide-react'
3
import React from 'react'
4
import { withTranslation } from 'react-i18next'
5
import ReactPlayer from 'react-player'
6
import { connect } from 'react-redux'
7
import { withRouter } from 'react-router'
8

9
import { cn } from '@/lib/css-utils'
10

11
import { MIN_REPUTATION_ADD_UNLISTED_VIDEO, MIN_REPUTATION_ADD_VIDEO } from '../../constants'
12
import { facebookVideoRegex, youtubeRegex } from '../../lib/url_utils'
13
import { postVideo, searchVideo } from '../../state/videos/effects'
14
import FieldWithButton from '../FormUtils/FieldWithButton'
15
import { withLoggedInUser } from '../LoggedInUser/UserProvider'
16
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '../ui/card'
17
import { Switch } from '../ui/switch'
18
import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab'
19
import { LoadingFrame } from '../Utils/LoadingFrame'
20
import ReputationGuardTooltip from '../Utils/ReputationGuardTooltip'
21

UNCOV
22
const validate = ({ url }) => {
×
23
  if (!youtubeRegex.test(url) && !facebookVideoRegex.test(url)) {
×
24
    return {
×
25
      url: 'Invalid URL. Only YouTube and Facebook videos are currently supported',
26
    }
27
  }
28
  return {}
×
29
}
30

31
@withRouter
32
@withTranslation('main')
33
@connect(null, { postVideo, searchVideo })
34
@withLoggedInUser
35
export class AddVideoForm extends React.PureComponent {
36
  componentDidMount() {
37
    const searchParams = new URLSearchParams(location.search)
×
38
    const videoUrl = this.props.match.params.videoUrl || searchParams.get('videoUrl')
×
39
    if (videoUrl) {
×
40
      this.props.searchVideo(decodeURI(videoUrl)).then((action) => {
×
41
        if (!action.error && action.payload !== null) {
×
42
          this.props.history.push(`/videos/${action.payload.hash_id}`)
×
43
        }
44
      })
45
    }
46
  }
47

48
  renderVideo = (value, error) => {
×
49
    return error || !value ? (
×
50
      <div className="w-full aspect-video bg-gray-100">
51
        <div />
52
      </div>
53
    ) : (
54
      <ReactPlayer className="w-full aspect-video" url={value} controls />
55
    )
56
  }
57

58
  renderVideoAdvice() {
59
    const { t } = this.props
×
60
    return (
×
61
      <Card className="mb-4 mx-2 md:mx-auto w-full md:max-w-[600px] mt-32">
62
        <CardHeader>
63
          <CardTitle>{t('videos.adviceTitle')}</CardTitle>
64
        </CardHeader>
65
        <CardContent>
66
          <p>{t('videos.advice1')}</p>
67
          <ul className="list-disc pl-6 space-y-1 mb-5">
68
            <li>{t('videos.adviceBulletPoint1')}</li>
69
            <li>{t('videos.adviceBulletPoint2')}</li>
70
            <li>{t('videos.adviceBulletPoint3')}</li>
71
            <li>{t('videos.adviceBulletPoint4')}</li>
72
          </ul>
73
        </CardContent>
74
        <CardFooter>
75
          <ExternalLinkNewTab href="/help/contributionGuidelines">
76
            {t('videos.adviceReadMoreLink')}
77
          </ExternalLinkNewTab>
78
        </CardFooter>
79
      </Card>
80
    )
81
  }
82

83
  render() {
84
    const { t, match, location, history, isAuthenticated, loggedInUser } = this.props
×
85
    const searchParams = new URLSearchParams(location.search)
×
86
    const initialURL = match.params.videoUrl || searchParams.get('url') || ''
×
87
    return (
×
88
      <Formik
89
        initialValues={{
90
          url: initialURL,
91
          isPublicVideo:
92
            loggedInUser.reputation >= MIN_REPUTATION_ADD_UNLISTED_VIDEO ? true : false,
×
93
        }}
94
        validate={validate}
95
        onSubmit={({ url, isPublicVideo }, { setSubmitting }) => {
96
          setSubmitting(true)
×
97
          this.props.postVideo({ url, unlisted: !isPublicVideo }).then((action) => {
×
98
            setSubmitting(false)
×
99
            if (!action.error) {
×
100
              history.push(`/videos/${action.payload.hash_id}`)
×
101
            } else if (action.payload === 'unauthorized' && !isAuthenticated) {
×
102
              history.push('/login')
×
103
            }
104
          })
105
        }}
106
      >
107
        {({
108
          handleSubmit,
109
          handleChange,
110
          setFieldValue,
111
          handleBlur,
112
          values,
113
          errors,
114
          isSubmitting,
115
        }) => (
NEW
116
          <div className="grid grid-cols-1 md:grid-cols-4">
×
117
            <form
118
              className="md:col-span-1 bg-white border-r h-[--main-height]"
119
              onSubmit={handleSubmit}
120
            >
121
              {this.renderVideo(values.url, errors.url)}
122
              <div className="p-4">
123
                <FieldWithButton
124
                  input={{
125
                    onChange: handleChange,
126
                    onBlur: handleBlur,
127
                    name: 'url',
128
                    value: values.url,
129
                  }}
130
                  onChange={handleChange}
131
                  onBlur={handleBlur}
132
                  buttonLabel={this.props.t('videos.addThis')}
133
                  placeholder={this.props.t('videos.placeholder')}
134
                  meta={{ invalid: errors.url && values.url, submitting: isSubmitting }}
×
135
                  expandInput
136
                />
137
              </div>
138

139
              <ReputationGuardTooltip requiredRep={MIN_REPUTATION_ADD_VIDEO}>
140
                {({ hasReputation }) => (
NEW
141
                  <div className="flex items-center space-x-2 mb-4 pl-5">
×
142
                    <Switch
143
                      id="switch-isPublicVideo"
144
                      checked={values.isPublicVideo}
145
                      disabled={!hasReputation}
NEW
146
                      onCheckedChange={(checked) => setFieldValue('isPublicVideo', checked)}
×
147
                    />
148
                    <label className="min-w-28 cursor-pointer" htmlFor="switch-isPublicVideo">
149
                      {t(values.isPublicVideo ? 'videos.public' : 'videos.unlisted')}
×
150
                    </label>
151
                  </div>
152
                )}
153
              </ReputationGuardTooltip>
154

155
              <Card className="pt-4 mx-4 text-sm">
156
                <CardContent>
157
                  <div
158
                    className={cn('flex items-center', {
159
                      'font-bold': values.isPublicVideo,
160
                    })}
161
                  >
162
                    <Eye size={24} className="mr-4" />
163
                    <span>{t('videos.publicDescription')}</span>
164
                  </div>
165
                  <div
166
                    className={cn('flex items-center mt-4', {
167
                      'font-bold': !values.isPublicVideo,
168
                    })}
169
                  >
170
                    <EyeOff size={42} className="mr-4" />
171
                    <span>{t('videos.unlistedDescription')}</span>
172
                  </div>
173
                </CardContent>
174
              </Card>
175
            </form>
176

177
            <div className="md:col-span-3">
178
              {isSubmitting ? (
×
179
                <LoadingFrame title={this.props.t('videos.analysing')} />
180
              ) : (
181
                this.renderVideoAdvice()
182
              )}
183
            </div>
184
          </div>
185
        )}
186
      </Formik>
187
    )
188
  }
189
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc