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

CaptainFact / captain-fact-frontend / 20411409289

21 Dec 2025 02:42PM UTC coverage: 1.46% (-1.8%) from 3.249%
20411409289

push

github

Betree
iterate

26 of 1952 branches covered (1.33%)

Branch coverage included in aggregate %.

1 of 8 new or added lines in 4 files covered. (12.5%)

528 existing lines in 32 files now uncovered.

39 of 2500 relevant lines covered (1.56%)

0.07 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 { useMutation } from '@apollo/client'
2
import { Formik } from 'formik'
3
import { Eye, EyeOff } from 'lucide-react'
4
import React, { useEffect } from 'react'
5
import { useTranslation } from 'react-i18next'
6
import ReactPlayer from 'react-player'
7
import { useHistory, useParams } from 'react-router-dom'
8

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

11
import { CREATE_VIDEO_MUTATION, SEARCH_VIDEO_QUERY } from '../../API/graphql_queries'
12
import { MIN_REPUTATION_ADD_UNLISTED_VIDEO, MIN_REPUTATION_ADD_VIDEO } from '../../constants'
13
import { facebookVideoRegex, youtubeRegex } from '../../lib/url_utils'
14
import { useLoggedInUser } from '../LoggedInUser/UserProvider'
15
import { Button } from '../ui/button'
16
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '../ui/card'
17
import { Input } from '../ui/input'
18
import { Switch } from '../ui/switch'
19
import ExternalLinkNewTab from '../Utils/ExternalLinkNewTab'
20
import { LoadingFrame } from '../Utils/LoadingFrame'
21
import ReputationGuardTooltip from '../Utils/ReputationGuardTooltip'
22

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

UNCOV
32
const renderVideo = (value, error) => {
×
UNCOV
33
  return error || !value ? (
×
34
    <div className="w-full aspect-video bg-gray-100">
35
      <div />
36
    </div>
37
  ) : (
38
    <ReactPlayer className="w-full aspect-video" url={value} controls />
39
  )
40
}
41

42
const renderVideoAdvice = (t) => {
×
UNCOV
43
  return (
×
44
    <Card className="mb-4 mx-2 md:mx-auto w-full md:max-w-[600px] mt-32">
45
      <CardHeader>
46
        <CardTitle>{t('videos.adviceTitle')}</CardTitle>
47
      </CardHeader>
48
      <CardContent>
49
        <p>{t('videos.advice1')}</p>
50
        <ul className="list-disc pl-6 space-y-1 mb-5">
51
          <li>{t('videos.adviceBulletPoint1')}</li>
52
          <li>{t('videos.adviceBulletPoint2')}</li>
53
          <li>{t('videos.adviceBulletPoint3')}</li>
54
          <li>{t('videos.adviceBulletPoint4')}</li>
55
        </ul>
56
      </CardContent>
57
      <CardFooter>
58
        <ExternalLinkNewTab href="/help/contributionGuidelines">
59
          {t('videos.adviceReadMoreLink')}
60
        </ExternalLinkNewTab>
61
      </CardFooter>
62
    </Card>
63
  )
64
}
65

UNCOV
66
const AddVideoForm = () => {
×
UNCOV
67
  const { t } = useTranslation('main')
×
UNCOV
68
  const history = useHistory()
×
UNCOV
69
  const { videoUrl, url: initialURL } = useParams()
×
UNCOV
70
  const { isAuthenticated, loggedInUser } = useLoggedInUser()
×
71

UNCOV
72
  const [createVideo, { loading: creatingVideo }] = useMutation(CREATE_VIDEO_MUTATION)
×
UNCOV
73
  const [searchVideoQuery] = useMutation(SEARCH_VIDEO_QUERY)
×
74

UNCOV
75
  useEffect(() => {
×
UNCOV
76
    if (videoUrl) {
×
UNCOV
77
      searchVideoQuery({
×
78
        variables: { url: decodeURI(videoUrl) },
79
      })
80
        .then((result) => {
UNCOV
81
          if (result.data?.searchVideo) {
×
UNCOV
82
            history.push(`/videos/${result.data.searchVideo.hashId}`)
×
83
          }
84
        })
85
        .catch((error) => {
86
          console.error('Error searching video:', error)
×
87
        })
88
    }
89
  }, [videoUrl, searchVideoQuery, history])
90

UNCOV
91
  return (
×
92
    <Formik
93
      initialValues={{
94
        url: initialURL,
95
        isPublicVideo: loggedInUser.reputation >= MIN_REPUTATION_ADD_UNLISTED_VIDEO ? true : false,
×
96
      }}
97
      validate={validate}
98
      onSubmit={async ({ url, isPublicVideo }, { setSubmitting }) => {
99
        setSubmitting(true)
×
100
        try {
×
101
          const result = await createVideo({
×
102
            variables: { url, unlisted: !isPublicVideo },
103
          })
UNCOV
104
          if (result.data?.createVideo) {
×
UNCOV
105
            history.push(`/videos/${result.data.createVideo.hashId}`)
×
106
          }
107
        } catch (error) {
UNCOV
108
          console.error('Error creating video:', error)
×
UNCOV
109
          if (error.message?.includes('unauthorized') && !isAuthenticated) {
×
UNCOV
110
            history.push('/login')
×
111
          }
112
        } finally {
UNCOV
113
          setSubmitting(false)
×
114
        }
115
      }}
116
    >
117
      {({
118
        handleSubmit,
119
        handleChange,
120
        setFieldValue,
121
        handleBlur,
122
        values,
123
        errors,
124
        isSubmitting,
125
      }) => (
UNCOV
126
        <div className="grid grid-cols-1 md:grid-cols-4">
×
127
          <form
128
            className="md:col-span-1 bg-white border-r h-[--main-height]"
129
            onSubmit={handleSubmit}
130
          >
131
            {renderVideo(values.url, errors.url)}
132
            <div className="p-4">
133
              <div className="flex items-center">
134
                <Input
135
                  name="url"
136
                  value={values.url}
137
                  onChange={handleChange}
138
                  onBlur={handleBlur}
139
                  placeholder={t('videos.placeholder')}
140
                  className={cn('rounded-tr-none rounded-br-none', {
141
                    'border-red-500': errors.url && values.url,
×
142
                  })}
143
                />
144
                <Button
145
                  type="submit"
146
                  disabled={isSubmitting || (errors.url && values.url)}
×
147
                  className="rounded-tl-none rounded-bl-none whitespace-nowrap"
148
                  loading={isSubmitting}
149
                >
150
                  {t('videos.addThis')}
151
                </Button>
152
              </div>
153
            </div>
154

155
            <ReputationGuardTooltip requiredRep={MIN_REPUTATION_ADD_VIDEO}>
156
              {({ hasReputation }) => (
UNCOV
157
                <div className="flex items-center space-x-2 mb-4 pl-5">
×
158
                  <Switch
159
                    id="switch-isPublicVideo"
160
                    checked={values.isPublicVideo}
161
                    disabled={!hasReputation}
UNCOV
162
                    onCheckedChange={(checked) => setFieldValue('isPublicVideo', checked)}
×
163
                  />
164
                  <label className="min-w-28 cursor-pointer" htmlFor="switch-isPublicVideo">
165
                    {t(values.isPublicVideo ? 'videos.public' : 'videos.unlisted')}
×
166
                  </label>
167
                </div>
168
              )}
169
            </ReputationGuardTooltip>
170

171
            <Card className="pt-4 mx-4 text-sm">
172
              <CardContent>
173
                <div
174
                  className={cn('flex items-center', {
175
                    'font-bold': values.isPublicVideo,
176
                  })}
177
                >
178
                  <Eye size={24} className="mr-4" />
179
                  <span>{t('videos.publicDescription')}</span>
180
                </div>
181
                <div
182
                  className={cn('flex items-center mt-4', {
183
                    'font-bold': !values.isPublicVideo,
184
                  })}
185
                >
186
                  <EyeOff size={42} className="mr-4" />
187
                  <span>{t('videos.unlistedDescription')}</span>
188
                </div>
189
              </CardContent>
190
            </Card>
191
          </form>
192

193
          <div className="md:col-span-3">
194
            {isSubmitting ? <LoadingFrame title={t('videos.analysing')} /> : renderVideoAdvice(t)}
×
195
          </div>
196
        </div>
197
      )}
198
    </Formik>
199
  )
200
}
201

202
export default AddVideoForm
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