From d811b87b125fcf4202826e782c74559f839dae6d Mon Sep 17 00:00:00 2001 From: adilallo <39313955+adilallo@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:44:15 -0700 Subject: [PATCH] Final review template --- app/create/final-review/page.tsx | 116 ++++++++++++++++++++-- app/create/layout.tsx | 4 +- tests/components/FinalReviewPage.test.tsx | 69 +++++++++++++ tests/pages/user-journey.test.jsx | 28 +++--- 4 files changed, 194 insertions(+), 23 deletions(-) create mode 100644 tests/components/FinalReviewPage.test.tsx diff --git a/app/create/final-review/page.tsx b/app/create/final-review/page.tsx index bd12c20..3f50a17 100644 --- a/app/create/final-review/page.tsx +++ b/app/create/final-review/page.tsx @@ -1,25 +1,125 @@ "use client"; +import { useState, useEffect } from "react"; import { useMediaQuery } from "../../hooks/useMediaQuery"; import HeaderLockup from "../../components/type/HeaderLockup"; +import RuleCard from "../../components/cards/RuleCard"; +import type { Category } from "../../components/cards/RuleCard/RuleCard.types"; + +const TITLE = "Review your CommunityRule"; +const DESCRIPTION = + "Here's what other people will see. Make sure everything looks good before you finalize everything. Once the rule is finalized, you must use one of your decision-making mechanisms to edit it again."; + +const RULE_CARD_TITLE = "Mutual Aid Mondays"; +const RULE_CARD_DESCRIPTION = + "Mutual Aid Monday is a grassroots community in Denver, founded in November 2020 by Kelsang Virya, dedicated to supporting neighbors experiencing homelessness."; + +/** Static categories for final review (read-only display). */ +const FINAL_REVIEW_CATEGORIES: Category[] = [ + { + name: "Values", + chipOptions: [ + { id: "v1", label: "Consciousness", state: "unselected" }, + { id: "v2", label: "Ecology", state: "unselected" }, + { id: "v3", label: "Abundance", state: "unselected" }, + { id: "v4", label: "Art", state: "unselected" }, + { id: "v5", label: "Decisiveness", state: "unselected" }, + ], + }, + { + name: "Communication", + chipOptions: [{ id: "c1", label: "Signal", state: "unselected" }], + }, + { + name: "Membership", + chipOptions: [ + { id: "m1", label: "Open Admission", state: "unselected" }, + ], + }, + { + name: "Decision-making", + chipOptions: [ + { id: "d1", label: "Lazy Consensus", state: "unselected" }, + { id: "d2", label: "Modified Consensus", state: "unselected" }, + ], + }, + { + name: "Conflict management", + chipOptions: [ + { id: "cf1", label: "Code of Conduct", state: "unselected" }, + { id: "cf2", label: "Restorative Justice", state: "unselected" }, + ], + }, +]; /** * Final review step (right before completed). - * Figma: 20907-212767 + * Figma: 20907-212767 (full-size), 20976-220705 (small breakpoint). */ export default function FinalReviewPage() { + const [isMounted, setIsMounted] = useState(false); const isMdOrLarger = useMediaQuery("(min-width: 640px)"); + // Avoid flash: only use breakpoint after mount so SSR and first paint use same layout (desktop). + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: defer layout breakpoint until after mount to prevent flash + setIsMounted(true); + }, []); + + const showDesktopLayout = !isMounted || isMdOrLarger; + + if (showDesktopLayout) { + return ( +
+
+
+ +
+
+ {}} + /> +
+
+
+ ); + } + return ( -
-
+
+
+ {}} /> - {/* Content area: summary or checklist can be added per Figma */}
); diff --git a/app/create/layout.tsx b/app/create/layout.tsx index b7e5ec0..770e26c 100644 --- a/app/create/layout.tsx +++ b/app/create/layout.tsx @@ -92,7 +92,9 @@ function CreateFlowLayoutContent({ children }: { children: ReactNode }) { className="md:!text-[14px] md:!leading-[16px] !text-[12px] !leading-[14px] !px-[var(--spacing-measures-spacing-200,8px)] md:!px-[var(--spacing-measures-spacing-250,10px)] !py-[var(--spacing-measures-spacing-200,8px)] md:!py-[var(--spacing-measures-spacing-250,10px)]" onClick={handleNext} > - Next + {currentStep === "final-review" + ? "Finalize CommunityRule" + : "Next"} ) : null } diff --git a/tests/components/FinalReviewPage.test.tsx b/tests/components/FinalReviewPage.test.tsx new file mode 100644 index 0000000..2395f39 --- /dev/null +++ b/tests/components/FinalReviewPage.test.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { describe, it, expect } from "vitest"; +import { renderWithProviders as render, screen } from "../utils/test-utils"; +import "@testing-library/jest-dom/vitest"; +import FinalReviewPage from "../../app/create/final-review/page"; + +describe("FinalReviewPage", () => { + it("renders without crashing", () => { + render(); + expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument(); + }); + + it("renders HeaderLockup with expected title", () => { + render(); + expect( + screen.getByRole("heading", { + name: "Review your CommunityRule", + }), + ).toBeInTheDocument(); + }); + + it("renders HeaderLockup with expected description", () => { + render(); + expect( + screen.getByText( + /Here's what other people will see. Make sure everything looks good before you finalize everything. Once the rule is finalized, you must use one of your decision-making mechanisms to edit it again./i, + ), + ).toBeInTheDocument(); + }); + + it("renders RuleCard with title", () => { + render(); + expect(screen.getByText("Mutual Aid Mondays")).toBeInTheDocument(); + }); + + it("renders RuleCard with description", () => { + render(); + expect( + screen.getByText( + /Mutual Aid Monday is a grassroots community in Denver, founded in November 2020 by Kelsang Virya, dedicated to supporting neighbors experiencing homelessness./i, + ), + ).toBeInTheDocument(); + }); + + it("renders RuleCard as a button (card is interactive)", () => { + render(); + const buttons = screen.getAllByRole("button"); + expect(buttons.length).toBeGreaterThanOrEqual(1); + expect( + buttons.some((el) => el.textContent?.includes("Mutual Aid Mondays")), + ).toBe(true); + }); + + it("renders expanded RuleCard with category labels", () => { + render(); + expect(screen.getByText("Values")).toBeInTheDocument(); + expect(screen.getByText("Communication")).toBeInTheDocument(); + expect(screen.getByText("Membership")).toBeInTheDocument(); + expect(screen.getByText("Decision-making")).toBeInTheDocument(); + expect(screen.getByText("Conflict management")).toBeInTheDocument(); + }); + + it("renders category chips", () => { + render(); + expect(screen.getByText("Consciousness")).toBeInTheDocument(); + expect(screen.getByText("Signal")).toBeInTheDocument(); + expect(screen.getByText("Open Admission")).toBeInTheDocument(); + }); +}); diff --git a/tests/pages/user-journey.test.jsx b/tests/pages/user-journey.test.jsx index 31be9bf..e1bcbbc 100644 --- a/tests/pages/user-journey.test.jsx +++ b/tests/pages/user-journey.test.jsx @@ -9,24 +9,24 @@ import { vi, describe, test, expect, afterEach } from "vitest"; import React from "react"; import Page from "../../app/(marketing)/page"; -// Mock next/dynamic to return components synchronously in tests +// Mock next/dynamic so dynamically loaded components render after the import resolves vi.mock("next/dynamic", () => { + const React = require("react"); return { default: (importFn, options) => { - // In tests, resolve the dynamic import immediately and return the component - let Component = null; - importFn().then((mod) => { - Component = mod.default || mod; - }); - // Return a synchronous wrapper that uses the mocked component - return (props) => { - // Use the mocked component directly once resolved - if (Component) { - return ; + function DynamicWrapper(props) { + const [Component, setComponent] = React.useState(null); + React.useEffect(() => { + importFn().then((mod) => + setComponent(() => mod.default || mod), + ); + }, []); + if (!Component) { + return options?.loading ? options.loading() : null; } - // Fallback: return the loading placeholder if component not ready - return options?.loading ? options.loading() : null; - }; + return ; + } + return DynamicWrapper; }, }; });