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

Feature/tech stack p1 #133

Merged
merged 37 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5d2f7e7
setup wrapper component and GET logic.
MattRueter May 6, 2024
6d2d59e
populate TechStackCard with data.
MattRueter May 7, 2024
5c25e8c
add properties for Button and TextInput required by UI design (for Te…
MattRueter May 9, 2024
9fe71d1
update TechStackCard to match design changes (vote & remove-vote butt…
MattRueter May 9, 2024
1b0a770
create SettingsMenu for each li in techStackCards.
MattRueter May 9, 2024
0a0e64a
move smaller componets from TechStackCard to separate file.
MattRueter May 9, 2024
7c64523
add click outside to close menu logic.
MattRueter May 9, 2024
8cb0b3e
open editing input.
MattRueter May 9, 2024
3e39b56
add Finalize Selection button to page.
MattRueter May 9, 2024
1b4ca33
handle several votes and sizing & simplify handleChange for inputs.
MattRueter May 9, 2024
6f95727
clear linting errors.
MattRueter May 9, 2024
5fc3d3a
Merge branch 'dev' into feature/tech-stack-p1
MattRueter May 9, 2024
3fbfd5e
clean linting errors (from push).
MattRueter May 9, 2024
92344ba
Merge branch 'dev' into feature/tech-stack-p1
MattRueter May 14, 2024
b978a48
fix Finalize Selection btn layout.
MattRueter May 14, 2024
914bdff
fix button styles / layout.
MattRueter May 14, 2024
88b8915
update button rendering logic based on number of votes and user id.
MattRueter May 14, 2024
7a82529
add styles to tech stack titles.
MattRueter May 14, 2024
6eab65a
update redirect on fetch error to go to dashboard instead of root.
MattRueter May 14, 2024
6e3c9ce
fix typo.
MattRueter May 14, 2024
30eedec
adjust sizing and layout of techStackCards.
MattRueter May 14, 2024
a43884b
avatars in AvatarGroup overlap.
MattRueter May 14, 2024
d8e57da
click outside of inputs closes inputs.
MattRueter May 14, 2024
b4a8083
Move JSX of EditPopver into reusable EditMenu component.
MattRueter May 15, 2024
615e6b8
make EditMenu more reusable.
MattRueter May 15, 2024
8421e9e
fix font weight to match design in figma.
MattRueter May 15, 2024
c7c174d
fix linting error.
MattRueter May 15, 2024
3d5c9b5
Merge branch 'dev' into feature/tech-stack-p1
MattRueter May 22, 2024
f71d637
fix SettingsMenu layout relative to <li>.
MattRueter May 22, 2024
4ea3293
fix layout problem with toggling add tech button & input.
MattRueter May 22, 2024
6588161
adjust layout of SettingsMenu and TechStackCard.
MattRueter May 28, 2024
8d833a9
move SettingsMenu into RemoveVoteBtn component.
MattRueter May 28, 2024
6d00dfc
remove unused prop from TextInput.
MattRueter May 28, 2024
240f049
use routePaths for redirecting.
MattRueter May 28, 2024
3663ccd
make use of already existing interface for voyage members.
MattRueter May 28, 2024
f9b641f
fix linting indentation error.
MattRueter May 28, 2024
0a59de5
Merge branch 'dev' into feature/tech-stack-p1
Dan-Y-Ko May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { PencilSquareIcon, TrashIcon } from "@heroicons/react/24/outline";
import { type Dispatch, type SetStateAction } from "react";
import Button from "@/components/Button";
import { useAppDispatch } from "@/store/hooks";
import { onOpenModal } from "@/store/features/modal/modalSlice";
import { deleteFeature } from "@/myVoyage/features/featuresService";
import EditMenu from "@/components/EditMenu";

interface EditPopoverProps {
setEditMode: Dispatch<SetStateAction<boolean>>;
Expand Down Expand Up @@ -45,26 +44,5 @@ export default function EditPopover({
);
}

return (
<div className="flex flex-col justify-evenly items-center w-[150px] h-[116px] bg-base-200 border border-base-100 rounded-lg shadow-lg absolute top-0 right-0 z-10">
<Button
variant="outline"
size="sm"
className="w-[118px] h-[34px] justify-start"
onClick={handleClick}
>
<PencilSquareIcon className="w-4 h-4" />
Edit
</Button>
<Button
variant="error"
size="sm"
className="w-[118px] h-[34px] justify-start"
onClick={handleDelete}
>
<TrashIcon className="w-4 h-4" />
Delete
</Button>
</div>
);
return <EditMenu handleClick={handleClick} handleDelete={handleDelete} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Button from "@/components/Button";

export default function AddVoteBtn() {
return (
<div className="col-span-3 flex justify-end">
<Button variant="primary" size="xs" className="rounded-3xl font-semibold">
Add Vote
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
ComputerDesktopIcon,
SwatchIcon,
CodeBracketSquareIcon,
ChartPieIcon,
CloudIcon,
ServerStackIcon,
} from "@heroicons/react/24/solid";

export default function GetIcon(cardTitle: string) {
if (cardTitle === "Frontend") {
return <ComputerDesktopIcon className="w-1/12 h-1/12 mr-2" />;
}
if (cardTitle === "CSS Library") {
return <SwatchIcon className="w-1/12 h-1/12 mr-2" />;
}
if (cardTitle === "Backend") {
return <CodeBracketSquareIcon className="w-1/12 h-1/12 mr-2" />;
}
if (cardTitle === "Project Management") {
return <ChartPieIcon className="w-1/12 h-1/12 mr-2" />;
}
if (cardTitle === "Cloud Provider") {
return <CloudIcon className="w-1/12 h-1/12 mr-2" />;
}
if (cardTitle === "Hosting") {
return <ServerStackIcon className="w-1/12 h-1/12 mr-2" />;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { Dispatch, SetStateAction } from "react";
import { EllipsisVerticalIcon } from "@heroicons/react/24/solid";
import SettingsMenu from "./SettingsMenu";
import Button from "@/components/Button";

interface RemoveVoteBtnProps {
id: number;
openMenu: (value: number) => void;
numberOfVotes: number;
closeMenu: () => void;
setIsEditing: Dispatch<SetStateAction<number>>;
isMenuOpen: number;
}

export default function RemoveVoteBtn({
id,
openMenu,
numberOfVotes,
closeMenu,
setIsEditing,
isMenuOpen,
}: RemoveVoteBtnProps) {
const handleClick = () => {
openMenu(id);
};

return (
<div className="flex justify-end items-center w-[165px] col-span-2 relative">
{numberOfVotes < 2 && (
<div className="w-1/6 h-1/6">
<EllipsisVerticalIcon
className="rounded-xl hover:bg-base-100 mr-2 hover:cursor-pointer"
onClick={handleClick}
/>
{isMenuOpen === id && (
<SettingsMenu
onClose={closeMenu}
setIsEditing={setIsEditing}
id={id}
/>
)}
</div>
)}
<Button
variant="error"
size="xs"
className="rounded-3xl justify-self-end font-semibold"
>
Remove Vote
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";
import { useEffect, useRef } from "react";
import EditMenu from "@/components/EditMenu";

interface SettingsMenuProps {
onClose: () => void;
setIsEditing: (value: number) => void;
id: number;
}

export default function SettingsMenu({
onClose,
setIsEditing,
id,
}: SettingsMenuProps) {
const menuRef = useRef<HTMLDivElement>(null);

const openEdit = () => {
setIsEditing(id);
};

useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
onClose();
}
}
document.body.addEventListener("click", handleClickOutside);
return () => {
document.body.removeEventListener("click", handleClickOutside);
};
}, [onClose]);

return (
<div className="ml-[12px] -mt-6 absolute" ref={menuRef}>
<EditMenu handleClick={openEdit} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,175 @@
import { PlusCircleIcon } from "@heroicons/react/24/outline";
import type { TechItem } from "./fixtures/TechStack";
import myAvatar from "@/public/img/avatar.png";
"use client";
import { useRef, useState, useEffect } from "react";
import type { FormEvent } from "react";
import GetIcon from "./GetIcons";
import AddVoteBtn from "./AddVoteBtn";
import RemoveVoteBtn from "./RemoveVoteBtn";
import TextInput from "@/components/inputs/TextInput";
import AvatarGroup from "@/components/avatar/AvatarGroup";
import Avatar from "@/components/avatar/Avatar";
import Button from "@/components/Button";
import type { TechStackItem } from "@/store/features/techStack/techStackSlice";
import { useUser } from "@/store/hooks";

interface TechStackCardProps {
title: string;
data: TechItem[];
data: TechStackItem[];
}

export default function TechStackCard({ title, data }: TechStackCardProps) {
const [isInput, setIsInput] = useState(false);
const [isEditing, setIsEditing] = useState(-1);
const [isDuplicate, setIsDuplicate] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const editRef = useRef<HTMLInputElement>(null);
const items = data.map((item) => item.name.toLowerCase());
const userId = useUser().id;
const [openMenuId, setOpenMenuId] = useState(-1);

const toggleAddItemInput = () => {
setIsInput(!isInput);
};

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
};

const handleSettingsMenuClose = () => {
setOpenMenuId(-1);
};

const clearActionEditItem = () => {
setIsEditing(-1);
setOpenMenuId(-1);
};

const clearActionAdditem = () => {
setIsInput(!isInput);
};

const handleOnChange = () => {
const addingItemValue = inputRef.current?.value.toLowerCase();

const isDuplicateInAdding =
addingItemValue && items.includes(addingItemValue);

if (addingItemValue && isDuplicateInAdding !== isDuplicate) {
setIsDuplicate(!!isDuplicateInAdding);
}
};

useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
inputRef.current &&
!inputRef.current.contains(event.target as Node)
) {
setIsInput(false);
}
if (editRef.current && !editRef.current.contains(event.target as Node)) {
setIsEditing(-1);
setOpenMenuId(-1);
}
}
document.body.addEventListener("click", handleClickOutside);
return () => {
document.body.removeEventListener("click", handleClickOutside);
};
}, [isInput, isEditing]);

return (
<div className="card min-w-[400px] sm:w-96 text-base-300 bg-base-200 rounded-lg px-6 py-5">
<div className="flex flex-row justify-between">
<div className="card min-w-[420px] h-80 text-base-300 bg-base-200 rounded-lg px-6 py-5 sm:w-96">
<div className="flex flex-row justify-start">
{GetIcon(title)}
<h3 className="self-center text-xl font-semibold text-base-300">
{title}
</h3>
</div>

<div className="h-40 pt-1 mt-6 overflow-y-auto">
<ul className="text-base-300">
{data.map((element) => (
<li
className="text-base mb-5 last:mb-0 grid grid-cols-3 items-center"
className="text-base mb-8 grid grid-cols-6 items-center relative "
key={element.id}
>
{element.value}
<AvatarGroup>
{element.users.map((user) => (
<Avatar
key={user}
/*TO DO: replace image={myAvatar} with {user.avatar} or {user}
...depending on data schema.*/
image={myAvatar}
width={24}
height={24}
{isEditing === element.id && (
<form className="col-span-6 h-12 -my-2">
<TextInput
id={element.id.toString()}
ref={editRef}
placeholder={element.name}
submitButtonText="Save"
clearInputAction={clearActionEditItem}
onChange={handleOnChange}
/>
))}
</AvatarGroup>
<button
type="button"
className="capitalize w-[62px] h-[32px] p-0 min-h-full text-xs font-medium text-base-300 bg-primary-content border-transparent mr-4 rounded-[32px] hover:bg-primary hover:border-transparent gap-x-0 place-self-end"
>
Vote
</button>
</form>
)}

{isEditing !== element.id && (
<>
{/*item name*/}
<p className="font-medium text-base leading-5">
{element.name}
</p>
{/*Avatars of voters*/}
<div className="ml-8 col-span-2 bg-base-200">
<AvatarGroup>
{element.teamTechStackItemVotes.map((vote) => (
<Avatar
key={vote.votedBy.member.id}
image={vote.votedBy.member.avatar}
width={24}
height={24}
/>
))}
</AvatarGroup>
</div>
{element.teamTechStackItemVotes
.map((item) => item.votedBy.member.id)
.includes(userId) ? (
<RemoveVoteBtn
id={element.id}
openMenu={setOpenMenuId}
numberOfVotes={element.teamTechStackItemVotes.length}
closeMenu={handleSettingsMenuClose}
setIsEditing={setIsEditing}
isMenuOpen={openMenuId}
/>
) : (
<AddVoteBtn />
)}
</>
)}
</li>
))}
</ul>
</div>
<Button variant="secondary" className="justify-start w-full">
<PlusCircleIcon className="h-[18px] w-[18px] text-base-300" />
Add Tech Stack
</Button>

{isInput ? (
<form onSubmit={handleSubmit}>
<TextInput
id={title}
ref={inputRef}
placeholder="Add Tech Stack"
submitButtonText="Save"
errorMessage={isDuplicate ? "Duplicate Item" : ""}
clearInputAction={clearActionAdditem}
onChange={handleOnChange}
className="z-10"
/>
</form>
) : (
<div className="px-3.5 py-2.5">
<Button
variant="outline"
className="justify-center w-full border-2"
onClick={toggleAddItemInput}
>
Add Tech Stack
</Button>
</div>
)}
</div>
);
}
Loading
Loading