Skip to content

Commit 0aed6a1

Browse files
refactor(ui): unify proposal stage layouts
and simplify formation Unify proposal Summary/Team/Milestones/Invision sections across PP/Chamber/Formation via ProposalSections.tsx Make proposal summaries single-source-of-truth by deriving them from proposals.ts Remove meaningless “Impact” from proposal mocks and UI Update era baseline to 150 active governors and adjust quorum/upvote-floor numbers + displayed percentages Simplify Formation proposal page by removing redundant lead/audit/proposer CTA block and extra container wrappers
1 parent a71f3d5 commit 0aed6a1

9 files changed

Lines changed: 503 additions & 614 deletions

File tree

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import type { ReactNode } from "react";
2+
3+
import {
4+
AttachmentList,
5+
type AttachmentItem,
6+
} from "@/components/AttachmentList";
7+
import { StatTile } from "@/components/StatTile";
8+
import { Surface } from "@/components/Surface";
9+
import { TitledSurface } from "@/components/TitledSurface";
10+
11+
export type ProposalSummaryStat = {
12+
label: string;
13+
value: ReactNode;
14+
};
15+
16+
export type ProposalTeamMember = {
17+
name: string;
18+
role: string;
19+
};
20+
21+
export type ProposalOpenSlot = {
22+
title: string;
23+
desc: string;
24+
};
25+
26+
export type ProposalMilestoneDetail = {
27+
title: string;
28+
desc: string;
29+
};
30+
31+
export type ProposalInvisionInsight = {
32+
role: string;
33+
bullets: string[];
34+
};
35+
36+
type ProposalSummaryCardProps = {
37+
summary: string;
38+
stats: ProposalSummaryStat[];
39+
overview: string;
40+
executionPlan: string[];
41+
budgetScope: string;
42+
attachments: AttachmentItem[];
43+
};
44+
45+
export function ProposalSummaryCard({
46+
summary,
47+
stats,
48+
overview,
49+
executionPlan,
50+
budgetScope,
51+
attachments,
52+
}: ProposalSummaryCardProps) {
53+
return (
54+
<section className="space-y-3 text-sm text-muted">
55+
<h2 className="text-lg font-semibold text-text">Summary</h2>
56+
<p>{summary}</p>
57+
{stats.length > 0 && (
58+
<div className="grid gap-2 text-sm text-text sm:grid-cols-2 lg:grid-cols-4">
59+
{stats.map((item) => (
60+
<StatTile
61+
key={item.label}
62+
label={item.label}
63+
value={item.value}
64+
className="px-3 py-2"
65+
/>
66+
))}
67+
</div>
68+
)}
69+
<div className="space-y-4 text-text">
70+
<TitledSurface title="Proposal overview">
71+
<p className="text-sm leading-relaxed text-muted">{overview}</p>
72+
</TitledSurface>
73+
<TitledSurface title="Execution plan">
74+
<ul className="list-disc space-y-1 pl-5 text-sm text-muted">
75+
{executionPlan.map((item) => (
76+
<li key={item}>{item}</li>
77+
))}
78+
</ul>
79+
</TitledSurface>
80+
<TitledSurface title="Budget & scope">
81+
<p className="text-sm text-muted">{budgetScope}</p>
82+
</TitledSurface>
83+
<AttachmentList items={attachments} />
84+
</div>
85+
</section>
86+
);
87+
}
88+
89+
type ProposalTeamMilestonesCardProps = {
90+
teamLocked: ProposalTeamMember[];
91+
openSlots: ProposalOpenSlot[];
92+
milestonesDetail: ProposalMilestoneDetail[];
93+
};
94+
95+
export function ProposalTeamMilestonesCard({
96+
teamLocked,
97+
openSlots,
98+
milestonesDetail,
99+
}: ProposalTeamMilestonesCardProps) {
100+
return (
101+
<section className="space-y-4 text-sm text-muted">
102+
<h2 className="text-lg font-semibold text-text">Team & milestones</h2>
103+
<div className="grid gap-3 lg:grid-cols-2">
104+
<TitledSurface title="Team (locked)">
105+
<ul className="space-y-2 text-sm text-muted">
106+
{teamLocked.map((member) => (
107+
<Surface
108+
key={member.name}
109+
as="li"
110+
variant="panel"
111+
radius="xl"
112+
shadow="control"
113+
className="flex items-center justify-between px-3 py-2"
114+
>
115+
<span className="font-semibold text-text">{member.name}</span>
116+
<span className="text-xs text-muted">{member.role}</span>
117+
</Surface>
118+
))}
119+
{teamLocked.length === 0 && (
120+
<Surface
121+
as="li"
122+
variant="panel"
123+
radius="xl"
124+
borderStyle="dashed"
125+
className="px-3 py-3 text-center text-xs text-muted"
126+
>
127+
No locked team members yet.
128+
</Surface>
129+
)}
130+
</ul>
131+
</TitledSurface>
132+
133+
<TitledSurface title="Open slots (positions)">
134+
<ul className="space-y-2 text-sm text-muted">
135+
{openSlots.map((slot) => (
136+
<Surface
137+
key={slot.title}
138+
as="li"
139+
variant="panel"
140+
radius="xl"
141+
shadow="control"
142+
className="px-3 py-2"
143+
>
144+
<p className="font-semibold text-text">{slot.title}</p>
145+
<p className="text-xs text-muted">{slot.desc}</p>
146+
</Surface>
147+
))}
148+
{openSlots.length === 0 && (
149+
<Surface
150+
as="li"
151+
variant="panel"
152+
radius="xl"
153+
borderStyle="dashed"
154+
className="px-3 py-3 text-center text-xs text-muted"
155+
>
156+
No open slots.
157+
</Surface>
158+
)}
159+
</ul>
160+
</TitledSurface>
161+
</div>
162+
163+
<TitledSurface title="Milestones">
164+
<ul className="space-y-2 text-sm text-muted">
165+
{milestonesDetail.map((ms) => (
166+
<Surface
167+
key={ms.title}
168+
as="li"
169+
variant="panel"
170+
radius="xl"
171+
shadow="control"
172+
className="px-3 py-2"
173+
>
174+
<p className="font-semibold text-text">{ms.title}</p>
175+
<p className="text-xs text-muted">{ms.desc}</p>
176+
</Surface>
177+
))}
178+
{milestonesDetail.length === 0 && (
179+
<Surface
180+
as="li"
181+
variant="panel"
182+
radius="xl"
183+
borderStyle="dashed"
184+
className="px-3 py-3 text-center text-xs text-muted"
185+
>
186+
No milestones defined yet.
187+
</Surface>
188+
)}
189+
</ul>
190+
</TitledSurface>
191+
</section>
192+
);
193+
}
194+
195+
type ProposalInvisionInsightCardProps = {
196+
insight: ProposalInvisionInsight;
197+
};
198+
199+
export function ProposalInvisionInsightCard({
200+
insight,
201+
}: ProposalInvisionInsightCardProps) {
202+
return (
203+
<section className="space-y-3 text-sm text-text">
204+
<h2 className="text-lg font-semibold text-text">Invision insight</h2>
205+
<p className="text-sm font-semibold text-text">Role: {insight.role}</p>
206+
<ul className="list-disc space-y-2 pl-5 text-muted">
207+
{insight.bullets.map((item) => (
208+
<li key={item}>{item}</li>
209+
))}
210+
</ul>
211+
</section>
212+
);
213+
}

src/data/mock/feed.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const feedItems: FeedItem[] = [
3838
{
3939
title: "Voting quorum",
4040
description: "Strict 33% active governors",
41-
value: "Met · 42%",
41+
value: "Met · 35%",
4242
tone: "ok",
4343
},
4444
{

0 commit comments

Comments
 (0)