Confirm stakeholder template
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useMediaQuery } from "../../hooks/useMediaQuery";
|
||||
import HeaderLockup from "../../components/type/HeaderLockup";
|
||||
import MultiSelect from "../../components/controls/MultiSelect";
|
||||
import Alert from "../../components/modals/Alert";
|
||||
import type { ChipOption } from "../../components/controls/MultiSelect/MultiSelect.types";
|
||||
|
||||
const TITLE =
|
||||
"Do other stakeholders need to be involved in creating your community?";
|
||||
|
||||
const DESCRIPTION =
|
||||
"Adding people at this step will invite them to see your proposed CommunityRule and make their own proposals.";
|
||||
|
||||
const DRAFT_TOAST_TITLE =
|
||||
"Congratulations! You've drafted your CommunityRule!";
|
||||
|
||||
/**
|
||||
* Confirm stakeholders step — stacked lockup + MultiSelect (not split columns).
|
||||
* Figma: 21104-46594.
|
||||
*/
|
||||
export default function ConfirmStakeholdersPage() {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [toastDismissed, setToastDismissed] = useState(false);
|
||||
const [stakeholderOptions, setStakeholderOptions] = useState<ChipOption[]>([]);
|
||||
const isMdOrLarger = useMediaQuery("(min-width: 640px)");
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: defer layout breakpoint until after mount to prevent flash
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
const effectiveMdOrLarger = !isMounted || isMdOrLarger;
|
||||
|
||||
const handleAddStakeholder = () => {
|
||||
setStakeholderOptions((prev) => [
|
||||
...prev,
|
||||
{ id: crypto.randomUUID(), label: "", state: "Custom" },
|
||||
]);
|
||||
};
|
||||
|
||||
const handleCustomChipConfirm = (chipId: string, value: string) => {
|
||||
setStakeholderOptions((prev) =>
|
||||
prev.map((opt) =>
|
||||
opt.id === chipId
|
||||
? { ...opt, label: value, state: "Selected" }
|
||||
: opt,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleCustomChipClose = (chipId: string) => {
|
||||
setStakeholderOptions((prev) => prev.filter((opt) => opt.id !== chipId));
|
||||
};
|
||||
|
||||
const handleChipClick = (chipId: string) => {
|
||||
setStakeholderOptions((prev) => prev.filter((opt) => opt.id !== chipId));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full flex flex-col items-center px-[var(--spacing-measures-spacing-500,20px)] md:px-[var(--measures-spacing-1800,64px)] pb-28 md:pb-32">
|
||||
<div className="flex w-full max-w-[640px] flex-col gap-[var(--measures-spacing-300,12px)] items-start">
|
||||
<div className="flex w-full flex-col gap-[var(--measures-spacing-200,8px)] py-[12px]">
|
||||
<HeaderLockup
|
||||
title={TITLE}
|
||||
description={DESCRIPTION}
|
||||
justification="left"
|
||||
size={effectiveMdOrLarger ? "L" : "M"}
|
||||
/>
|
||||
</div>
|
||||
<MultiSelect
|
||||
formHeader={false}
|
||||
showHelpIcon={false}
|
||||
size="S"
|
||||
options={stakeholderOptions}
|
||||
onChipClick={handleChipClick}
|
||||
onAddClick={handleAddStakeholder}
|
||||
onCustomChipConfirm={handleCustomChipConfirm}
|
||||
onCustomChipClose={handleCustomChipClose}
|
||||
addButton
|
||||
addButtonText="Add stakeholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!toastDismissed && (
|
||||
<div
|
||||
className="fixed left-1/2 z-10 w-[min(640px,calc(100%-2.5rem))] max-w-[640px] -translate-x-1/2 bottom-[5.25rem] md:bottom-[5.5rem]"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
>
|
||||
<Alert
|
||||
type="banner"
|
||||
status="positive"
|
||||
title={DRAFT_TOAST_TITLE}
|
||||
hasLeadingIcon={false}
|
||||
hasBodyText={false}
|
||||
onClose={() => setToastDismissed(true)}
|
||||
className="w-full !px-[var(--space-600,24px)] !py-[var(--space-400,16px)] md:!py-4"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -87,7 +87,9 @@ function CreateFlowLayoutContent({ children }: { children: ReactNode }) {
|
||||
>
|
||||
{currentStep === "final-review"
|
||||
? "Finalize CommunityRule"
|
||||
: "Next"}
|
||||
: currentStep === "confirm-stakeholders"
|
||||
? "Confirm Stakeholders"
|
||||
: "Next"}
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
|
||||
+69
-21
@@ -1,9 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useMemo, type Dispatch, type SetStateAction } from "react";
|
||||
import { useMediaQuery } from "../../hooks/useMediaQuery";
|
||||
import HeaderLockup from "../../components/type/HeaderLockup";
|
||||
import MultiSelect from "../../components/controls/MultiSelect";
|
||||
import type { ChipOption } from "../../components/controls/MultiSelect/MultiSelect.types";
|
||||
|
||||
function createListCustomHandlers(
|
||||
setList: Dispatch<SetStateAction<ChipOption[]>>,
|
||||
confirmState: "Unselected" | "Selected",
|
||||
) {
|
||||
return {
|
||||
onAddClick: () =>
|
||||
setList((prev) => [
|
||||
...prev,
|
||||
{ id: crypto.randomUUID(), label: "", state: "Custom" },
|
||||
]),
|
||||
onCustomChipConfirm: (chipId: string, value: string) =>
|
||||
setList((prev) =>
|
||||
prev.map((opt) =>
|
||||
opt.id === chipId
|
||||
? { ...opt, label: value, state: confirmState }
|
||||
: opt,
|
||||
),
|
||||
),
|
||||
onCustomChipClose: (chipId: string) =>
|
||||
setList((prev) => prev.filter((o) => o.id !== chipId)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Select page for the create flow
|
||||
@@ -24,30 +48,48 @@ export default function SelectPage() {
|
||||
|
||||
const effectiveMdOrLarger = !isMounted || isMdOrLarger;
|
||||
|
||||
// Sample options for MultiSelect components
|
||||
const [communitySizeOptions, setCommunitySizeOptions] = useState([
|
||||
{ id: "1", label: "1 member", state: "Unselected" as const },
|
||||
{ id: "2", label: "2-10 members", state: "Unselected" as const },
|
||||
{ id: "3", label: "10-24 members", state: "Unselected" as const },
|
||||
{ id: "4", label: "24-64 members", state: "Unselected" as const },
|
||||
{ id: "5", label: "64-128 members", state: "Unselected" as const },
|
||||
{ id: "6", label: "125-1000 members", state: "Unselected" as const },
|
||||
{ id: "7", label: "1000+ members", state: "Unselected" as const },
|
||||
const [communitySizeOptions, setCommunitySizeOptions] = useState<ChipOption[]>(
|
||||
[
|
||||
{ id: "1", label: "1 member", state: "Unselected" },
|
||||
{ id: "2", label: "2-10 members", state: "Unselected" },
|
||||
{ id: "3", label: "10-24 members", state: "Unselected" },
|
||||
{ id: "4", label: "24-64 members", state: "Unselected" },
|
||||
{ id: "5", label: "64-128 members", state: "Unselected" },
|
||||
{ id: "6", label: "125-1000 members", state: "Unselected" },
|
||||
{ id: "7", label: "1000+ members", state: "Unselected" },
|
||||
],
|
||||
);
|
||||
|
||||
const [organizationTypeOptions, setOrganizationTypeOptions] = useState<
|
||||
ChipOption[]
|
||||
>([
|
||||
{ id: "1", label: "Non-profit", state: "Unselected" },
|
||||
{ id: "2", label: "For-profit", state: "Unselected" },
|
||||
{ id: "3", label: "Community", state: "Unselected" },
|
||||
{ id: "4", label: "Educational", state: "Unselected" },
|
||||
]);
|
||||
|
||||
const [organizationTypeOptions, setOrganizationTypeOptions] = useState([
|
||||
{ id: "1", label: "Non-profit", state: "Unselected" as const },
|
||||
{ id: "2", label: "For-profit", state: "Unselected" as const },
|
||||
{ id: "3", label: "Community", state: "Unselected" as const },
|
||||
{ id: "4", label: "Educational", state: "Unselected" as const },
|
||||
const [governanceStyleOptions, setGovernanceStyleOptions] = useState<
|
||||
ChipOption[]
|
||||
>([
|
||||
{ id: "1", label: "Democratic", state: "Unselected" },
|
||||
{ id: "2", label: "Consensus", state: "Unselected" },
|
||||
{ id: "3", label: "Hierarchical", state: "Unselected" },
|
||||
{ id: "4", label: "Flat", state: "Unselected" },
|
||||
]);
|
||||
|
||||
const [governanceStyleOptions, setGovernanceStyleOptions] = useState([
|
||||
{ id: "1", label: "Democratic", state: "Unselected" as const },
|
||||
{ id: "2", label: "Consensus", state: "Unselected" as const },
|
||||
{ id: "3", label: "Hierarchical", state: "Unselected" as const },
|
||||
{ id: "4", label: "Flat", state: "Unselected" as const },
|
||||
]);
|
||||
const communityCustomHandlers = useMemo(
|
||||
() => createListCustomHandlers(setCommunitySizeOptions, "Unselected"),
|
||||
[],
|
||||
);
|
||||
const organizationCustomHandlers = useMemo(
|
||||
() => createListCustomHandlers(setOrganizationTypeOptions, "Unselected"),
|
||||
[],
|
||||
);
|
||||
const governanceCustomHandlers = useMemo(
|
||||
() => createListCustomHandlers(setGovernanceStyleOptions, "Unselected"),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCommunitySizeClick = (chipId: string) => {
|
||||
setCommunitySizeOptions((prev) =>
|
||||
@@ -110,6 +152,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={communitySizeOptions}
|
||||
onChipClick={handleCommunitySizeClick}
|
||||
{...communityCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
@@ -118,6 +161,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={organizationTypeOptions}
|
||||
onChipClick={handleOrganizationTypeClick}
|
||||
{...organizationCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
@@ -126,6 +170,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={governanceStyleOptions}
|
||||
onChipClick={handleGovernanceStyleClick}
|
||||
{...governanceCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
@@ -148,6 +193,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={communitySizeOptions}
|
||||
onChipClick={handleCommunitySizeClick}
|
||||
{...communityCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
@@ -156,6 +202,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={organizationTypeOptions}
|
||||
onChipClick={handleOrganizationTypeClick}
|
||||
{...organizationCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
@@ -164,6 +211,7 @@ export default function SelectPage() {
|
||||
size="S"
|
||||
options={governanceStyleOptions}
|
||||
onChipClick={handleGovernanceStyleClick}
|
||||
{...governanceCustomHandlers}
|
||||
addButton={true}
|
||||
addButtonText="Add organization type"
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@ export type CreateFlowStep =
|
||||
| "review"
|
||||
| "cards"
|
||||
| "right-rail"
|
||||
| "confirm-stakeholders"
|
||||
| "final-review"
|
||||
| "completed";
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export const FLOW_STEP_ORDER: readonly CreateFlowStep[] = [
|
||||
"review",
|
||||
"cards",
|
||||
"right-rail",
|
||||
"confirm-stakeholders",
|
||||
"final-review",
|
||||
"completed",
|
||||
] as const;
|
||||
|
||||
Reference in New Issue
Block a user