diff --git a/__tests__/components/header/AppHeader.test.tsx b/__tests__/components/header/AppHeader.test.tsx index 8a5ec06542..7674e5a158 100644 --- a/__tests__/components/header/AppHeader.test.tsx +++ b/__tests__/components/header/AppHeader.test.tsx @@ -40,6 +40,9 @@ jest.mock("@/components/header/HeaderActionButtons", () => ({ jest.mock("@/contexts/NavigationHistoryContext", () => ({ useNavigationHistoryContext: jest.fn(), })); +jest.mock("@/components/ipfs/IPFSContext", () => ({ + resolveIpfsUrlSync: (url: string) => url, +})); const { useSeizeConnectContext, diff --git a/__tests__/components/waves/drop/WaveDropAdditionalInfo.test.tsx b/__tests__/components/waves/drop/WaveDropAdditionalInfo.test.tsx index 92330bc1db..785f490a69 100644 --- a/__tests__/components/waves/drop/WaveDropAdditionalInfo.test.tsx +++ b/__tests__/components/waves/drop/WaveDropAdditionalInfo.test.tsx @@ -31,7 +31,7 @@ describe("WaveDropAdditionalInfo", () => { /> ); - expect(screen.getByText("Process")).toBeInTheDocument(); + expect(screen.getByText("Artwork Commentary")).toBeInTheDocument(); expect(screen.getByText("Process notes here.")).toBeInTheDocument(); }); @@ -58,7 +58,50 @@ describe("WaveDropAdditionalInfo", () => { /> ); - expect(screen.getByText("Process")).toBeInTheDocument(); + expect(screen.getByText("Additional Media")).toBeInTheDocument(); expect(screen.getAllByRole("img")).toHaveLength(4); }); + + it("renders promo video when provided", () => { + const additionalMedia = JSON.stringify({ + artist_profile_media: [], + artwork_commentary_media: [], + preview_image: "", + promo_video: "https://example.com/promo.mp4", + }); + + render( + + ); + + expect(screen.getByText("Promo Video")).toBeInTheDocument(); + }); + + it("does not render promo video section when not provided", () => { + const additionalMedia = JSON.stringify({ + artist_profile_media: [], + artwork_commentary_media: [], + preview_image: "https://example.com/preview.jpg", + }); + + render( + + ); + + expect(screen.queryByText("Promo Video")).not.toBeInTheDocument(); + }); }); diff --git a/__tests__/components/waves/memes/submission/components/AdditionalMediaUpload.test.tsx b/__tests__/components/waves/memes/submission/components/AdditionalMediaUpload.test.tsx index 64bc1c38f1..a15ba7fd17 100644 --- a/__tests__/components/waves/memes/submission/components/AdditionalMediaUpload.test.tsx +++ b/__tests__/components/waves/memes/submission/components/AdditionalMediaUpload.test.tsx @@ -8,10 +8,13 @@ describe("AdditionalMediaUpload", () => { artworkCommentary: "", aboutArtist: "", previewImage: "", + promoVideo: "", requiresPreviewImage: false, + requiresPromoVideoOption: false, previewRequiredMediaType: null, onSupportingMediaChange: jest.fn(), onPreviewImageChange: jest.fn(), + onPromoVideoChange: jest.fn(), onArtworkCommentaryChange: jest.fn(), onAboutArtistChange: jest.fn(), }; @@ -24,17 +27,30 @@ describe("AdditionalMediaUpload", () => { ).toBeInTheDocument(); }); - it("shows preview image section when video submission", () => { + it("shows preview section when video submission", () => { render(); - expect(screen.getByText(/Preview Image \*/i)).toBeInTheDocument(); + expect(screen.getByText(/Preview \*/i)).toBeInTheDocument(); expect( screen.getByText(/Video submissions require a preview image/i) ).toBeInTheDocument(); }); - it("does not show preview image section for regular submissions", () => { + it("does not show preview section for regular submissions", () => { render(); - expect(screen.queryByText(/Preview Image \*/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Preview \*/i)).not.toBeInTheDocument(); + }); + + it("shows promo video section for HTML submissions", () => { + render(); + expect(screen.getByText("Promo Video")).toBeInTheDocument(); + expect( + screen.getByText(/For HTML submissions, we recommend providing a promo video/i) + ).toBeInTheDocument(); + }); + + it("does not show promo video section for video submissions", () => { + render(); + expect(screen.queryByText("Promo Video")).not.toBeInTheDocument(); }); it("calls onArtworkCommentaryChange when commentary is updated", async () => { diff --git a/__tests__/helpers/waves/drop.helpers.test.ts b/__tests__/helpers/waves/drop.helpers.test.ts index f25df73426..2298f0c5b8 100644 --- a/__tests__/helpers/waves/drop.helpers.test.ts +++ b/__tests__/helpers/waves/drop.helpers.test.ts @@ -1,4 +1,4 @@ -import { getStableDropKey, DropSize, convertApiDropToExtendedDrop, getFeedItemKey, getDropPreviewImageUrl } from '@/helpers/waves/drop.helpers'; +import { getStableDropKey, DropSize, convertApiDropToExtendedDrop, getFeedItemKey, getDropPreviewImageUrl, getDropPromoVideoUrl } from '@/helpers/waves/drop.helpers'; import { ApiFeedItemType } from '@/generated/models/ApiFeedItemType'; import { MemesSubmissionAdditionalInfoKey } from '@/components/waves/memes/submission/types/OperationalData'; @@ -72,4 +72,45 @@ describe('drop.helpers', () => { expect(getDropPreviewImageUrl(metadata as any)).toBeNull(); }); }); + + describe('getDropPromoVideoUrl', () => { + it('returns null when metadata is undefined', () => { + expect(getDropPromoVideoUrl(undefined)).toBeNull(); + }); + + it('returns null when metadata is empty', () => { + expect(getDropPromoVideoUrl([])).toBeNull(); + }); + + it('returns null when additional_media entry is missing', () => { + const metadata = [{ data_key: 'other_key', data_value: 'value' }]; + expect(getDropPromoVideoUrl(metadata as any)).toBeNull(); + }); + + it('returns null when promo_video is not in additional_media', () => { + const metadata = [{ + data_key: MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA, + data_value: JSON.stringify({ artist_profile_media: [] }) + }]; + expect(getDropPromoVideoUrl(metadata as any)).toBeNull(); + }); + + it('returns parsed IPFS URL when promo_video exists', () => { + const ipfsHash = 'QmPromoVideo123'; + const metadata = [{ + data_key: MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA, + data_value: JSON.stringify({ promo_video: `ipfs://${ipfsHash}` }) + }]; + const result = getDropPromoVideoUrl(metadata as any); + expect(result).toContain(ipfsHash); + }); + + it('returns null when JSON parsing fails', () => { + const metadata = [{ + data_key: MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA, + data_value: 'invalid json' + }]; + expect(getDropPromoVideoUrl(metadata as any)).toBeNull(); + }); + }); }); diff --git a/components/brain/BrainMobile.tsx b/components/brain/BrainMobile.tsx index c9dc37c804..ed413fe91e 100644 --- a/components/brain/BrainMobile.tsx +++ b/components/brain/BrainMobile.tsx @@ -277,8 +277,8 @@ const BrainMobile: React.FC = ({ children }) => { }; const dropOverlayClass = isApp - ? "tw-fixed tw-inset-0 tw-z-50 tw-bg-black tailwind-scope" - : "tw-absolute tw-inset-0 tw-z-50"; + ? "tw-fixed tw-inset-0 tw-z-[1010] tw-bg-black tailwind-scope" + : "tw-absolute tw-inset-0 tw-z-[1010]"; return (
diff --git a/components/header/AppHeader.tsx b/components/header/AppHeader.tsx index 9c81634a9c..877bc14e2d 100644 --- a/components/header/AppHeader.tsx +++ b/components/header/AppHeader.tsx @@ -1,6 +1,7 @@ "use client"; import { capitalizeEveryWord, formatAddress } from "@/helpers/Helpers"; +import { resolveIpfsUrlSync } from "@/components/ipfs/IPFSContext"; import Image from "next/image"; import { useIdentity } from "@/hooks/useIdentity"; import { useWaveById } from "@/hooks/useWaveById"; @@ -127,7 +128,7 @@ export default function AppHeader() { {address ? ( pfp ? ( pfp = ({
{children} {shouldShowDropOverlay && ( -
+
{ + const url = getDropPromoVideoUrl(drop.metadata); + if (!url) return null; + + const info = getFileInfoFromUrl(url); + if (!info) return null; + + return { url, fileInfo: info }; + }, [drop.metadata]); + const fileName = useMemo(() => { let name = title; if (wave?.name) { @@ -195,7 +206,9 @@ export const MemesSingleWaveDropInfoPanel = ({ - {(artworkMedia && fileInfo) || previewImageData ? ( + {(artworkMedia && fileInfo) || + previewImageData || + promoVideoData ? (
{artworkMedia && fileInfo && ( @@ -232,6 +245,23 @@ export const MemesSingleWaveDropInfoPanel = ({ /> )} + {promoVideoData && ( + <> + + Promo Video: + + + {promoVideoData.fileInfo.extension.toUpperCase()} + + + + )}
) : null} diff --git a/components/waves/drop/WaveDropAdditionalInfo.tsx b/components/waves/drop/WaveDropAdditionalInfo.tsx index 2f3d014727..4444116efc 100644 --- a/components/waves/drop/WaveDropAdditionalInfo.tsx +++ b/components/waves/drop/WaveDropAdditionalInfo.tsx @@ -1,15 +1,15 @@ "use client"; -import { useMemo } from "react"; -import Image from "next/image"; -import { ExtendedDrop } from "@/helpers/waves/drop.helpers"; -import { parseIpfsUrl } from "@/helpers/Helpers"; -import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; -import { getFileInfoFromUrl } from "@/helpers/file.helpers"; import { AdditionalMedia, MemesSubmissionAdditionalInfoKey, } from "@/components/waves/memes/submission/types/OperationalData"; +import { getFileInfoFromUrl } from "@/helpers/file.helpers"; +import { parseIpfsUrl } from "@/helpers/Helpers"; +import { getScaledImageUri, ImageScale } from "@/helpers/image.helpers"; +import { ExtendedDrop } from "@/helpers/waves/drop.helpers"; +import Image from "next/image"; +import { useMemo } from "react"; const MAX_MEDIA = 4; const VIDEO_EXTENSIONS = new Set(["mp4", "mov", "m4v", "webm", "ogv"]); @@ -18,6 +18,7 @@ const emptyAdditionalMedia: AdditionalMedia = { artist_profile_media: [], artwork_commentary_media: [], preview_image: "", + promo_video: "", }; const parseAdditionalMedia = (rawValue?: string): AdditionalMedia => { @@ -40,6 +41,8 @@ const parseAdditionalMedia = (rawValue?: string): AdditionalMedia => { : [], preview_image: typeof parsed.preview_image === "string" ? parsed.preview_image : "", + promo_video: + typeof parsed.promo_video === "string" ? parsed.promo_video : "", }; } catch { return emptyAdditionalMedia; @@ -61,56 +64,64 @@ interface WaveDropAdditionalInfoProps { export const WaveDropAdditionalInfo = ({ drop, }: WaveDropAdditionalInfoProps) => { - const { commentary, aboutArtist, previewImage, mediaItems } = useMemo(() => { - const metadata = drop.metadata ?? []; - const getMetadataValue = (key: MemesSubmissionAdditionalInfoKey) => - metadata.find((item) => item.data_key === key)?.data_value?.trim() ?? ""; - - const commentaryValue = getMetadataValue( - MemesSubmissionAdditionalInfoKey.COMMENTARY - ); - const aboutArtistValue = getMetadataValue( - MemesSubmissionAdditionalInfoKey.ABOUT_ARTIST - ); - - const additionalMediaEntry = metadata.find( - (item) => - item.data_key === MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA - ); - const additionalMedia = parseAdditionalMedia( - additionalMediaEntry?.data_value - ); - - const previewImageValue = additionalMedia.preview_image - ? parseIpfsUrl(additionalMedia.preview_image) - : ""; - - const mediaItemsValue = additionalMedia.artwork_commentary_media - .filter( - (url): url is string => - typeof url === "string" && url.trim().length > 0 - ) - .map((url) => parseIpfsUrl(url)) - .filter((url): url is string => url.length > 0) - .map((url) => { - const isVideo = isVideoUrl(url); - const displayUrl = isVideo - ? url - : getScaledImageUri(url, ImageScale.AUTOx600); - return { url, displayUrl, isVideo }; - }); - - return { - commentary: commentaryValue, - aboutArtist: aboutArtistValue, - previewImage: previewImageValue, - mediaItems: mediaItemsValue, - }; - }, [drop.metadata]); + const { commentary, aboutArtist, previewImage, promoVideo, mediaItems } = + useMemo(() => { + const metadata = drop.metadata ?? []; + const getMetadataValue = (key: MemesSubmissionAdditionalInfoKey) => + metadata.find((item) => item.data_key === key)?.data_value?.trim() ?? + ""; + + const commentaryValue = getMetadataValue( + MemesSubmissionAdditionalInfoKey.COMMENTARY + ); + const aboutArtistValue = getMetadataValue( + MemesSubmissionAdditionalInfoKey.ABOUT_ARTIST + ); + + const additionalMediaEntry = metadata.find( + (item) => + item.data_key === MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA + ); + const additionalMedia = parseAdditionalMedia( + additionalMediaEntry?.data_value + ); + + const previewImageValue = additionalMedia.preview_image + ? parseIpfsUrl(additionalMedia.preview_image) + : ""; + + const promoVideoValue = additionalMedia.promo_video + ? parseIpfsUrl(additionalMedia.promo_video) + : ""; + + const mediaItemsValue = additionalMedia.artwork_commentary_media + .filter( + (url): url is string => + typeof url === "string" && url.trim().length > 0 + ) + .map((url) => parseIpfsUrl(url)) + .filter((url): url is string => url.length > 0) + .map((url) => { + const isVideo = isVideoUrl(url); + const displayUrl = isVideo + ? url + : getScaledImageUri(url, ImageScale.AUTOx600); + return { url, displayUrl, isVideo }; + }); + + return { + commentary: commentaryValue, + aboutArtist: aboutArtistValue, + previewImage: previewImageValue, + promoVideo: promoVideoValue, + mediaItems: mediaItemsValue, + }; + }, [drop.metadata]); const displayedMedia = mediaItems.slice(0, MAX_MEDIA); const hasContent = previewImage || + promoVideo || displayedMedia.length > 0 || aboutArtist || commentary; @@ -140,12 +151,37 @@ export const WaveDropAdditionalInfo = ({
)} + {promoVideo && ( +
+

+ Promo Video +

+
+
+ +
+
+
+ )} + {displayedMedia.length > 0 && (

Additional Media

-
+
{displayedMedia.map((item, index) => (
About the Artist -

+

{aboutArtist}

@@ -200,7 +236,7 @@ export const WaveDropAdditionalInfo = ({

Artwork Commentary

-

+

{commentary}

diff --git a/components/waves/memes/submission/MemesArtSubmissionContainer.tsx b/components/waves/memes/submission/MemesArtSubmissionContainer.tsx index cdcba02d04..01b7ccba88 100644 --- a/components/waves/memes/submission/MemesArtSubmissionContainer.tsx +++ b/components/waves/memes/submission/MemesArtSubmissionContainer.tsx @@ -77,6 +77,13 @@ const MemesArtSubmissionContainer: FC = ({ [form.setAdditionalMedia] ); + const handlePromoVideoChange = useCallback( + (url: string) => { + form.setAdditionalMedia({ promo_video: url }); + }, + [form.setAdditionalMedia] + ); + // Phase change handler const handlePhaseChange = useCallback((phase: SubmissionPhase) => { // Any additional phase-specific handling can be done here @@ -140,7 +147,7 @@ const MemesArtSubmissionContainer: FC = ({ } : null; - const previewRequiredMediaType = (() => { + const mediaTypeInfo = (() => { const getMediaTypeLabel = (mimeType: string): string | null => { if (mimeType.startsWith("video/")) return "Video"; if (mimeType === "text/html") return "HTML"; @@ -148,16 +155,26 @@ const MemesArtSubmissionContainer: FC = ({ return null; }; + const isInteractiveMedia = (mimeType: string): boolean => { + return mimeType === "text/html" || mimeType === "model/gltf-binary"; + }; + if (form.mediaSource === "upload" && form.selectedFile) { - return getMediaTypeLabel(form.selectedFile.type); + const label = getMediaTypeLabel(form.selectedFile.type); + const isInteractive = isInteractiveMedia(form.selectedFile.type); + return { label, isInteractive }; } if (form.mediaSource === "url" && form.isExternalMediaValid) { - return getMediaTypeLabel(form.externalMediaMimeType); + const label = getMediaTypeLabel(form.externalMediaMimeType); + const isInteractive = isInteractiveMedia(form.externalMediaMimeType); + return { label, isInteractive }; } - return null; + return { label: null, isInteractive: false }; })(); + const previewRequiredMediaType = mediaTypeInfo.label; const requiresPreviewImage = previewRequiredMediaType !== null; + const requiresPromoVideoOption = mediaTypeInfo.isInteractive; // Map of steps to their corresponding components const stepComponents = { @@ -214,11 +231,14 @@ const MemesArtSubmissionContainer: FC = ({ artworkCommentary={form.operationalData.commentary} aboutArtist={form.operationalData.about_artist} previewImage={form.operationalData.additional_media.preview_image} + promoVideo={form.operationalData.additional_media.promo_video} requiresPreviewImage={requiresPreviewImage} + requiresPromoVideoOption={requiresPromoVideoOption} previewRequiredMediaType={previewRequiredMediaType} onBatchesChange={form.setAllowlistBatches} onSupportingMediaChange={handleArtworkCommentaryMediaChange} onPreviewImageChange={handlePreviewImageChange} + onPromoVideoChange={handlePromoVideoChange} onArtworkCommentaryChange={form.setCommentary} onAboutArtistChange={form.setAboutArtist} onBack={form.handleBackToArtwork} diff --git a/components/waves/memes/submission/components/AdditionalMediaUpload.tsx b/components/waves/memes/submission/components/AdditionalMediaUpload.tsx index cdffe5dd7c..62e6b2566e 100644 --- a/components/waves/memes/submission/components/AdditionalMediaUpload.tsx +++ b/components/waves/memes/submission/components/AdditionalMediaUpload.tsx @@ -14,10 +14,13 @@ interface AdditionalMediaUploadProps { readonly artworkCommentary: string; readonly aboutArtist: string; readonly previewImage: string; + readonly promoVideo: string; readonly requiresPreviewImage: boolean; + readonly requiresPromoVideoOption: boolean; readonly previewRequiredMediaType: string | null; readonly onSupportingMediaChange: (media: string[]) => void; readonly onPreviewImageChange: (url: string) => void; + readonly onPromoVideoChange: (url: string) => void; readonly onArtworkCommentaryChange: (commentary: string) => void; readonly onAboutArtistChange: (aboutArtist: string) => void; readonly errors?: { @@ -34,19 +37,24 @@ const AdditionalMediaUpload: FC = ({ artworkCommentary, aboutArtist, previewImage: _previewImage, + promoVideo: _promoVideo, requiresPreviewImage, + requiresPromoVideoOption, previewRequiredMediaType, onSupportingMediaChange, onPreviewImageChange, + onPromoVideoChange, onArtworkCommentaryChange, onAboutArtistChange, errors, }) => { const mediaInputRef = useRef(null); const previewInputRef = useRef(null); + const promoVideoInputRef = useRef(null); const mediaUpload = useMediaUpload(MAX_FILES); const previewUpload = useMediaUpload(1); + const promoVideoUpload = useMediaUpload(1); const prevMediaUrlsRef = useRef(""); const prevPreviewUrlRef = useRef(""); @@ -61,6 +69,11 @@ const AdditionalMediaUpload: FC = ({ return urls[0] || ""; }, [previewUpload.items]); + const promoVideoServerUrl = useMemo(() => { + const urls = promoVideoUpload.getServerUrls(); + return urls[0] || ""; + }, [promoVideoUpload.items]); + useEffect(() => { const urlsKey = mediaServerUrls.join(","); if (urlsKey !== prevMediaUrlsRef.current) { @@ -76,6 +89,15 @@ const AdditionalMediaUpload: FC = ({ } }, [previewServerUrl, onPreviewImageChange]); + const prevPromoVideoUrlRef = useRef(""); + + useEffect(() => { + if (promoVideoServerUrl !== prevPromoVideoUrlRef.current) { + prevPromoVideoUrlRef.current = promoVideoServerUrl; + onPromoVideoChange(promoVideoServerUrl); + } + }, [promoVideoServerUrl, onPromoVideoChange]); + const handleFileChange = useCallback( (upload: ReturnType) => (e: ChangeEvent) => { @@ -217,7 +239,7 @@ const AdditionalMediaUpload: FC = ({
{description && ( -

{description}

+

{description}

)} {upload.items.length > 0 && ( @@ -236,13 +258,26 @@ const AdditionalMediaUpload: FC = ({ {requiresPreviewImage && (
{renderMediaSection( - "Preview Image *", + "Preview *", previewUpload, previewInputRef, 1, errors?.previewImage, "image/*", - `${previewRequiredMediaType} submissions require a preview image or GIF. This will be used as the NFT's image field, while your main media is served via animation_url.` + `${previewRequiredMediaType} submissions require a preview image or GIF. This will be displayed as the thumbnail for your artwork.` + )} +
+ )} + {requiresPromoVideoOption && ( +
+ {renderMediaSection( + "Promo Video", + promoVideoUpload, + promoVideoInputRef, + 1, + undefined, + "video/*", + `For ${previewRequiredMediaType} submissions, we recommend providing a promo video for social media sharing. If no promo video is provided, the preview image will be used instead.` )}
)} diff --git a/components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts b/components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts index 3f64359746..a1d27e618a 100644 --- a/components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts +++ b/components/waves/memes/submission/hooks/useArtworkSubmissionForm.ts @@ -424,6 +424,7 @@ export function useArtworkSubmissionForm() { artist_profile_media: [], artwork_commentary_media: [], preview_image: "", + promo_video: "", }, commentary: "", about_artist: "", diff --git a/components/waves/memes/submission/steps/AdditionalInfoStep.tsx b/components/waves/memes/submission/steps/AdditionalInfoStep.tsx index a94719a640..e68b3c7285 100644 --- a/components/waves/memes/submission/steps/AdditionalInfoStep.tsx +++ b/components/waves/memes/submission/steps/AdditionalInfoStep.tsx @@ -28,11 +28,14 @@ interface AdditionalInfoStepProps { readonly artworkCommentary: string; readonly aboutArtist: string; readonly previewImage: string; + readonly promoVideo: string; readonly requiresPreviewImage: boolean; + readonly requiresPromoVideoOption: boolean; readonly previewRequiredMediaType: string | null; readonly onBatchesChange: (batches: AllowlistBatchRaw[]) => void; readonly onSupportingMediaChange: (media: string[]) => void; readonly onPreviewImageChange: (url: string) => void; + readonly onPromoVideoChange: (url: string) => void; readonly onArtworkCommentaryChange: (commentary: string) => void; readonly onAboutArtistChange: (aboutArtist: string) => void; readonly onBack: () => void; @@ -50,11 +53,14 @@ const AdditionalInfoStep: FC = ({ artworkCommentary, aboutArtist, previewImage, + promoVideo, requiresPreviewImage, + requiresPromoVideoOption, previewRequiredMediaType, onBatchesChange, onSupportingMediaChange, onPreviewImageChange, + onPromoVideoChange, onArtworkCommentaryChange, onAboutArtistChange, onBack, @@ -181,10 +187,13 @@ const AdditionalInfoStep: FC = ({ artworkCommentary={artworkCommentary} aboutArtist={aboutArtist} previewImage={previewImage} + promoVideo={promoVideo} requiresPreviewImage={requiresPreviewImage} + requiresPromoVideoOption={requiresPromoVideoOption} previewRequiredMediaType={previewRequiredMediaType} onSupportingMediaChange={onSupportingMediaChange} onPreviewImageChange={onPreviewImageChange} + onPromoVideoChange={onPromoVideoChange} onArtworkCommentaryChange={onArtworkCommentaryChange} onAboutArtistChange={onAboutArtistChange} /> diff --git a/components/waves/memes/submission/types/OperationalData.ts b/components/waves/memes/submission/types/OperationalData.ts index e9bcbd20d5..74a1ef577f 100644 --- a/components/waves/memes/submission/types/OperationalData.ts +++ b/components/waves/memes/submission/types/OperationalData.ts @@ -39,6 +39,7 @@ export interface AdditionalMedia { artist_profile_media: string[]; artwork_commentary_media: string[]; preview_image: string; + promo_video: string; } export interface OperationalData { diff --git a/helpers/waves/drop.helpers.ts b/helpers/waves/drop.helpers.ts index 909f9e40ae..9553669224 100644 --- a/helpers/waves/drop.helpers.ts +++ b/helpers/waves/drop.helpers.ts @@ -183,3 +183,22 @@ export const getDropPreviewImageUrl = ( return null; } }; + +export const getDropPromoVideoUrl = ( + metadata: ApiDropMetadata[] | undefined +): string | null => { + const additionalMediaEntry = metadata?.find( + (m) => m.data_key === MemesSubmissionAdditionalInfoKey.ADDITIONAL_MEDIA + ); + if (!additionalMediaEntry?.data_value) return null; + + try { + const parsed = JSON.parse(additionalMediaEntry.data_value) as { + promo_video?: string; + }; + if (!parsed?.promo_video) return null; + return parseIpfsUrl(parsed.promo_video); + } catch { + return null; + } +};