Skip to content

Commit 0dce78b

Browse files
committed
feat(evaluations): add more categories and reorder schema + types
1 parent 56a1b44 commit 0dce78b

5 files changed

Lines changed: 139 additions & 118 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { useState } from "react";
2+
3+
import type { Category } from "../types.js";
4+
import { CATEGORIES } from "../schema.js";
5+
6+
interface ProtocolCategoriesProps {
7+
categories: Category[];
8+
onUpdate: (categories: Category[]) => void;
9+
}
10+
11+
export default function ProtocolCategories({ categories, onUpdate }: ProtocolCategoriesProps) {
12+
const [isEditing, setIsEditing] = useState(false);
13+
const [editValue, setEditValue] = useState<Category[]>([]);
14+
15+
const startEdit = (): void => {
16+
setEditValue(categories);
17+
setIsEditing(true);
18+
};
19+
20+
const saveEdit = (): void => {
21+
onUpdate(editValue);
22+
setIsEditing(false);
23+
};
24+
25+
const cancelEdit = (): void => {
26+
setIsEditing(false);
27+
};
28+
29+
const toggleCategory = (category: Category, checked: boolean): void => {
30+
const updated = checked ? [...editValue, category] : editValue.filter((c) => c !== category);
31+
setEditValue(updated);
32+
};
33+
34+
return (
35+
<div
36+
className="group cursor-pointer mt-3"
37+
onDoubleClick={startEdit}
38+
onKeyDown={(e) => {
39+
if (e.key === "Enter") startEdit();
40+
}}
41+
role="button"
42+
tabIndex={0}
43+
>
44+
{isEditing ? (
45+
<div className="flex gap-2">
46+
<div className="space-y-2 bg-gray-800 border border-indigo-500 rounded p-2 flex-1">
47+
{CATEGORIES.map((category) => (
48+
<label key={category} className="flex items-center gap-2 cursor-pointer">
49+
<input
50+
type="checkbox"
51+
checked={editValue.includes(category)}
52+
onChange={(e) => {
53+
toggleCategory(category, e.target.checked);
54+
}}
55+
className="w-4 h-4 bg-gray-700 border border-gray-600 rounded focus:outline-none focus:border-indigo-500"
56+
/>
57+
<span className="text-xs text-gray-300">{category}</span>
58+
</label>
59+
))}
60+
</div>
61+
<div className="flex flex-col gap-2">
62+
<button
63+
onClick={saveEdit}
64+
className="text-xs px-2 py-1 bg-indigo-600 text-white rounded hover:bg-indigo-700 whitespace-nowrap"
65+
>
66+
Save
67+
</button>
68+
<button
69+
onClick={cancelEdit}
70+
className="text-xs px-2 py-1 bg-gray-700 text-gray-300 rounded hover:bg-gray-600 whitespace-nowrap"
71+
>
72+
Cancel
73+
</button>
74+
</div>
75+
</div>
76+
) : (
77+
<div>
78+
<p className="text-xs text-gray-400 group-hover:text-gray-300 transition-colors mb-2">Categories</p>
79+
<div className="flex flex-wrap gap-2">
80+
{categories.length > 0 ? (
81+
categories.map((category) => (
82+
<span
83+
key={category}
84+
className="inline-block text-xs bg-gray-800 text-gray-300 rounded px-2 py-0.5 group-hover:bg-gray-700 transition-colors"
85+
>
86+
{category}
87+
</span>
88+
))
89+
) : (
90+
<span className="text-xs text-gray-500">— no categories selected —</span>
91+
)}
92+
</div>
93+
</div>
94+
)}
95+
</div>
96+
);
97+
}

project-evaluations/src/components/ProtocolDetail.tsx

Lines changed: 13 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useState, type Dispatch, type SetStateAction } from "react";
22

3-
import type { Evaluation, Category } from "../types.js";
4-
import { PROPERTY_GROUPS, definitionsByGroup, CATEGORIES } from "../schema.js";
3+
import type { Evaluation } from "../types.js";
4+
import { PROPERTY_GROUPS, definitionsByGroup } from "../schema.js";
55
import PropertyRow from "./PropertyRow.js";
6+
import ProtocolCategories from "./ProtocolCategories.js";
67

78
interface ProtocolDetailProps {
89
evaluations: Evaluation[];
@@ -18,10 +19,9 @@ export default function ProtocolDetail({
1819
setSelectedId,
1920
}: ProtocolDetailProps) {
2021
const [editingField, setEditingField] = useState<string | null>(null);
21-
const [editValues, setEditValues] = useState<{ title: string; description: string; categories: Category[] }>({
22+
const [editValues, setEditValues] = useState<{ title: string; description: string }>({
2223
title: "",
2324
description: "",
24-
categories: [],
2525
});
2626

2727
const evaluation = evaluations.find((entry) => entry.id === selectedId) ?? null;
@@ -34,14 +34,11 @@ export default function ProtocolDetail({
3434
);
3535
}
3636

37-
const startEdit = (field: "title" | "description" | "categories"): void => {
37+
const startEdit = (field: "title" | "description"): void => {
3838
if (field === "title") {
3939
setEditValues((prev) => ({ ...prev, title: evaluation.title }));
40-
} else if (field === "description") {
41-
setEditValues((prev) => ({ ...prev, description: evaluation.description }));
4240
} else {
43-
const cats = evaluation.categories;
44-
setEditValues((prev) => ({ ...prev, categories: cats }));
41+
setEditValues((prev) => ({ ...prev, description: evaluation.description }));
4542
}
4643
setEditingField(field);
4744
};
@@ -53,7 +50,6 @@ export default function ProtocolDetail({
5350
if (entry.id !== selectedId) return entry;
5451
if (editingField === "title") return { ...entry, title: editValues.title };
5552
if (editingField === "description") return { ...entry, description: editValues.description };
56-
if (editingField === "categories") return { ...entry, categories: editValues.categories };
5753
return entry;
5854
}),
5955
);
@@ -189,75 +185,14 @@ export default function ProtocolDetail({
189185
)}
190186
</div>
191187

192-
<div
193-
className="group cursor-pointer mt-3"
194-
onDoubleClick={() => {
195-
startEdit("categories");
188+
<ProtocolCategories
189+
categories={evaluation.categories}
190+
onUpdate={(categories) => {
191+
setEvaluations((previous) =>
192+
previous.map((entry) => (entry.id === selectedId ? { ...entry, categories } : entry)),
193+
);
196194
}}
197-
onKeyDown={(e) => {
198-
if (e.key === "Enter") startEdit("categories");
199-
}}
200-
role="button"
201-
tabIndex={0}
202-
>
203-
{editingField === "categories" ? (
204-
<div className="flex gap-2">
205-
<div className="space-y-2 bg-gray-800 border border-indigo-500 rounded p-2 flex-1">
206-
{CATEGORIES.map((cat) => (
207-
<label key={cat} className="flex items-center gap-2 cursor-pointer">
208-
<input
209-
type="checkbox"
210-
checked={editValues.categories.includes(cat)}
211-
onChange={(e) => {
212-
const updated = e.target.checked
213-
? [...editValues.categories, cat]
214-
: editValues.categories.filter((c) => c !== cat);
215-
setEditValues({ ...editValues, categories: updated });
216-
}}
217-
className="w-4 h-4 bg-gray-700 border border-gray-600 rounded focus:outline-none focus:border-indigo-500"
218-
/>
219-
<span className="text-xs text-gray-300">{cat}</span>
220-
</label>
221-
))}
222-
</div>
223-
<div className="flex flex-col gap-2">
224-
<button
225-
onClick={saveEdit}
226-
className="text-xs px-2 py-1 bg-indigo-600 text-white rounded hover:bg-indigo-700 whitespace-nowrap"
227-
>
228-
Save
229-
</button>
230-
<button
231-
onClick={cancelEdit}
232-
className="text-xs px-2 py-1 bg-gray-700 text-gray-300 rounded hover:bg-gray-600 whitespace-nowrap"
233-
>
234-
Cancel
235-
</button>
236-
</div>
237-
</div>
238-
) : (
239-
<div>
240-
<p className="text-xs text-gray-400 group-hover:text-gray-300 transition-colors mb-2">Categories</p>
241-
<div className="flex flex-wrap gap-2">
242-
{(() => {
243-
const cats = evaluation.categories;
244-
return cats.length > 0 ? (
245-
cats.map((cat) => (
246-
<span
247-
key={cat}
248-
className="inline-block text-xs bg-gray-800 text-gray-300 rounded px-2 py-0.5 group-hover:bg-gray-700 transition-colors"
249-
>
250-
{cat}
251-
</span>
252-
))
253-
) : (
254-
<span className="text-xs text-gray-500">— no categories selected —</span>
255-
);
256-
})()}
257-
</div>
258-
</div>
259-
)}
260-
</div>
195+
/>
261196
</div>
262197

263198
<button

project-evaluations/src/data/evaluations.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"id": "intmax",
2626
"title": "Intmax",
2727
"description": "A stateless zkRollup optimised for private payments, pushing most data off-chain so that on-chain costs are minimal while preserving user-side privacy through client-side proving.",
28-
"categories": ["Shielded pool"],
28+
"categories": ["Shielded pool", "L2"],
2929
"properties": []
3030
}
3131
]

project-evaluations/src/schema.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
1-
import type { PropertyContent, PropertyGroup } from "./types.js";
1+
import type { PropertyContent } from "./types.js";
22

33
export const CATEGORIES = [
44
"Stealth addresses",
5-
"Mixer",
6-
"Shielded pool",
75
"zkWormholes",
8-
"TEEs",
6+
"Mixer",
97
"Homomorphic encryption (FHE - HE)",
10-
"Multi Party Computation (MPC)",
8+
"Edge blockchain",
9+
"L2s",
1110
"Garbled circuits",
11+
"TEEs",
1212
"EVM Obfuscators",
13+
"Wallets",
14+
"Alternative L1",
15+
"Shielded pool",
16+
"Plasma",
17+
"Validium",
1318
"Cross-L1 CEX aggregator and mixer",
19+
"Multi Party Computation (MPC)",
1420
"VPN (Private Intents)",
1521
] as const;
1622

23+
/** Group definitions ordered for display */
24+
export const PROPERTY_GROUPS = [
25+
"Privacy",
26+
"Cost and Performance",
27+
"UX",
28+
"Decentralization & Security",
29+
"Compliance",
30+
"Verifiable",
31+
"State",
32+
"Composability",
33+
] as const;
34+
1735
export const PROPERTY_DEFINITIONS: PropertyContent[] = [
1836
// ── Privacy ──────────────────────────────────────────────────────────────
1937
{
@@ -334,19 +352,7 @@ export const PROPERTY_DEFINITIONS: PropertyContent[] = [
334352
},
335353
];
336354

337-
/** Group definitions ordered for display */
338-
export const PROPERTY_GROUPS: PropertyGroup[] = [
339-
"Privacy",
340-
"Cost and Performance",
341-
"UX",
342-
"Decentralization & Security",
343-
"Compliance",
344-
"Verifiable",
345-
"State",
346-
"Composability",
347-
];
348-
349-
export const definitionsByGroup = (group: PropertyGroup): PropertyContent[] =>
355+
export const definitionsByGroup = (group: (typeof PROPERTY_GROUPS)[number]): PropertyContent[] =>
350356
PROPERTY_DEFINITIONS.filter((d) => d.group === group);
351357

352358
export const definitionByName = (name: string): PropertyContent | undefined =>

project-evaluations/src/types.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,8 @@
1-
export type Category =
2-
| "Stealth addresses"
3-
| "Mixer"
4-
| "Shielded pool"
5-
| "zkWormholes"
6-
| "TEEs"
7-
| "Homomorphic encryption (FHE - HE)"
8-
| "Multi Party Computation (MPC)"
9-
| "Garbled circuits"
10-
| "EVM Obfuscators"
11-
| "Cross-L1 CEX aggregator and mixer"
12-
| "VPN (Private Intents)";
1+
import { type CATEGORIES, type PROPERTY_GROUPS } from "./schema";
132

14-
export type PropertyGroup =
15-
| "Privacy"
16-
| "Cost and Performance"
17-
| "UX"
18-
| "Decentralization & Security"
19-
| "Compliance"
20-
| "Verifiable"
21-
| "State"
22-
| "Composability";
3+
export type Category = (typeof CATEGORIES)[number];
4+
5+
export type PropertyGroup = (typeof PROPERTY_GROUPS)[number];
236

247
export type PropertyInputType = "text" | "number" | "select" | "multi-select";
258

0 commit comments

Comments
 (0)