Skip to content

Commit 0e26a71

Browse files
authored
Merge pull request #103 from DemocracyDevelopers/release
Release
2 parents 685c0a7 + 3628bb5 commit 0e26a71

116 files changed

Lines changed: 18382 additions & 1102 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,10 @@ yarn-error.log*
3636
next-env.d.ts
3737

3838
# vscode
39-
.vscode
39+
.vscode
40+
41+
# idea
42+
.idea
43+
44+
# pnpm-lock
45+
pnpm-lock.yaml

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
## Team Member
66

77
| Name | Roles |
8-
|-------------|-------------------------------------|
8+
| ----------- | ----------------------------------- |
99
| Aaron Lee | Technical Officer/Client Liaison |
1010
| Emily Zhang | Project Manager/Front-end Developer |
1111
| Yilin Lyu | Scrum Mater/Front-end Developer |
@@ -19,14 +19,25 @@
1919

2020
# Technologies Used
2121

22-
- react
23-
- typescript
24-
- nextjs
25-
- tailwind css
26-
- shadcn
27-
- zustand
28-
- jest
29-
- husky
22+
- React (^18) - JavaScript library for building user interfaces
23+
- TypeScript (^5) - Typed superset of JavaScript
24+
- Next.js (14.2.5) - React framework for production applications
25+
- Tailwind CSS (^3.4.1) - Utility-first CSS framework
26+
- shadcn/ui - Re-usable components built using Radix UI and Tailwind CSS
27+
- Zustand (^4.5.4) - Small, fast, and scalable state management
28+
- Jest (^29.7.0) - JavaScript testing framework
29+
- Husky (^9.1.4) - Git hooks made easy
30+
- @mdi/js: ^7.4.47 - Material Design Icons JavaScript library
31+
- @mdi/react: ^1.6.1 - Material Design Icons React components
32+
- lucide: ^0.439.0 - Beautiful & consistent icon toolkit
33+
- lucide-react: ^0.427.0 - Lucide icons for React
34+
- react-icons: ^5.3.0 - Popular icon libraries for React
35+
36+
Icons
37+
38+
Lucide (lucide ^0.439.0, lucide-react ^0.427.0) - Beautiful & consistent icon toolkit
39+
MDI (@mdi/js ^7.4.47, @mdi/react ^1.6.1) - Material Design Icons
40+
React Icons (react-icons ^5.3.0) - Popular icon libraries including support for Iconfont
3041

3142
# Environment and Tools
3243

app/ClientTourProvider.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { TourProvider } from "@reactour/tour";
5+
import { TourStepWatcher } from "./TourStepWatcher";
6+
import { TourSyncWatcher } from "./TourSyncWatcher";
7+
import { CloseTourOnRouteChange } from "@/hooks/CloseTourOnRouteChange";
8+
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
9+
import { X } from "lucide-react";
10+
11+
interface ClientTourProviderProps {
12+
readonly children: React.ReactNode;
13+
}
14+
15+
/**
16+
* ClientTourProvider wraps the application with the Reactour TourProvider.
17+
*/
18+
export default function ClientTourProvider({
19+
children,
20+
}: ClientTourProviderProps) {
21+
const disableBody = (target: any) => disableBodyScroll(target);
22+
const enableBody = (target: any) => enableBodyScroll(target);
23+
return (
24+
<TourProvider
25+
steps={[]}
26+
showNavigation={false}
27+
showDots={false}
28+
disableInteraction={true}
29+
disableKeyboardNavigation={["esc", "right", "left"]}
30+
onClickClose={({ setIsOpen }) => setIsOpen(false)}
31+
onClickMask={() => {}}
32+
styles={{
33+
popover: (base) => ({ ...base, borderRadius: "12px" }),
34+
}}
35+
scrollSmooth={true}
36+
afterOpen={disableBody}
37+
beforeClose={enableBody}
38+
components={{
39+
Close: ({ onClick }) => (
40+
<button
41+
onClick={onClick}
42+
className="absolute top-2 right-2 text-gray-700 hover:text-gray-900 bg-white rounded-full p-2 shadow-md"
43+
aria-label="Close"
44+
>
45+
<X size={18} />
46+
</button>
47+
),
48+
}}
49+
>
50+
<CloseTourOnRouteChange />
51+
<TourSyncWatcher />
52+
<TourStepWatcher />
53+
{children}
54+
</TourProvider>
55+
);
56+
}

app/TourStepWatcher.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { useEffect } from "react";
2+
import { useTour } from "@reactour/tour";
3+
import { expandTreeLayerByLayer } from "@/utils/treeActions";
4+
5+
/**
6+
* Defines side effects associated with specific tour steps.
7+
* When a tour step is reached, the corresponding effect (if defined) will be triggered.
8+
*/
9+
const stepEffects: Record<number, () => void> = {
10+
6: () => {
11+
expandTreeLayerByLayer();
12+
},
13+
};
14+
15+
/**
16+
* TourStepWatcher
17+
* A non-visual component that watches the current tour step and
18+
* triggers any side effects mapped in `stepEffects`.
19+
*/
20+
export const TourStepWatcher = () => {
21+
const { currentStep } = useTour();
22+
23+
useEffect(() => {
24+
const effect = stepEffects[currentStep];
25+
if (effect) effect();
26+
}, [currentStep]);
27+
28+
return null;
29+
};

app/TourSyncWatcher.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import { useTour, StepType } from "@reactour/tour";
5+
import { useFileDataStore } from "@/store/fileData";
6+
import { getCandidateNumber } from "@/app/explain-assertions/components/explain-process";
7+
import { getSteps } from "@/app/steps";
8+
9+
export const TourSyncWatcher = () => {
10+
const fileData = useFileDataStore((state) => state.fileData);
11+
const { setSteps, setCurrentStep } = useTour();
12+
13+
useEffect(() => {
14+
if (!fileData) return;
15+
16+
const count = getCandidateNumber(fileData);
17+
if (typeof count === "number") {
18+
const newSteps = getSteps(count);
19+
setSteps?.(newSteps as StepType[]);
20+
setCurrentStep?.(0); // Reset to the first step
21+
}
22+
}, [fileData, setSteps]);
23+
24+
return null;
25+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useState } from "react";
2+
import {
3+
Tooltip,
4+
TooltipTrigger,
5+
TooltipContent,
6+
TooltipProvider,
7+
} from "@/components/ui/tooltip";
8+
import { FaInfoCircle } from "react-icons/fa";
9+
import Link from "next/link";
10+
11+
interface TooltipWithIconProps {
12+
title: string;
13+
linkText: string;
14+
linkHref: string;
15+
description: string;
16+
}
17+
18+
const TooltipWithIcon: React.FC<TooltipWithIconProps> = ({
19+
title,
20+
linkText,
21+
linkHref,
22+
description,
23+
}) => {
24+
const [isOpen, setIsOpen] = useState(false);
25+
26+
const handleClick = () => {
27+
setIsOpen(!isOpen);
28+
};
29+
30+
const handleMouseEnter = () => {
31+
setIsOpen(true);
32+
};
33+
34+
return (
35+
<TooltipProvider>
36+
<Tooltip
37+
open={isOpen}
38+
onOpenChange={setIsOpen}
39+
disableHoverableContent={false}
40+
>
41+
<TooltipTrigger asChild>
42+
<span
43+
className="ml-2 relative"
44+
onClick={handleClick}
45+
onMouseEnter={handleMouseEnter}
46+
>
47+
{/* Information Icon */}
48+
<FaInfoCircle className="text-primary cursor-pointer" size={18} />
49+
</span>
50+
</TooltipTrigger>
51+
52+
<TooltipContent
53+
side="right"
54+
align="center"
55+
className="bg-background text-primary rounded-lg shadow-lg p-4 w-64"
56+
>
57+
<div className="font-bold mb-2">{title}</div>
58+
<div className="font-normal">
59+
{description}{" "}
60+
<Link
61+
href={linkHref}
62+
target="_blank"
63+
rel="noopener noreferrer"
64+
className="text-blue-500 hover:underline"
65+
>
66+
{linkText}
67+
</Link>
68+
.
69+
</div>
70+
</TooltipContent>
71+
</Tooltip>
72+
</TooltipProvider>
73+
);
74+
};
75+
76+
export default TooltipWithIcon;
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import React, { useState, useRef } from "react";
2+
import { Avatar } from "@/components/ui/avatar";
3+
import { Button } from "@/components/ui/button";
4+
import AuditProgressAnimation from "./audit-progress-animation";
5+
6+
interface Assertion {
7+
index: number;
8+
content: string;
9+
type: string;
10+
winner: number; // It should be the winner's candidate id
11+
name: string; // candidate name
12+
}
13+
14+
interface AssertionTableProps {
15+
assertions: Assertion[];
16+
winnerName: string;
17+
isValid: boolean;
18+
}
19+
20+
const pageSize = 4;
21+
22+
const AssertionTable: React.FC<AssertionTableProps> = ({
23+
assertions,
24+
winnerName,
25+
isValid,
26+
}) => {
27+
const [currentPage, setCurrentPage] = useState(1);
28+
const totalPages = Math.ceil(assertions.length / pageSize);
29+
const tableRef = useRef<HTMLDivElement>(null);
30+
const paginationRef = useRef<HTMLDivElement>(null);
31+
32+
const animationSpacerRef = useRef<HTMLDivElement>(null);
33+
34+
const paged = assertions.slice(
35+
(currentPage - 1) * pageSize,
36+
currentPage * pageSize,
37+
);
38+
39+
const handlePageChange = (page: number) => {
40+
if (page >= 1 && page <= totalPages) {
41+
setCurrentPage(page);
42+
tableRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
43+
}
44+
};
45+
46+
const [showAnimation, setShowAnimation] = useState(true);
47+
48+
return (
49+
<div className="flex flex-col h-full">
50+
<div ref={tableRef} className="flex-1 overflow-y-auto border rounded-lg">
51+
<table className="min-w-full table-auto">
52+
<thead className="sticky top-0 bg-background z-10">
53+
<tr>
54+
<th className="px-4 py-2 border-b">Index</th>
55+
<th className="px-4 py-2 border-b">Content</th>
56+
<th className="px-4 py-2 border-b">Type</th>
57+
</tr>
58+
</thead>
59+
<tbody>
60+
{paged.map((a) => (
61+
<tr key={a.index} className="h-[48px]">
62+
<td className="px-4 py-2 text-center border-b">{a.index}</td>
63+
<td className="px-4 py-2 text-left border-b">
64+
<div className="flex items-center">
65+
<Avatar candidateId={a.winner} className="mr-2" />
66+
<span>{a.content}</span>
67+
</div>
68+
</td>
69+
<td className="px-4 py-2 text-center border-b">{a.type}</td>
70+
</tr>
71+
))}
72+
</tbody>
73+
</table>
74+
</div>
75+
76+
<div ref={paginationRef} className="mt-2 pt-2 border-t">
77+
<div className="flex justify-center items-center gap-2 flex-wrap">
78+
<Button
79+
size="sm"
80+
variant="outline"
81+
disabled={currentPage === 1}
82+
onClick={() => handlePageChange(currentPage - 1)}
83+
>
84+
Prev
85+
</Button>
86+
87+
<div className="flex items-center gap-1 text-sm text-gray-700">
88+
Page
89+
<select
90+
value={currentPage}
91+
onChange={(e) => handlePageChange(Number(e.target.value))}
92+
className="border border-gray-300 rounded px-2 py-1 text-sm"
93+
>
94+
{(() => {
95+
const rangeSize = 5;
96+
const half = Math.floor(rangeSize / 2);
97+
let start = Math.max(1, currentPage - half);
98+
let end = Math.min(totalPages, start + rangeSize - 1);
99+
100+
if (end - start + 1 < rangeSize && start > 1) {
101+
start = Math.max(1, end - rangeSize + 1);
102+
}
103+
104+
return Array.from(
105+
{ length: end - start + 1 },
106+
(_, i) => start + i,
107+
).map((page) => (
108+
<option key={page} value={page}>
109+
{page}
110+
</option>
111+
));
112+
})()}
113+
</select>
114+
of {totalPages}
115+
</div>
116+
117+
<Button
118+
size="sm"
119+
variant="outline"
120+
disabled={currentPage === totalPages}
121+
onClick={() => handlePageChange(currentPage + 1)}
122+
>
123+
Next
124+
</Button>
125+
</div>
126+
127+
{showAnimation && (
128+
<div className="mt-2">
129+
<AuditProgressAnimation
130+
championName={winnerName}
131+
isValid={isValid}
132+
onClose={() => {
133+
setShowAnimation(false);
134+
setTimeout(() => {
135+
animationSpacerRef.current?.scrollIntoView({
136+
behavior: "smooth",
137+
block: "start",
138+
});
139+
}, 100);
140+
}}
141+
/>
142+
</div>
143+
)}
144+
145+
<div ref={animationSpacerRef} className="h-1" />
146+
</div>
147+
</div>
148+
);
149+
};
150+
151+
export default AssertionTable;

0 commit comments

Comments
 (0)