Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: header responsiveness & share integration #31

Merged
merged 11 commits into from
Oct 22, 2024
20 changes: 13 additions & 7 deletions app/allocation/[category]/components/RankingRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC, useState } from 'react';
import { FC } from 'react';
import Image from 'next/image';
import { NumericFormat } from 'react-number-format';
import { IProjectRanking } from '@/app/comparison/utils/types';
Expand All @@ -7,17 +7,20 @@ import { ExpandVertical } from '@/public/assets/icon-components/ExpandVertical';
import { LockIcon } from '@/public/assets/icon-components/Lock';
import { UnlockIcon } from '@/public/assets/icon-components/Unlock';
import styles from '@/app/styles/Project.module.css';
// @ts-ignore

interface IRankingRowProps {
project: IProjectRanking
selected: boolean
onSelect: (id: number) => void
onVote: (id: number, share: number) => void
}

const RankingRow: FC<IRankingRowProps> = ({ project, selected, onSelect }) => {
const [value, setValue] = useState(0);

const RankingRow: FC<IRankingRowProps> = ({
project,
selected,
onSelect,
onVote,
}) => {
const handleAllowdValue = (values: any) => {
const { floatValue } = values;
return !floatValue || floatValue <= 100;
Expand Down Expand Up @@ -61,9 +64,12 @@ const RankingRow: FC<IRankingRowProps> = ({ project, selected, onSelect }) => {
<NumericFormat
suffix="%"
decimalScale={2}
value={value}
value={project.share * 100}
onValueChange={(values) => {
setValue(values?.floatValue || 0);
onVote(
project.projectId,
values?.floatValue ? values.floatValue / 100 : 0
);
}}
className="w-24 rounded-md border border-gray-200 bg-gray-50 px-4 py-2 text-center focus:outline-none focus:ring-1"
placeholder="0.00%"
Expand Down
111 changes: 99 additions & 12 deletions app/allocation/[category]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
'use client';

import { useState } from 'react';
import { useParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useParams, useRouter } from 'next/navigation';
import RankingRow from './components/RankingRow';
import HeaderRF6 from '../../comparison/card/Header-RF6';
import Spinner from '@/app/components/Spinner';
import SearchBar from './components/SearchBar';
import { categorySlugIdMap, categoryIdTitleMap } from '../../comparison/utils/helpers';
import {
categorySlugIdMap,
categoryIdTitleMap,
} from '../../comparison/utils/helpers';
import { Checkbox } from '@/app/utils/Checkbox';
import { LockIcon } from '@/public/assets/icon-components/Lock';
import NotFoundComponent from '@/app/components/404';
import { useProjectsRankingByCategoryId } from '@/app/comparison/utils/data-fetching/ranking';
import {
useProjectsRankingByCategoryId,
useUpdateProjectRanking,
IProjectRankingObj,
} from '@/app/comparison/utils/data-fetching/ranking';
import { CheckIcon } from '@/public/assets/icon-components/Check';
import { IProjectRanking } from '@/app/comparison/utils/types';
import { ArrowLeft2Icon } from '@/public/assets/icon-components/ArrowLeft2';
import { ArrowRightIcon } from '@/public/assets/icon-components/ArrowRight';

enum VotingStatus {
VOTED,
Expand All @@ -29,14 +39,21 @@ const votingStatusMap = {

const RankingPage = () => {
const params = useParams();
const router = useRouter();

const category = categorySlugIdMap.get((params?.category as string) || '');

const [search, setSearch] = useState<string>('');
const [checkedItems, setCheckedItems] = useState<number[]>([]);
const [projects, setProjects] = useState<IProjectRanking[] | null>(null);
const [rankingArray, setRankingArray] = useState<IProjectRankingObj[]>([]);
const [totalShareError, setTotalShareError] = useState<string | null>(null);

const { data: ranking, isLoading } = useProjectsRankingByCategoryId(category);
const projects = ranking?.ranking;
const { mutate: updateProjectRanking } = useUpdateProjectRanking({
cid: category,
ranking: rankingArray,
});

console.log(projects);

Expand All @@ -51,17 +68,62 @@ const RankingPage = () => {
}
};

const handleVote = (id: number, share: number) => {
if (!projects) return;

const updatedProjects = projects.map(project =>
project.projectId === id ? { ...project, share } : project
);

setProjects(updatedProjects);
};

const submitVotes = () => {
if (!projects) return;

const totalShare = projects.reduce(
(acc, project) => acc + project.share * 100,
0
);

if (totalShare !== 100) {
if (totalShare > 100) {
setTotalShareError(
`Percentages must add up to 100% (remove ${
totalShare - 100
}% from your ballot)`
);
}
else {
setTotalShareError(
`Percentages must add up to 100% (add ${
100 - totalShare
}% to your ballot)`
);
}
return;
}

const rankingArray = projects.map(project => ({
id: project.projectId,
share: project.share,
}));

setRankingArray(rankingArray);

updateProjectRanking();
};

useEffect(() => {
if (ranking) setProjects(ranking?.ranking);
}, [ranking]);

if (!category) return <NotFoundComponent />;

return (
<div>
<HeaderRF6
progress={30}
category="category"
question="Which project had the greatest impact on the OP Stack?"
isFirstSelection={false}
/>
<div className="flex flex-col justify-between gap-4 px-72 py-16">
<HeaderRF6 />
<div className="flex flex-col justify-between gap-4 px-6 py-16 lg:px-20 xl:px-52 2xl:px-72">
<p className="mb-4 text-2xl font-semibold text-gray-700">
Edit your votes
</p>
Expand Down Expand Up @@ -135,6 +197,7 @@ const RankingPage = () => {
setCheckedItems([...checkedItems, id]);
}
}}
onVote={handleVote}
/>
))}
</tbody>
Expand All @@ -143,6 +206,30 @@ const RankingPage = () => {
: (
<p className="text-center text-gray-400">No projects found</p>
)}

{totalShareError && (
<div className="flex justify-end gap-4">
<p className="text-sm font-medium text-primary">
{totalShareError}
</p>
</div>
)}
<div className="flex justify-between">
<button
className="flex items-center justify-center gap-3 rounded-lg border bg-gray-50 px-4 py-2 font-semibold text-gray-700"
onClick={() => router.push('/allocation')}
>
<ArrowLeft2Icon />
Back to Categories
</button>
<button
className="flex items-center justify-center gap-3 rounded-lg bg-primary px-10 py-2 font-semibold text-white"
onClick={submitVotes}
>
Submit Vote
<ArrowRightIcon size={20} />
</button>
</div>
</div>
</div>
</div>
Expand Down
11 changes: 10 additions & 1 deletion app/allocation/components/CategoryAllocation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { ChangeEventHandler, FC, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { roundFractions } from '../utils';
import { useAuth } from '@/app/utils/wallet/AuthProvider';
Expand All @@ -13,6 +14,8 @@ import {
} from '@/app/comparison/utils/types';
import { CheckIcon } from '@/public/assets/icon-components/Check';
import { UserColabGroupIcon } from '@/public/assets/icon-components/UserColabGroup';
import { categoryIdSlugMap } from '@/app/comparison/utils/helpers';

export interface Category {
id: number
imageSrc: string
Expand All @@ -34,6 +37,7 @@ interface CategoryAllocationProps extends Category {
}

const CategoryAllocation: FC<CategoryAllocationProps> = ({
id,
allocatingBudget,
imageSrc,
title,
Expand All @@ -49,6 +53,7 @@ const CategoryAllocation: FC<CategoryAllocationProps> = ({
onPercentageChange,
}) => {
const { isAutoConnecting } = useAuth();
const router = useRouter();

const inputRef = useRef<HTMLInputElement>(null);
const handleInputChange: ChangeEventHandler<HTMLInputElement> = debounce(
Expand Down Expand Up @@ -188,7 +193,11 @@ const CategoryAllocation: FC<CategoryAllocationProps> = ({
: status === CollectionProgressStatusEnum.Finished
? (
<div className="flex w-full flex-col items-center justify-center gap-4">
<button className="flex w-full items-center justify-center gap-2 rounded-md border py-3 font-semibold">
<button
className="flex w-full items-center justify-center gap-2 rounded-md border py-3 font-semibold"
onClick={() =>
router.push(`/allocation/${categoryIdSlugMap.get(id)}`)}
>
Edit
</button>
<div className="flex w-full justify-center gap-2 rounded-xl border border-[#17B26A] bg-[#ECFDF3] py-1">
Expand Down
22 changes: 12 additions & 10 deletions app/allocation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,11 @@ const AllocationPage = () => {
null
);

const [delegationState, setDelegationState] = useState(DelegationState.Initial);
const [categoryToDelegate, setCategoryToDelegate] = useState<Pick<Category, 'id' | 'title'>>();
const [delegationState, setDelegationState] = useState(
DelegationState.Initial
);
const [categoryToDelegate, setCategoryToDelegate]
= useState<Pick<Category, 'id' | 'title'>>();
const [targetDelegate, setTargetDelegate] = useState<TargetDelegate>();

const handleDelegate = async (username: string, target: TargetDelegate) => {
Expand Down Expand Up @@ -186,14 +189,18 @@ const AllocationPage = () => {
return (
<div>
<Modal
isOpen={delegationState !== DelegationState.Initial && !!categoryToDelegate}
isOpen={
delegationState !== DelegationState.Initial && !!categoryToDelegate
}
onClose={resetDelegateState}
showCloseButton={true}
>
{delegationState === DelegationState.DelegationMethod && (
<DelegateModal
categoryName={categoryToDelegate!.title}
onFindDelegatesFarcaster={() => { setDelegationState(DelegationState.Lookup); }}
onFindDelegatesFarcaster={() => {
setDelegationState(DelegationState.Lookup);
}}
onFindDelegatesTwitter={() => {}}
/>
)}
Expand Down Expand Up @@ -225,12 +232,7 @@ const AllocationPage = () => {
selectedCategoryId={selectedCategoryId}
/>
</Modal>
<HeaderRF6
progress={30}
category="category"
question="Which project had the greatest impact on the OP Stack?"
isFirstSelection={false}
/>
<HeaderRF6 />
<WorldIdSignInSuccessModal
isOpen={isWorldIdSignSuccessModal}
onClose={() => {
Expand Down
4 changes: 2 additions & 2 deletions app/comparison/[category]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useAccount } from 'wagmi';
import { JWTPayload } from '@/app/utils/wallet/types';
import { AutoScrollAction, ProjectCard } from '../card/ProjectCard';
import ConflictButton from '../card/CoIButton';
import Header from '../card/Header';
import HeaderRF6 from '../card/Header-RF6';
import { Rating } from '../card/Rating';
import UndoButton from '../card/UndoButton';
import VoteButton from '../card/VoteButton';
Expand Down Expand Up @@ -476,7 +476,7 @@ export default function Home() {
/>
)}
</Modal>
<Header
<HeaderRF6
progress={progress * 100}
category={convertCategoryToLabel(category! as JWTPayload['category'])}
question="Which project had the greatest impact on the OP Stack?"
Expand Down
57 changes: 57 additions & 0 deletions app/comparison/card/DropDown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useState, useRef, useEffect, ReactNode } from 'react';

interface DropdownProps {
children: ReactNode
customClass?: string
}

const Dropdown: React.FC<DropdownProps> = ({ children, customClass }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

// Close the dropdown when clicking outside of it
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
dropdownRef.current
&& !dropdownRef.current.contains(event.target as Node)
) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);

return (
<div className={`relative ${customClass}`} ref={dropdownRef}>
<button
className="rounded-lg border border-gray-300 p-2 shadow-md"
onClick={() => setIsOpen(prev => !prev)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="size-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 6.75a.75.75 0 110-1.5.75.75 0 010 1.5zm0 5.25a.75.75 0 110-1.5.75.75 0 010 1.5zm0 5.25a.75.75 0 110-1.5.75.75 0 010 1.5z"
/>
</svg>
</button>

{isOpen && (
<div className="absolute right-0 mt-2 w-60 rounded-md border bg-white p-4 shadow-lg">
{children}
</div>
)}
</div>
);
};

export default Dropdown;
Loading