diff --git a/app/(app)/create/hooks/usePreviewSurfaceColor.ts b/app/(app)/create/hooks/usePreviewSurfaceColor.ts index ef39c1c..4d32ebc 100644 --- a/app/(app)/create/hooks/usePreviewSurfaceColor.ts +++ b/app/(app)/create/hooks/usePreviewSurfaceColor.ts @@ -1,19 +1,21 @@ "use client"; -import { useEffect, useState } from "react"; -import { dominantColorFromImageUrl } from "../../../../lib/create/previewSurfaceColor"; +import { useEffect, useLayoutEffect, useState } from "react"; +import { + dominantColorFromImageUrl, + isPreviewSurfaceColor, +} from "../../../../lib/create/previewSurfaceColor"; import { resolvePreviewSurfaceFallback } from "../utils/resolvePreviewSurfaceFallback"; /** * Fill for the review preview card. An uploaded logo contributes its most * common color; no image uses a random light color stored on the draft so - * reload keeps the same fill. Unset until mount so the server and client - * share the fallback class. + * reload keeps the same fill. * * @param imageUrl Uploaded logo URL, or `null` when the user skipped the photo. * @param storedFallback Draft `previewSurfaceColor`, if already picked. * @param persistFallback Writes the fallback onto React state once. - * @returns A CSS color, or `undefined` before the client picks one. + * @returns A CSS color, or `undefined` before the client resolves one. * * @example * const color = usePreviewSurfaceColor(avatarUrl, state.previewSurfaceColor, persist); @@ -23,16 +25,22 @@ export function usePreviewSurfaceColor( storedFallback: string | undefined, persistFallback: (_color: string) => void, ): string | undefined { - const [color, setColor] = useState(undefined); + const [color, setColor] = useState(() => + storedFallback && isPreviewSurfaceColor(storedFallback) + ? storedFallback + : undefined, + ); + + useLayoutEffect(() => { + if (imageUrl) return; + const next = resolvePreviewSurfaceFallback(storedFallback); + persistFallback(next); + // eslint-disable-next-line react-hooks/set-state-in-effect -- apply before paint so reload does not flash the teal token + setColor((prev) => (prev === next ? prev : next)); + }, [imageUrl, persistFallback, storedFallback]); useEffect(() => { - if (!imageUrl) { - const next = resolvePreviewSurfaceFallback(storedFallback); - persistFallback(next); - // eslint-disable-next-line react-hooks/set-state-in-effect -- random fill is client-only; first paint stays on the fallback class - setColor(next); - return; - } + if (!imageUrl) return; let cancelled = false; void dominantColorFromImageUrl(imageUrl).then((extracted) => { diff --git a/app/(app)/create/screens/review/FinalReviewScreen.tsx b/app/(app)/create/screens/review/FinalReviewScreen.tsx index ae5c337..cee9ab7 100644 --- a/app/(app)/create/screens/review/FinalReviewScreen.tsx +++ b/app/(app)/create/screens/review/FinalReviewScreen.tsx @@ -311,12 +311,12 @@ export function FinalReviewScreen({ size={mdUp ? "L" : "M"} expanded={true} descriptionAtBottom - backgroundColor="bg-[var(--color-surface-invert-brand-teal)]" + backgroundColor="bg-transparent" surfaceColor={surfaceColor} logoUrl={avatarUrl ?? getAssetPath(vectorMarkPath("mutual-aid"))} logoAlt={ruleCardTitle} categories={finalReviewCategories} - className={CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS} + className={`${CREATE_FLOW_REVIEW_RULE_LAYOUT_CLASS}${surfaceColor ? "" : " invisible"}`} onClick={() => {}} />