Fix magic-link verify URLs in email and related create-flow QA #70

Merged
an.di merged 4 commits from adilallo/fix/QaFeedback into main 2026-09-01 16:09:31 +00:00
13 changed files with 153 additions and 11 deletions
Showing only changes of commit a820739d07 - Show all commits
@@ -15,7 +15,7 @@ import type { CoreValueDetailEntry } from "../../types";
export interface CoreValueEditFieldsProps { export interface CoreValueEditFieldsProps {
value: CoreValueDetailEntry; value: CoreValueDetailEntry;
onChange: (_next: CoreValueDetailEntry) => void; onChange: (_next: CoreValueDetailEntry) => void;
/** Disable meaning/signals. Create-flow core-values omits this; final-review locks until Customize. */ /** Disable meaning/signals. Facet and final-review callers omit this so fields stay editable on open. */
readOnly?: boolean; readOnly?: boolean;
} }
@@ -10,8 +10,8 @@
* *
* Card click opens the Figma create modal (node `20246-15829`) with three * Card click opens the Figma create modal (node `20246-15829`) with three
* editable sections rendered by {@link CommunicationMethodEditFields}. The primary * editable sections rendered by {@link CommunicationMethodEditFields}. The primary
* action is **Add Platform** for an unselected card; a selected card in view mode has * action is **Add Platform** for an unselected card and **Save** for a selected
* no footer primary — **Remove** is available from the kebab (same behavior as legacy * card. **Remove** is available from the kebab (same behavior as legacy
* footer remove via {@link removeMethodCardFromFacetSelection}). * footer remove via {@link removeMethodCardFromFacetSelection}).
*/ */
@@ -216,7 +216,7 @@ export function CommunicationMethodsScreen() {
methodId: pendingCardId, methodId: pendingCardId,
meta: state.customMethodCardMetaById, meta: state.customMethodCardMetaById,
fieldBlocksById: state.customMethodCardFieldBlocksById, fieldBlocksById: state.customMethodCardFieldBlocksById,
modalEditUnlocked: false, modalEditUnlocked: true,
draftFieldBlocks, draftFieldBlocks,
customFacetDetailsMatchPreset, customFacetDetailsMatchPreset,
}), }),
@@ -217,7 +217,7 @@ export function ConflictManagementScreen() {
methodId: pendingCardId, methodId: pendingCardId,
meta: state.customMethodCardMetaById, meta: state.customMethodCardMetaById,
fieldBlocksById: state.customMethodCardFieldBlocksById, fieldBlocksById: state.customMethodCardFieldBlocksById,
modalEditUnlocked: false, modalEditUnlocked: true,
draftFieldBlocks, draftFieldBlocks,
customFacetDetailsMatchPreset, customFacetDetailsMatchPreset,
}), }),
@@ -214,7 +214,7 @@ export function MembershipMethodsScreen() {
methodId: pendingCardId, methodId: pendingCardId,
meta: state.customMethodCardMetaById, meta: state.customMethodCardMetaById,
fieldBlocksById: state.customMethodCardFieldBlocksById, fieldBlocksById: state.customMethodCardFieldBlocksById,
modalEditUnlocked: false, modalEditUnlocked: true,
draftFieldBlocks, draftFieldBlocks,
customFacetDetailsMatchPreset, customFacetDetailsMatchPreset,
}), }),
@@ -250,7 +250,7 @@ export function DecisionApproachesScreen() {
methodId: pendingCardId, methodId: pendingCardId,
meta: state.customMethodCardMetaById, meta: state.customMethodCardMetaById,
fieldBlocksById: state.customMethodCardFieldBlocksById, fieldBlocksById: state.customMethodCardFieldBlocksById,
modalEditUnlocked: false, modalEditUnlocked: true,
draftFieldBlocks, draftFieldBlocks,
customFacetDetailsMatchPreset, customFacetDetailsMatchPreset,
}), }),
@@ -80,7 +80,8 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
}, },
}; };
// State styles (embedded: Figma 20736-12668 borderless, darker grey block, white text) // Embedded (Figma 20736-12668): borderless grey block; default copy is
// tertiary, primary on focus so seeded modal fields stay muted until edit.
const getStateStyles = (): { const getStateStyles = (): {
textarea: string; textarea: string;
label: string; label: string;
@@ -89,13 +90,13 @@ const TextAreaContainer = forwardRef<HTMLTextAreaElement, TextAreaProps>(
if (disabled) { if (disabled) {
return { return {
textarea: textarea:
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] cursor-not-allowed opacity-60", "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] cursor-not-allowed opacity-60",
label: "text-[var(--color-content-default-secondary)]", label: "text-[var(--color-content-default-secondary)]",
}; };
} }
return { return {
textarea: textarea:
"border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset", "border-0 bg-[var(--color-surface-default-secondary)] text-[var(--color-content-default-tertiary,#b4b4b4)] placeholder:text-[var(--color-content-default-tertiary,#b4b4b4)] focus:text-[var(--color-content-default-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-default-tertiary)] focus:ring-inset",
label: "text-[var(--color-content-default-secondary)]", label: "text-[var(--color-content-default-secondary)]",
}; };
} }
@@ -48,7 +48,8 @@ export interface TextAreaProps extends Omit<
showHelpIcon?: boolean; showHelpIcon?: boolean;
/** /**
* Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668): * Visual appearance. "embedded" matches Create modal sections (Figma 20736-12668):
* borderless, darker grey background, white text. "default" is standard bordered input. * borderless, darker grey background, tertiary text in default (primary on
* focus). "default" is the standard bordered input.
* @default "default" * @default "default"
*/ */
appearance?: TextAreaAppearanceValue; appearance?: TextAreaAppearanceValue;
@@ -29,6 +29,9 @@ import { isCustomMethodCardId } from "./isCustomMethodCardId";
* so meta-only wizard cards show policy copy instead of empty section editors. * so meta-only wizard cards show policy copy instead of empty section editors.
* Pass `customFacetDetailsMatchPreset: false` when the caller knows facet details * Pass `customFacetDetailsMatchPreset: false` when the caller knows facet details
* were edited or cloned from a filled preset. * were edited or cloned from a filled preset.
*
* Create-flow facet screens and final-review pass `modalEditUnlocked: true` so
* section fields stay editable on open (Customize is the kebab wizard).
*/ */
export function usesWizardFieldBlocksModalBody(args: { export function usesWizardFieldBlocksModalBody(args: {
methodId: string; methodId: string;
@@ -0,0 +1,37 @@
import { describe, it, expect, afterEach } from "vitest";
import {
renderWithProviders as render,
screen,
cleanup,
within,
} from "../utils/test-utils";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { ConflictManagementScreen } from "../../app/(app)/create/screens/card/ConflictManagementScreen";
afterEach(() => {
cleanup();
});
describe("ConflictManagementScreen", () => {
it("opens section fields editable without Customize", async () => {
render(<ConflictManagementScreen />);
fireEvent.click(
screen.getAllByRole("button", {
name: /Peer Mediation: Trained members/,
})[0],
);
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
);
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
});
});
@@ -0,0 +1,37 @@
import { describe, it, expect, afterEach } from "vitest";
import {
renderWithProviders as render,
screen,
cleanup,
within,
} from "../utils/test-utils";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { MembershipMethodsScreen } from "../../app/(app)/create/screens/card/MembershipMethodsScreen";
afterEach(() => {
cleanup();
});
describe("MembershipMethodsScreen", () => {
it("opens section fields editable without Customize", async () => {
render(<MembershipMethodsScreen />);
fireEvent.click(
screen.getAllByRole("button", {
name: /Open Access: Maximum inclusion/,
})[0],
);
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
);
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
});
});
+13
View File
@@ -42,4 +42,17 @@ describe("TextArea appearance", () => {
expect(textarea).toBeInTheDocument(); expect(textarea).toBeInTheDocument();
expect(textarea).toHaveClass("border-0"); expect(textarea).toHaveClass("border-0");
}); });
it("uses tertiary text in the embedded default state and primary on focus", () => {
renderWithProviders(
<TextArea label="Notes" value="Some text" appearance="embedded" />,
);
const textarea = screen.getByRole("textbox", { name: /notes/i });
expect(textarea).toHaveClass(
"text-[var(--color-content-default-tertiary,#b4b4b4)]",
);
expect(textarea).toHaveClass(
"focus:text-[var(--color-content-default-primary)]",
);
});
}); });
@@ -126,6 +126,33 @@ describe("Create flow communication-methods page", () => {
expect(screen.getByRole("menuitem", { name: "Customize" })).toBeInTheDocument(); expect(screen.getByRole("menuitem", { name: "Customize" })).toBeInTheDocument();
}); });
test("meta-only custom policy opens with editable section fields", async () => {
const user = userEvent.setup();
render(
<CommunicationMethodsScreenWithState
initial={{
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
customMethodCardMetaById: {
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Desc" },
},
}}
/>,
);
await user.click(
screen.getAllByRole("button", { name: /My policy: Desc/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textboxes = within(dialog).getAllByRole("textbox");
expect(textboxes.length).toBeGreaterThan(0);
for (const field of textboxes) {
expect(field).not.toBeDisabled();
}
expect(
within(dialog).queryByText("No custom fields yet."),
).not.toBeInTheDocument();
});
test("renders without error", () => { test("renders without error", () => {
render(<CommunicationMethodsScreen />); render(<CommunicationMethodsScreen />);
+23
View File
@@ -201,6 +201,29 @@ describe("Create flow decision-approaches page", () => {
).toHaveTextContent("SELECTED"); ).toHaveTextContent("SELECTED");
}); });
test("opens approach fields editable without Customize", async () => {
const user = userEvent.setup();
render(<DecisionApproachesScreen />);
await user.click(
screen.getByRole("button", {
name: /Lazy Consensus: A decision is assumed approved/,
}),
);
const dialog = await screen.findByRole("dialog");
const textboxes = within(dialog).getAllByRole("textbox");
expect(textboxes.length).toBeGreaterThan(0);
for (const field of textboxes) {
expect(field).not.toBeDisabled();
}
await user.click(
within(dialog).getByRole("button", { name: "More options" }),
);
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
});
test("clicking a card opens the create modal and confirming selects it", async () => { test("clicking a card opens the create modal and confirming selects it", async () => {
const user = userEvent.setup(); const user = userEvent.setup();
render(<DecisionApproachesScreen />); render(<DecisionApproachesScreen />);