diff --git a/__tests__/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.test.tsx b/__tests__/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.test.tsx new file mode 100644 index 0000000000..6a9f56ee0c --- /dev/null +++ b/__tests__/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.test.tsx @@ -0,0 +1,90 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { MemesSubmissionPreviewScreen } from "@/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen"; + +jest.mock("framer-motion", () => ({ + motion: { + div: ({ children, ...props }: any) =>
{children}
, + }, +})); + +jest.mock("@/components/memes/drops/MemesLeaderboardDrop", () => ({ + MemesLeaderboardDrop: () =>
, +})); + +jest.mock( + "@/components/waves/leaderboard/gallery/WaveLeaderboardGalleryItem", + () => ({ + WaveLeaderboardGalleryItem: () => ( +
+ ), + }) +); + +jest.mock("@/components/utils/button/SecondaryButton", () => (props: any) => ( + +)); + +jest.mock("@/components/utils/button/PrimaryButton", () => (props: any) => ( + +)); + +describe("MemesSubmissionPreviewScreen", () => { + const previewDrop = { id: "drop-1" } as any; + + it("renders isolated preview cases in the expected order", () => { + render( + + ); + + expect( + screen.getByRole("heading", { name: "Submission Preview" }) + ).toBeInTheDocument(); + + const caseTitles = screen + .getAllByRole("heading", { level: 5 }) + .map((heading) => heading.textContent?.trim()); + + expect(caseTitles).toEqual([ + "Leaderboard List Card", + "Leaderboard Gallery Card", + ]); + + expect(screen.getByTestId("preview-list-card")).toBeInTheDocument(); + expect(screen.getByTestId("preview-gallery-card")).toBeInTheDocument(); + }); + + it("keeps footer actions wired", async () => { + const user = userEvent.setup(); + const onBackToEdit = jest.fn(); + const onSubmit = jest.fn(); + + render( + + ); + + await user.click(screen.getByRole("button", { name: "Back to Edit" })); + await user.click(screen.getByRole("button", { name: "Submit Artwork" })); + + expect(onBackToEdit).toHaveBeenCalledTimes(1); + expect(onSubmit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/__tests__/components/waves/memes/submission/preview/presets.test.ts b/__tests__/components/waves/memes/submission/preview/presets.test.ts new file mode 100644 index 0000000000..776301a442 --- /dev/null +++ b/__tests__/components/waves/memes/submission/preview/presets.test.ts @@ -0,0 +1,18 @@ +import { PREVIEW_CASE_WIDTHS } from "@/components/waves/memes/submission/preview/presentation/presets"; + +describe("PREVIEW_CASE_WIDTHS", () => { + it("includes all expected preview cases", () => { + expect(Object.keys(PREVIEW_CASE_WIDTHS).sort()).toEqual( + ["leaderboardGalleryItem", "leaderboardList"].sort() + ); + }); + + it("uses production-mimic responsive width presets", () => { + expect(PREVIEW_CASE_WIDTHS).toEqual({ + leaderboardList: + "tw-max-w-full sm:tw-max-w-[34rem] lg:tw-max-w-[44rem] xl:tw-max-w-[52rem]", + leaderboardGalleryItem: + "tw-max-w-full sm:tw-max-w-[20rem] lg:tw-max-w-[22rem] xl:tw-max-w-[24rem]", + }); + }); +}); diff --git a/__tests__/components/waves/memes/submission/utils/buildPreviewDrop.test.ts b/__tests__/components/waves/memes/submission/utils/buildPreviewDrop.test.ts new file mode 100644 index 0000000000..61578545b8 --- /dev/null +++ b/__tests__/components/waves/memes/submission/utils/buildPreviewDrop.test.ts @@ -0,0 +1,43 @@ +import { buildPreviewDrop } from "@/components/waves/memes/submission/utils/buildPreviewDrop"; + +describe("buildPreviewDrop", () => { + it("sets placeholder score and voter count for preview cards", () => { + const previewDrop = buildPreviewDrop({ + wave: { + id: "wave-1", + name: "Preview Wave", + picture: null, + description_drop: { id: "drop-description-1" }, + voting: { + authenticated_user_eligible: true, + period: { min: 1, max: 2 }, + credit_type: "NIC", + forbid_negative_votes: false, + }, + participation: { authenticated_user_eligible: true }, + chat: { authenticated_user_eligible: true }, + wave: { admin_drop_deletion_enabled: false }, + pinned: false, + } as any, + traits: { + title: "Preview Title", + description: "Preview Description", + } as any, + operationalData: undefined, + mediaSelection: { + mediaSource: "url", + selectedFile: null, + externalUrl: "https://example.com/art.png", + externalMimeType: "image/png", + isExternalValid: true, + }, + uploadArtworkUrl: "", + connectedProfile: null, + }); + + expect(previewDrop.rating).toBe(6529); + expect(previewDrop.realtime_rating).toBe(6529420); + expect(previewDrop.rating_prediction).toBe(69420); + expect(previewDrop.raters_count).toBe(69); + }); +}); diff --git a/components/waves/memes/submission/MemesArtSubmissionContainer.tsx b/components/waves/memes/submission/MemesArtSubmissionContainer.tsx index 01b7ccba88..f713bd4a30 100644 --- a/components/waves/memes/submission/MemesArtSubmissionContainer.tsx +++ b/components/waves/memes/submission/MemesArtSubmissionContainer.tsx @@ -1,19 +1,23 @@ "use client"; +import { useAuth } from "@/components/auth/Auth"; import { useSeizeConnectContext } from "@/components/auth/SeizeConnectContext"; import type { ApiWave } from "@/generated/models/ApiWave"; +import type { ExtendedDrop } from "@/helpers/waves/drop.helpers"; import { faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { motion } from "framer-motion"; import type { FC } from "react"; -import { useCallback, useEffect } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useArtworkSubmissionForm } from "./hooks/useArtworkSubmissionForm"; import { useArtworkSubmissionMutation } from "./hooks/useArtworkSubmissionMutation"; +import { MemesSubmissionPreviewScreen } from "./preview/MemesSubmissionPreviewScreen"; import AdditionalInfoStep from "./steps/AdditionalInfoStep"; import AgreementStep from "./steps/AgreementStep"; import ArtworkStep from "./steps/ArtworkStep"; import { SubmissionStep } from "./types/Steps"; import type { SubmissionPhase } from "./ui/SubmissionProgress"; +import { buildPreviewDrop } from "./utils/buildPreviewDrop"; interface MemesArtSubmissionContainerProps { readonly onClose: () => void; @@ -37,7 +41,10 @@ const MemesArtSubmissionContainer: FC = ({ }) => { // Use the form hook to manage all state const form = useArtworkSubmissionForm(); + const { connectedProfile } = useAuth(); const { isSafeWallet, address } = useSeizeConnectContext(); + const [isPreviewMode, setIsPreviewMode] = useState(false); + const [previewDrop, setPreviewDrop] = useState(null); // Use the mutation hook for submission const { @@ -58,6 +65,11 @@ const MemesArtSubmissionContainer: FC = ({ return () => clearTimeout(timer); }, [submissionPhase, onClose]); + const resetPreviewState = useCallback(() => { + setIsPreviewMode(false); + setPreviewDrop(null); + }, []); + // Handle file selection const handleFileSelect = (file: File) => { form.handleFileSelect(file); @@ -90,6 +102,32 @@ const MemesArtSubmissionContainer: FC = ({ console.warn(`Submission phase changed to: ${phase}`); }, []); + const handleOpenPreview = useCallback(() => { + const { imageUrl, traits, operationalData } = form.getSubmissionData(); + const media = form.getMediaSelection(); + + setPreviewDrop( + buildPreviewDrop({ + wave, + traits, + operationalData, + mediaSelection: media, + uploadArtworkUrl: imageUrl, + connectedProfile, + }) + ); + setIsPreviewMode(true); + }, [connectedProfile, form, wave]); + + const handleBackToEdit = useCallback(() => { + setIsPreviewMode(false); + }, []); + + const handleBackFromAdditionalInfo = () => { + resetPreviewState(); + form.handleBackToArtwork(); + }; + // Handle final submission const handleSubmit = async () => { // Get submission data including all traits @@ -218,34 +256,43 @@ const MemesArtSubmissionContainer: FC = ({ submissionError={submissionError} /> ), - [SubmissionStep.ADDITIONAL_INFO]: ( - - ), + [SubmissionStep.ADDITIONAL_INFO]: + isPreviewMode && previewDrop ? ( + + ) : ( + + ), }; return ( diff --git a/components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts b/components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts index e9d03a039a..30cf0d3e93 100644 --- a/components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts +++ b/components/waves/memes/submission/hooks/useArtworkSubmissionMutation.ts @@ -15,6 +15,7 @@ import type { InteractiveMediaMimeType } from "../constants/media"; import type { TraitsData } from "../types/TraitsData"; import type { SubmissionPhase } from "../ui/SubmissionProgress"; import { validateStrictAddress } from "../utils/addressValidation"; +import { objectEntries } from "../utils/objectEntries"; import { OperationalData } from "../types/OperationalData"; @@ -58,9 +59,9 @@ const transformToApiRequest = (data: { } = data; // Create metadata array from trait data - const metadata: ApiDropMetadata[] = Object.entries(traits) + const metadata: ApiDropMetadata[] = objectEntries(traits) .map(([key, value]) => ({ - data_key: key, + data_key: String(key), data_value: value?.toString(), })) .filter( diff --git a/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.tsx b/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.tsx new file mode 100644 index 0000000000..3f4aaf8df8 --- /dev/null +++ b/components/waves/memes/submission/preview/MemesSubmissionPreviewScreen.tsx @@ -0,0 +1,69 @@ +"use client"; + +import SecondaryButton from "@/components/utils/button/SecondaryButton"; +import PrimaryButton from "@/components/utils/button/PrimaryButton"; +import type { ExtendedDrop } from "@/helpers/waves/drop.helpers"; +import { motion } from "framer-motion"; +import { useCallback } from "react"; +import { PreviewLeaderboardGalleryCase } from "./components/PreviewLeaderboardGalleryCase"; +import { PreviewLeaderboardListCase } from "./components/PreviewLeaderboardListCase"; + +interface MemesSubmissionPreviewScreenProps { + readonly previewDrop: ExtendedDrop; + readonly onBackToEdit: () => void; + readonly onSubmit: () => void; + readonly isSubmitting: boolean; +} + +export function MemesSubmissionPreviewScreen({ + previewDrop, + onBackToEdit, + onSubmit, + isSubmitting, +}: MemesSubmissionPreviewScreenProps) { + const onDropClick = useCallback((_drop: ExtendedDrop) => {}, []); + + return ( + +
+
+

+ Submission Preview +

+

+ Read-only preview of how your submission may appear in different + views. +

+
+ + + +
+ +
+ + Back to Edit + + + Submit Artwork + +
+
+ ); +} diff --git a/components/waves/memes/submission/preview/components/PreviewCaseSection.tsx b/components/waves/memes/submission/preview/components/PreviewCaseSection.tsx new file mode 100644 index 0000000000..fc26c5670b --- /dev/null +++ b/components/waves/memes/submission/preview/components/PreviewCaseSection.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from "react"; + +interface PreviewCaseSectionProps { + readonly title: string; + readonly description?: string; + readonly children: ReactNode; +} + +export function PreviewCaseSection({ + title, + description, + children, +}: PreviewCaseSectionProps) { + return ( +
+
+ {title} +
+ {description && ( +

{description}

+ )} + {children} +
+ ); +} diff --git a/components/waves/memes/submission/preview/components/PreviewLeaderboardGalleryCase.tsx b/components/waves/memes/submission/preview/components/PreviewLeaderboardGalleryCase.tsx new file mode 100644 index 0000000000..fcfcedbd1d --- /dev/null +++ b/components/waves/memes/submission/preview/components/PreviewLeaderboardGalleryCase.tsx @@ -0,0 +1,25 @@ +import { WaveLeaderboardGalleryItem } from "@/components/waves/leaderboard/gallery/WaveLeaderboardGalleryItem"; +import type { ExtendedDrop } from "@/helpers/waves/drop.helpers"; +import { PREVIEW_CASE_WIDTHS } from "../presentation/presets"; +import { PreviewCaseSection } from "./PreviewCaseSection"; +import { PreviewWidthFrame } from "./PreviewWidthFrame"; + +interface PreviewLeaderboardGalleryCaseProps { + readonly drop: ExtendedDrop; + readonly onDropClick: (drop: ExtendedDrop) => void; +} + +export function PreviewLeaderboardGalleryCase({ + drop, + onDropClick, +}: PreviewLeaderboardGalleryCaseProps) { + return ( + + + + + + ); +} diff --git a/components/waves/memes/submission/preview/components/PreviewLeaderboardListCase.tsx b/components/waves/memes/submission/preview/components/PreviewLeaderboardListCase.tsx new file mode 100644 index 0000000000..2069e88a61 --- /dev/null +++ b/components/waves/memes/submission/preview/components/PreviewLeaderboardListCase.tsx @@ -0,0 +1,23 @@ +import { MemesLeaderboardDrop } from "@/components/memes/drops/MemesLeaderboardDrop"; +import type { ExtendedDrop } from "@/helpers/waves/drop.helpers"; +import { PREVIEW_CASE_WIDTHS } from "../presentation/presets"; +import { PreviewCaseSection } from "./PreviewCaseSection"; +import { PreviewWidthFrame } from "./PreviewWidthFrame"; + +interface PreviewLeaderboardListCaseProps { + readonly drop: ExtendedDrop; + readonly onDropClick: (drop: ExtendedDrop) => void; +} + +export function PreviewLeaderboardListCase({ + drop, + onDropClick, +}: PreviewLeaderboardListCaseProps) { + return ( + + + + + + ); +} diff --git a/components/waves/memes/submission/preview/components/PreviewWidthFrame.tsx b/components/waves/memes/submission/preview/components/PreviewWidthFrame.tsx new file mode 100644 index 0000000000..1ca5b17ab7 --- /dev/null +++ b/components/waves/memes/submission/preview/components/PreviewWidthFrame.tsx @@ -0,0 +1,35 @@ +import type { ReactNode } from "react"; + +interface PreviewWidthFrameProps { + readonly maxWidthClass: string; + readonly minWidthClass?: string; + readonly contentClassName?: string; + readonly disablePointerEvents?: boolean; + readonly children: ReactNode; +} + +export function PreviewWidthFrame({ + maxWidthClass, + minWidthClass, + contentClassName, + disablePointerEvents = true, + children, +}: PreviewWidthFrameProps) { + const frameClassName = ["tw-w-full", maxWidthClass, minWidthClass] + .filter(Boolean) + .join(" "); + const contentClasses = [ + disablePointerEvents ? "tw-pointer-events-none" : "", + contentClassName, + ] + .filter(Boolean) + .join(" "); + + return ( +
+
+
{children}
+
+
+ ); +} diff --git a/components/waves/memes/submission/preview/presentation/presets.ts b/components/waves/memes/submission/preview/presentation/presets.ts new file mode 100644 index 0000000000..783be435cf --- /dev/null +++ b/components/waves/memes/submission/preview/presentation/presets.ts @@ -0,0 +1,6 @@ +export const PREVIEW_CASE_WIDTHS = { + leaderboardList: + "tw-max-w-full sm:tw-max-w-[34rem] lg:tw-max-w-[44rem] xl:tw-max-w-[52rem]", + leaderboardGalleryItem: + "tw-max-w-full sm:tw-max-w-[20rem] lg:tw-max-w-[22rem] xl:tw-max-w-[24rem]", +} as const; diff --git a/components/waves/memes/submission/steps/AdditionalInfoStep.tsx b/components/waves/memes/submission/steps/AdditionalInfoStep.tsx index e68b3c7285..ee766abfff 100644 --- a/components/waves/memes/submission/steps/AdditionalInfoStep.tsx +++ b/components/waves/memes/submission/steps/AdditionalInfoStep.tsx @@ -7,13 +7,13 @@ import type { FC } from "react"; import AdditionalMediaUpload from "../components/AdditionalMediaUpload"; import AirdropConfig from "../components/AirdropConfig"; import AllowlistBatchManager, { - AllowlistBatchRaw, + type AllowlistBatchRaw, } from "../components/AllowlistBatchManager"; import PaymentConfig from "../components/PaymentConfig"; import { AIRDROP_TOTAL, - AirdropEntry, - PaymentInfo, + type AirdropEntry, + type PaymentInfo, } from "../types/OperationalData"; import { validateStrictAddress } from "../utils/addressValidation"; import { validateTokenIdFormat } from "../utils/tokenParsing"; @@ -39,6 +39,7 @@ interface AdditionalInfoStepProps { readonly onArtworkCommentaryChange: (commentary: string) => void; readonly onAboutArtistChange: (aboutArtist: string) => void; readonly onBack: () => void; + readonly onPreview: () => void; readonly onSubmit: () => void; readonly isSubmitting: boolean; } @@ -64,6 +65,7 @@ const AdditionalInfoStep: FC = ({ onArtworkCommentaryChange, onAboutArtistChange, onBack, + onPreview, onSubmit, isSubmitting, }) => { @@ -76,7 +78,7 @@ const AdditionalInfoStep: FC = ({ }; const getTokenIdsError = (tokenIds: string) => { - return validateTokenIdFormat(tokenIds) || undefined; + return validateTokenIdFormat(tokenIds) ?? undefined; }; const isFormValid = () => { @@ -87,10 +89,7 @@ const AdditionalInfoStep: FC = ({ ); if (hasInvalidCount) return false; - const totalAllocated = airdropEntries.reduce( - (sum, e) => sum + (e.count ?? 0), - 0 - ); + const totalAllocated = airdropEntries.reduce((sum, e) => sum + e.count, 0); if (totalAllocated !== AIRDROP_TOTAL) return false; // Every entry with count > 0 must have a valid address @@ -146,6 +145,8 @@ const AdditionalInfoStep: FC = ({ return true; }; + const formValid = isFormValid(); + return ( = ({ Back - - Submit Artwork - +
+ + Preview + + + Submit Artwork + +
); diff --git a/components/waves/memes/submission/utils/buildPreviewDrop.ts b/components/waves/memes/submission/utils/buildPreviewDrop.ts new file mode 100644 index 0000000000..08aaa317d7 --- /dev/null +++ b/components/waves/memes/submission/utils/buildPreviewDrop.ts @@ -0,0 +1,232 @@ +"use client"; + +import type { ApiIdentity } from "@/generated/models/ApiIdentity"; +import type { ApiDropMetadata } from "@/generated/models/ApiDropMetadata"; +import { ApiDropType } from "@/generated/models/ApiDropType"; +import type { ApiWave } from "@/generated/models/ApiWave"; +import { getBannerColorValue } from "@/helpers/profile-banner.helpers"; +import { DropSize, getOptimisticDropId } from "@/helpers/waves/drop.helpers"; +import type { ExtendedDrop } from "@/helpers/waves/drop.helpers"; +import type { OperationalData } from "../types/OperationalData"; +import type { TraitsData } from "../types/TraitsData"; +import { validateStrictAddress } from "./addressValidation"; +import { objectEntries } from "./objectEntries"; + +interface PreviewMediaSelection { + readonly mediaSource: "upload" | "url"; + readonly selectedFile: File | null; + readonly externalUrl: string; + readonly externalMimeType: string; + readonly isExternalValid: boolean; +} + +interface BuildPreviewDropInput { + readonly wave: ApiWave; + readonly traits: TraitsData; + readonly operationalData?: OperationalData | undefined; + readonly mediaSelection: PreviewMediaSelection; + readonly uploadArtworkUrl: string; + readonly connectedProfile: ApiIdentity | null; +} + +const FALLBACK_MEDIA_URL = + "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="; + +const buildMetadata = ( + traits: TraitsData, + operationalData?: OperationalData +): ApiDropMetadata[] => { + const metadata: ApiDropMetadata[] = objectEntries(traits) + .map(([key, value]) => ({ + data_key: String(key), + data_value: value.toString(), + })) + .filter((item) => item.data_value.length > 0); + + if (!operationalData) { + return metadata; + } + + metadata.push( + { + data_key: "payment_info", + data_value: JSON.stringify(operationalData.payment_info), + }, + { + data_key: "commentary", + data_value: operationalData.commentary, + }, + { + data_key: "about_artist", + data_value: operationalData.about_artist, + } + ); + + if (operationalData.airdrop_config.length > 0) { + const validEntries = operationalData.airdrop_config.filter((entry) => { + const trimmedAddress = entry.address.trim(); + return validateStrictAddress(trimmedAddress) && entry.count > 0; + }); + + if (validEntries.length > 0) { + metadata.push({ + data_key: "airdrop_config", + data_value: JSON.stringify(validEntries), + }); + } + } + + if (operationalData.allowlist_batches.length > 0) { + metadata.push({ + data_key: "allowlist_batches", + data_value: JSON.stringify( + operationalData.allowlist_batches.map((batch) => ({ + contract: batch.contract, + token_ids: batch.token_ids_raw || "", + })) + ), + }); + } + + metadata.push({ + data_key: "additional_media", + data_value: JSON.stringify(operationalData.additional_media), + }); + + return metadata; +}; + +const buildPreviewMedia = ({ + mediaSelection, + uploadArtworkUrl, +}: { + readonly mediaSelection: PreviewMediaSelection; + readonly uploadArtworkUrl: string; +}) => { + if ( + mediaSelection.mediaSource === "upload" && + mediaSelection.selectedFile && + uploadArtworkUrl + ) { + return { + url: uploadArtworkUrl, + mime_type: mediaSelection.selectedFile.type || "image/jpeg", + }; + } + + if ( + mediaSelection.mediaSource === "url" && + mediaSelection.isExternalValid && + mediaSelection.externalUrl + ) { + return { + url: mediaSelection.externalUrl, + mime_type: mediaSelection.externalMimeType || "text/html", + }; + } + + return { + url: FALLBACK_MEDIA_URL, + mime_type: "image/gif", + }; +}; + +export const buildPreviewDrop = ({ + wave, + traits, + operationalData, + mediaSelection, + uploadArtworkUrl, + connectedProfile, +}: BuildPreviewDropInput): ExtendedDrop => { + const id = getOptimisticDropId(); + const now = Date.now(); + const media = buildPreviewMedia({ mediaSelection, uploadArtworkUrl }); + const metadata = buildMetadata(traits, operationalData); + const primaryAddress = + connectedProfile?.primary_wallet ?? + "0x0000000000000000000000000000000000000000"; + + return { + type: DropSize.FULL, + stableKey: id, + stableHash: id, + id, + serial_no: now, + drop_type: ApiDropType.Participatory, + rank: null, + wave: { + id: wave.id, + name: wave.name, + picture: wave.picture, + description_drop_id: wave.description_drop.id, + authenticated_user_eligible_to_vote: + wave.voting.authenticated_user_eligible, + authenticated_user_eligible_to_participate: + wave.participation.authenticated_user_eligible, + authenticated_user_eligible_to_chat: + wave.chat.authenticated_user_eligible, + authenticated_user_admin: false, + visibility_group_id: null, + participation_group_id: null, + chat_group_id: null, + voting_group_id: null, + admin_group_id: null, + voting_period_start: wave.voting.period?.min ?? null, + voting_period_end: wave.voting.period?.max ?? null, + voting_credit_type: wave.voting.credit_type, + admin_drop_deletion_enabled: wave.wave.admin_drop_deletion_enabled, + forbid_negative_votes: wave.voting.forbid_negative_votes, + pinned: wave.pinned, + }, + author: { + id: connectedProfile?.id ?? "preview-user", + handle: connectedProfile?.handle ?? "preview-user", + pfp: connectedProfile?.pfp ?? null, + banner1_color: getBannerColorValue(connectedProfile?.banner1), + banner2_color: getBannerColorValue(connectedProfile?.banner2), + cic: connectedProfile?.cic ?? 0, + rep: connectedProfile?.rep ?? 0, + tdh: connectedProfile?.tdh ?? 0, + tdh_rate: connectedProfile?.tdh_rate ?? 0, + xtdh: connectedProfile?.xtdh ?? 0, + xtdh_rate: connectedProfile?.xtdh_rate ?? 0, + level: connectedProfile?.level ?? 0, + primary_address: primaryAddress, + subscribed_actions: [], + archived: false, + active_main_stage_submission_ids: + connectedProfile?.active_main_stage_submission_ids ?? [], + winner_main_stage_drop_ids: + connectedProfile?.winner_main_stage_drop_ids ?? [], + is_wave_creator: connectedProfile?.is_wave_creator ?? false, + }, + created_at: now, + updated_at: null, + title: traits.title || "Untitled Submission", + parts: [ + { + part_id: 1, + content: traits.description || null, + media: [media], + quoted_drop: null, + }, + ], + parts_count: 1, + referenced_nfts: [], + mentioned_users: [], + mentioned_waves: [], + metadata, + rating: 6529, + realtime_rating: 6529420, + rating_prediction: 69420, + top_raters: [], + raters_count: 69, + context_profile_context: null, + subscribed_actions: [], + is_signed: false, + reactions: [], + boosts: 0, + hide_link_preview: false, + }; +}; diff --git a/components/waves/memes/submission/utils/objectEntries.ts b/components/waves/memes/submission/utils/objectEntries.ts new file mode 100644 index 0000000000..4987321495 --- /dev/null +++ b/components/waves/memes/submission/utils/objectEntries.ts @@ -0,0 +1,6 @@ +type ObjectEntries = { + [K in keyof T]-?: [K, T[K]]; +}[keyof T][]; + +export const objectEntries = (obj: T): ObjectEntries => + Object.entries(obj) as ObjectEntries;