Skip to content

feat: add YouTube URL input to media screen video section (NES-1369)#8799

Open
jaco-brink wants to merge 2 commits intomainfrom
jacobusbrink/nes-1369-youtube-video-upload
Open

feat: add YouTube URL input to media screen video section (NES-1369)#8799
jaco-brink wants to merge 2 commits intomainfrom
jacobusbrink/nes-1369-youtube-video-upload

Conversation

@jaco-brink
Copy link
Contributor

@jaco-brink jaco-brink commented Mar 3, 2026

Summary

  • Adds a YouTube URL input field below the custom file upload button in VideosSection
  • URL is validated using the existing YouTube video ID regex; extracted 11-char ID is passed to VIDEO_BLOCK_UPDATE with source: youTube
  • Introduces useYouTubeVideoLinking hook that sets status: 'updating' during the mutation, keeping hasActiveUploads: true — this blocks the custom upload button and the "Next" navigation button until the link is complete
  • Adds extractYouTubeVideoId utility to videoSectionUtils and updates barrel exports

Test plan

  • Paste a valid YouTube URL (e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ) and click Set — video player should update with the YouTube video
  • Paste a youtu.be short link and confirm it works
  • Paste an invalid URL and confirm the inline error message appears
  • Confirm the upload button and Set button are both disabled while a YouTube link is being set
  • Confirm the "Next" button is disabled while a YouTube link is being set
  • Press Enter in the URL field to submit — confirm it behaves the same as clicking Set

Closes NES-1369

Made with Cursor

Summary by CodeRabbit

New Features

  • Users can now upload videos using YouTube URLs in template customization. A new YouTube URL input field has been added with a Set button, allowing direct linking to YouTube videos as an alternative to uploading video files. The system validates YouTube links and extracts video information automatically.

Adds a YouTube URL input below the custom file upload button in the
media screen's VideosSection. Users can paste a YouTube URL, which is
validated and linked to the selected card's video block via the existing
VIDEO_BLOCK_UPDATE mutation. Loading states reuse the same 'updating'
status as Mux uploads, preventing the custom upload button from being
clicked and blocking navigation via the Next button until complete.

Made-with: Cursor
@linear
Copy link

linear bot commented Mar 3, 2026

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

Walkthrough

This pull request adds YouTube video linking support to the template customization media workflow. It includes a YouTube video ID extraction utility, a new React hook for persisting YouTube videos via GraphQL mutation, updates to the TemplateVideoUploadContext to expose YouTube linking functionality, and UI enhancements to the VideosSection component to accept and validate YouTube URLs.

Changes

Cohort / File(s) Summary
YouTube Video ID Extraction Utility
apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/videoSectionUtils/videoSectionUtils.ts, videoSectionUtils/index.ts, utils/index.ts
Implements extractYouTubeVideoId function using regex to extract the 11-character YouTube video ID from a URL. The utility is exported through two barrel files for consumption by other modules.
YouTube Video Linking Hook and Context
apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/TemplateVideoUploadProvider/useYouTubeVideoLinking.ts, types.ts, TemplateVideoUploadProvider.tsx
Introduces useYouTubeVideoLinking hook that executes a VIDEO_BLOCK_UPDATE GraphQL mutation to persist YouTube videos, manages upload state tracking, displays toast notifications, and refetches journey data. Adds startYouTubeLink method to TemplateVideoUploadContextType and integrates the hook into the provider's context value.
VideosSection UI Enhancement
apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx
Adds YouTube URL input field with validation, error handling, and a Set button. Implements handleYouTubeSubmit and handleYouTubeKeyDown to validate URLs, extract video IDs, and trigger startYouTubeLink from the upload hook. New UI block is positioned after existing file upload with a Divider and "or" label.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant VideosSection
    participant VideoIDUtils
    participant TemplateVideoUploadContext
    participant useYouTubeVideoLinking
    participant ApolloMutation as Apollo (VIDEO_BLOCK_UPDATE)
    participant Notistack as Toast Notifications

    User->>VideosSection: Enter YouTube URL & click Set
    VideosSection->>VideosSection: handleYouTubeSubmit
    VideosSection->>VideoIDUtils: extractYouTubeVideoId(url)
    VideoIDUtils-->>VideosSection: youtubeVideoId
    VideosSection->>TemplateVideoUploadContext: startYouTubeLink(videoBlockId, youtubeVideoId)
    TemplateVideoUploadContext->>useYouTubeVideoLinking: linkYouTubeVideo(videoBlockId, youtubeVideoId)
    useYouTubeVideoLinking->>useYouTubeVideoLinking: Mark block as updating
    useYouTubeVideoLinking->>ApolloMutation: Execute VIDEO_BLOCK_UPDATE
    ApolloMutation-->>useYouTubeVideoLinking: Success/Error response
    useYouTubeVideoLinking->>useYouTubeVideoLinking: Refetch GET_JOURNEY
    alt Success
        useYouTubeVideoLinking->>Notistack: Show success toast
        useYouTubeVideoLinking->>useYouTubeVideoLinking: Update upload task state
    else Error
        useYouTubeVideoLinking->>Notistack: Show error toast
        useYouTubeVideoLinking->>useYouTubeVideoLinking: Update task with error status
    end
    useYouTubeVideoLinking->>useYouTubeVideoLinking: Clean up active block marker
    useYouTubeVideoLinking-->>VideosSection: Promise resolved
    VideosSection->>VideosSection: Clear input on success
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding YouTube URL input capability to the video section component, which aligns with the changeset that introduces YouTube linking functionality throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jacobusbrink/nes-1369-youtube-video-upload

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Mar 3, 2026

View your CI Pipeline Execution ↗ for commit 1b33bbb

Command Status Duration Result
nx run journeys-admin-e2e:e2e ✅ Succeeded 32s View ↗
nx run-many --target=vercel-alias --projects=jo... ✅ Succeeded 2s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 9s View ↗
nx run-many --target=deploy --projects=journeys... ✅ Succeeded 2m 51s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-05 20:37:26 UTC

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Fails
🚫 Please assign someone to merge this PR.

Generated by 🚫 dangerJS against 1b33bbb

@github-actions github-actions bot temporarily deployed to Preview - journeys-admin March 3, 2026 01:30 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx (1)

209-223: Use named handlers for input/button events and clear stale validation while typing.

Line 209 and Line 222 use inline event lambdas. Extract named handlers (e.g., handleYouTubeUrlChange, handleSetYouTubeClick) and clear youtubeUrlError inside the change handler so the error updates immediately as users edit.

♻️ Suggested refactor
-import { KeyboardEvent, ReactElement, useState } from 'react'
+import { ChangeEvent, KeyboardEvent, ReactElement, useState } from 'react'
@@
+  function handleYouTubeUrlChange(
+    event: ChangeEvent<HTMLInputElement>
+  ): void {
+    setYoutubeUrl(event.target.value)
+    if (youtubeUrlError != null) setYoutubeUrlError(undefined)
+  }
+
+  function handleSetYouTubeClick(): void {
+    void handleYouTubeSubmit()
+  }
+
@@
-            onChange={(e) => setYoutubeUrl(e.target.value)}
+            onChange={handleYouTubeUrlChange}
@@
-            onClick={() => void handleYouTubeSubmit()}
+            onClick={handleSetYouTubeClick}

As per coding guidelines, "event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx`
around lines 209 - 223, Extract inline lambdas into named handlers: add
handleYouTubeUrlChange that accepts the input event, calls
setYoutubeUrl(e.target.value) and clears youtubeUrlError (e.g.,
setYoutubeUrlError(null)) so validation clears while typing; replace
onChange={(e) => setYoutubeUrl(...)} with onChange={handleYouTubeUrlChange}. Add
handleSetYouTubeClick that calls void handleYouTubeSubmit() and use it for
onClick instead of the inline arrow; keep handleYouTubeKeyDown as the existing
key handler. Ensure the Button disabled prop still uses youtubeUrl.trim() === ''
and that the new handlers reference the existing state setters (setYoutubeUrl,
setYoutubeUrlError) and submit function (handleYouTubeSubmit).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx`:
- Around line 150-153: The code clears youtubeUrl (via setYoutubeUrl('')) before
awaiting the async startYouTubeLink call, which discards the user's pasted URL
if the mutation fails; change the flow in the handler that currently calls
setYoutubeUrlError(undefined); setYoutubeUrl(''); await
startYouTubeLink(videoBlock.id, extractedId) to instead clear the url only after
startYouTubeLink resolves successfully—e.g., call setYoutubeUrlError(undefined)
then await startYouTubeLink(videoBlock.id, extractedId) inside a try block and
only call setYoutubeUrl('') in the success path, and catch errors to set an
appropriate error via setYoutubeUrlError without clearing the input so the
user’s URL is preserved.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/videoSectionUtils/videoSectionUtils.ts`:
- Around line 8-17: The extractYouTubeVideoId function currently uses
YOUTUBE_ID_REGEX against any string and can return false positives from
non-YouTube hosts; update extractYouTubeVideoId to first parse the input with
the URL constructor (catching invalid URLs) and verify the hostname belongs to
an allowed YouTube host (e.g., endsWith "youtube.com", equals "youtu.be", or
includes "youtube-nocookie.com" and handle "shorts."), and only then apply
YOUTUBE_ID_REGEX (or extract from pathname/search) to get the 11-character ID;
ensure non-YouTube hosts or parse failures return null and keep YOUTUBE_ID_REGEX
as the extraction pattern reference.

---

Nitpick comments:
In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx`:
- Around line 209-223: Extract inline lambdas into named handlers: add
handleYouTubeUrlChange that accepts the input event, calls
setYoutubeUrl(e.target.value) and clears youtubeUrlError (e.g.,
setYoutubeUrlError(null)) so validation clears while typing; replace
onChange={(e) => setYoutubeUrl(...)} with onChange={handleYouTubeUrlChange}. Add
handleSetYouTubeClick that calls void handleYouTubeSubmit() and use it for
onClick instead of the inline arrow; keep handleYouTubeKeyDown as the existing
key handler. Ensure the Button disabled prop still uses youtubeUrl.trim() === ''
and that the new handlers reference the existing state setters (setYoutubeUrl,
setYoutubeUrlError) and submit function (handleYouTubeSubmit).

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1eda0ca and f621c18.

📒 Files selected for processing (7)
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/index.ts
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/videoSectionUtils/index.ts
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/videoSectionUtils/videoSectionUtils.ts
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/TemplateVideoUploadProvider/TemplateVideoUploadProvider.tsx
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/TemplateVideoUploadProvider/types.ts
  • apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/TemplateVideoUploadProvider/useYouTubeVideoLinking.ts

Comment on lines +150 to +153
setYoutubeUrlError(undefined)
setYoutubeUrl('')
await startYouTubeLink(videoBlock.id, extractedId)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Preserve the URL until linking succeeds.

Line 151 clears youtubeUrl before the async link attempt completes. If the mutation fails, users lose the pasted URL and need to re-enter it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/Sections/VideosSection/VideosSection.tsx`
around lines 150 - 153, The code clears youtubeUrl (via setYoutubeUrl(''))
before awaiting the async startYouTubeLink call, which discards the user's
pasted URL if the mutation fails; change the flow in the handler that currently
calls setYoutubeUrlError(undefined); setYoutubeUrl(''); await
startYouTubeLink(videoBlock.id, extractedId) to instead clear the url only after
startYouTubeLink resolves successfully—e.g., call setYoutubeUrlError(undefined)
then await startYouTubeLink(videoBlock.id, extractedId) inside a try block and
only call setYoutubeUrl('') in the success path, and catch errors to set an
appropriate error via setYoutubeUrlError without clearing the input so the
user’s URL is preserved.

Comment on lines +8 to +17
const YOUTUBE_ID_REGEX = /(\/|%3D|vi=|v=)([0-9A-Za-z-_]{11})([%#?&/]|$)/

/**
* Extracts an 11-character YouTube video ID from a URL.
*
* Supports standard watch URLs, youtu.be short links, shorts, and embed URLs.
* Returns null when no valid ID can be found.
*/
export function extractYouTubeVideoId(url: string): string | null {
return url.match(YOUTUBE_ID_REGEX)?.[2] ?? null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restrict extraction to YouTube hosts to avoid false positives.

Line 8 currently matches IDs from non-YouTube URLs (e.g., any domain containing /XXXXXXXXXXX or v=XXXXXXXXXXX). That means invalid links can pass validation and be submitted as YouTube videos.

🔧 Proposed fix
-const YOUTUBE_ID_REGEX = /(\/|%3D|vi=|v=)([0-9A-Za-z-_]{11})([%#?&/]|$)/
+const YOUTUBE_ID_REGEX = /^[0-9A-Za-z_-]{11}$/
+const YOUTUBE_HOST_REGEX = /(^|\.)youtube\.com$|(^|\.)youtu\.be$/

 export function extractYouTubeVideoId(url: string): string | null {
-  return url.match(YOUTUBE_ID_REGEX)?.[2] ?? null
+  try {
+    const parsed = new URL(url.trim())
+    const host = parsed.hostname.toLowerCase()
+    if (!YOUTUBE_HOST_REGEX.test(host)) return null
+
+    const candidate =
+      host.endsWith('youtu.be')
+        ? parsed.pathname.split('/').filter(Boolean)[0]
+        : parsed.searchParams.get('v') ??
+          parsed.pathname.split('/').filter(Boolean).at(-1)
+
+    return candidate != null && YOUTUBE_ID_REGEX.test(candidate)
+      ? candidate
+      : null
+  } catch {
+    return null
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/journeys-admin/src/components/TemplateCustomization/MultiStepForm/Screens/MediaScreen/utils/videoSectionUtils/videoSectionUtils.ts`
around lines 8 - 17, The extractYouTubeVideoId function currently uses
YOUTUBE_ID_REGEX against any string and can return false positives from
non-YouTube hosts; update extractYouTubeVideoId to first parse the input with
the URL constructor (catching invalid URLs) and verify the hostname belongs to
an allowed YouTube host (e.g., endsWith "youtube.com", equals "youtu.be", or
includes "youtube-nocookie.com" and handle "shorts."), and only then apply
YOUTUBE_ID_REGEX (or extract from pathname/search) to get the 11-character ID;
ensure non-YouTube hosts or parse failures return null and keep YOUTUBE_ID_REGEX
as the extraction pattern reference.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

The latest updates on your projects.

Name Status Preview Updated (UTC)
journeys-admin ✅ Ready journeys-admin preview Fri Mar 6 09:30:35 NZDT 2026

@github-actions github-actions bot temporarily deployed to Preview - journeys-admin March 5, 2026 20:25 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant