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 func p4 #168

Merged
merged 23 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cf4ebb7
create finalize tech stack route & add alert banner.
MattRueter Jul 22, 2024
652c25f
add techStack reducer to combineReducers.
MattRueter Jul 24, 2024
a2a3534
add finalize banner.
MattRueter Jul 24, 2024
7fbe912
add finalize tech stack page to route paths util.
MattRueter Jul 24, 2024
1bf93ab
render finalize techstack page.
MattRueter Jul 29, 2024
4f02361
fix params reading typo.
MattRueter Jul 30, 2024
22661a9
add selecting logic and UI to finalize page.
MattRueter Aug 1, 2024
f250ea9
only render cards in finalize page if items exist && refactor list co…
MattRueter Aug 1, 2024
0df760f
add finalize functionality.
MattRueter Aug 1, 2024
896c199
redirect and display finalized tech stack functionality.
MattRueter Aug 1, 2024
44e9f15
structure data and create UI for finalizedTechStackCards.
MattRueter Aug 2, 2024
176cbbd
add check to disable 'finalize' button if not all categories selected…
MattRueter Aug 6, 2024
5550933
add edit page UI and logic.
MattRueter Aug 7, 2024
d79c025
Merge branch 'dev' into feature/tech-stack-func-p4
MattRueter Aug 7, 2024
d3e5eaa
Merge branch 'dev' into feature/tech-stack-func-p4
Dan-Y-Ko Aug 12, 2024
972de54
fix styling on tech stack category name
Dan-Y-Ko Aug 17, 2024
6fe5440
add margin to bottom of cancel button
Dan-Y-Ko Aug 17, 2024
cbfe6ff
update styles in finalized tech stack card
Dan-Y-Ko Aug 17, 2024
ac95521
set previously selected items to false
Dan-Y-Ko Aug 17, 2024
06bc21b
refactor code to submit only necessary body for request when editing …
Dan-Y-Ko Aug 17, 2024
c0d0bd7
small refactor + add constraints to previousSelected props based on i…
Dan-Y-Ko Aug 17, 2024
80a47ba
Merge branch 'dev' into feature/tech-stack-func-p4
Dan-Y-Ko Aug 17, 2024
aef18bf
Merge branch 'dev' into feature/tech-stack-func-p4
JaneMoroz Aug 18, 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
@@ -0,0 +1,62 @@
import GetIcon from "./GetIcons";
import type { SelectedCategory } from "@/app/(main)/my-voyage/[teamId]/tech-stack/finalize/utils/getSelectedTechItems";
import type { TechStackItemVotes } from "@/store/features/techStack/techStackSlice";
import Avatar from "@/components/avatar/Avatar";
import AvatarGroup from "@/components/avatar/AvatarGroup";

interface FinalizedTechStackCardProps {
title: string;
data: SelectedCategory;
}

export default function FinalizedTechStackCard({
title,
data,
}: FinalizedTechStackCardProps) {
return (
<>
<div className="h-80 min-w-[420px] rounded-lg bg-base-200 p-6 text-base-300 sm:w-96 [&>*]:my-4">
<div className="flex flex-row justify-start">
{GetIcon(title)}
<span className="self-center text-xl font-semibold text-base-300">
{title}
</span>
</div>
{data.techItems.map((item) => (
<FinalizedTechListItem
key={item.id}
name={item.name}
votes={item.teamTechStackItemVotes}
/>
))}
</div>
</>
);
}

interface FinalizedTechListItemProps {
name: string;
votes: TechStackItemVotes[];
}
export function FinalizedTechListItem({
name,
votes,
}: FinalizedTechListItemProps) {
const avatars = votes.map((vote) => vote.votedBy.member);

return (
<div className="flex h-12 items-center rounded-md bg-base-100 px-4 py-2">
<h1 className="w-1/3 font-medium">{name}</h1>
<AvatarGroup>
{avatars.map((member) => (
<Avatar
key={member.id}
image={member.avatar}
width={24}
height={24}
/>
))}
</AvatarGroup>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export default function TechStackCard({ title, data }: TechStackCardProps) {
{title}
</span>
</div>

{/**TODO Refactor this beast into smaller more module pieces. */}
<div className="mt-6 h-40 overflow-y-auto p-1">
<ul className="text-base-300">
{data.map((element) => {
Expand Down Expand Up @@ -308,6 +308,7 @@ export default function TechStackCard({ title, data }: TechStackCardProps) {
})}
</ul>
</div>

{isInput ? (
<form ref={inputRef} onSubmit={handleSubmit(handleAddItem)}>
<TextInput
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,57 @@
"use client";

import Link from "next/link";
import { useParams } from "next/navigation";
import TechStackCard from "./TechStackCard";
import FinalizedTechStackCard from "./FinalizedTechStackCard";
import { getSelectedTechItems } from "@/app/(main)/my-voyage/[teamId]/tech-stack/finalize/utils/getSelectedTechItems";
import Button from "@/components/Button";
import type { TechStackData } from "@/store/features/techStack/techStackSlice";
import routePaths from "@/utils/routePaths";

interface TechStackContainerProps {
data: TechStackData[];
}

export default function TechStackContainer({ data }: TechStackContainerProps) {
const { teamId } = useParams<{ teamId: string }>();

const techCardData = data.map((item) => ({
id: item.id,
title: item.name,
techItems: item.teamTechStackItems,
}));

const selectedTechItems = getSelectedTechItems(techCardData);
const isFinalized = selectedTechItems.length > 0;

function renderContent() {
if (isFinalized) {
return selectedTechItems.map((item) => (
<FinalizedTechStackCard key={item.id} title={item.title} data={item} />
));
}

return techCardData.map((item) => (
<li key={item.id}>
<TechStackCard title={item.title} data={item.techItems} />
</li>
));
}

return (
<div className="w-full">
<div className="mb-10 grid grid-cols-2 place-items-center min-[1920px]:grid-cols-3">
<div className="col-start-2 flex min-w-[420px] flex-row-reverse sm:w-96 min-[1920px]:col-start-3">
<Button variant="secondary">Finalize Selection</Button>
<Link href={routePaths.finalizeTechStackPage(teamId)}>
<Button variant="secondary">
{isFinalized ? "Edit Final Selection" : "Finalize Selection"}
</Button>
</Link>
</div>
</div>
<ul className="grid grid-cols-2 place-items-center gap-y-10 min-[1920px]:grid-cols-3">
{techCardData.map((item) => (
<li key={item.id}>
<TechStackCard title={item.title} data={item.techItems} />
</li>
))}
{renderContent()}
</ul>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { useParams, useRouter } from "next/navigation";
import createFinalList from "./utils/createFinalList";
import editFinalList from "./utils/editFinalList";
import {
type FinalizedList,
finalizeTechStack,
} from "@/app/(main)/my-voyage/[teamId]/tech-stack/techStackService";
import type { ConfirmationButtonProps } from "@/app/(main)/my-voyage/[teamId]/tech-stack/finalize/types";
import Spinner from "@/components/Spinner";
import Button from "@/components/Button";
import { useAppDispatch } from "@/store/hooks";
import useServerAction from "@/hooks/useServerAction";
import { onOpenModal } from "@/store/features/modal/modalSlice";
import routePaths from "@/utils/routePaths";

export default function ConfirmationButton({
isFinalized,
allCategoriesSelected,
selectedItems,
previousSelected,
}: ConfirmationButtonProps) {
const params = useParams();
const teamId = Number(params.teamId);
const router = useRouter();
const dispatch = useAppDispatch();

const {
runAction: finalizeTechStackAction,
isLoading: finalizeTechStackLoading,
setIsLoading: setFinalizeTechStackLoading,
} = useServerAction(finalizeTechStack);

const handleClick = async () => {
if (isFinalized) {
const selection = editFinalList(previousSelected, selectedItems);

const finalList: FinalizedList = {
categories: selection.map((cat) => ({
categoryId: cat.categoryId,
techs: cat.techs,
})),
};

const [res, error] = await finalizeTechStackAction({
teamId,
finalizedList: finalList,
});
if (res) {
router.push(routePaths.techStackPage(teamId.toString()));
}
if (error) {
dispatch(
onOpenModal({ type: "error", content: { message: error.message } }),
);
}
} else {
const selection = createFinalList(selectedItems);

const finalList: FinalizedList = {
categories: selection.map((cat) => ({
categoryId: cat.categoryId,
techs: cat.techs,
})),
};

const [res, error] = await finalizeTechStackAction({
teamId,
finalizedList: finalList,
});
if (res) {
router.push(routePaths.techStackPage(teamId.toString()));
}
if (error) {
dispatch(
onOpenModal({ type: "error", content: { message: error.message } }),
);
}
}

setFinalizeTechStackLoading(false);
};

function renderButtonContent() {
if (finalizeTechStackLoading) {
return <Spinner />;
}
const text = isFinalized
? "Save Changes"
: "Finalize Tech Stack Selection.";
return text;
}

return (
<Button
variant="secondary"
disabled={finalizeTechStackLoading || !allCategoriesSelected}
className="mb-4 mt-10 w-full"
onClick={handleClick}
>
{renderButtonContent()}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use client";

import { checkIfFinalized } from "./utils/checkIfFinalized";
import Alert from "@/components/Alert";
import { useTechStack } from "@/store/hooks";

export default function FinalizeTechBanner() {
const { techStack } = useTechStack();
const isFinalized = checkIfFinalized(techStack);

return <>{isFinalized ? <TechBannerEdit /> : <TechBannerFinalize />}</>;
}

function TechBannerEdit() {
return (
<div className="flex h-[266px] w-[871px] flex-1 flex-col justify-center gap-y-4 rounded-2xl bg-base-200 p-10 shadow-md">
<h2 className="text-2xl font-semibold text-base-300">
Edit your choices
</h2>
<p className="text-base font-medium text-base-300">
Edit the tech stack that you and your team plan on using for your
Voyage!
</p>
</div>
);
}

function TechBannerFinalize() {
return (
<div className="flex h-[266px] w-[871px] flex-1 flex-col justify-center gap-y-4 rounded-2xl bg-base-200 p-10 shadow-md">
<h2 className="text-2xl font-semibold text-base-300">
Finalize your choices
</h2>
<p className="text-base font-medium text-base-300">
Finalize the tech stack that you and your team plan on creating for your
Voyage!
</p>
<div className="w-full">
<Alert
context="info"
message="Important: It's advisable to collaborate as a team and
ensure everyone agrees before finalizing your tech stack. You will be able to make changes to your tech stack later."
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useEffect, useRef } from "react";
import { CheckCircleIcon } from "@heroicons/react/24/outline";
import type {
FinalizeTechCardProps,
Vote,
SelectedItems,
FinalizedItem,
} from "./types";
import Button from "@/components/Button";
import AvatarGroup from "@/components/avatar/AvatarGroup";
import Avatar from "@/components/avatar/Avatar";

export default function FinalizeTechCard({
title,
techItemVotes,
categoryId,
techId,
selectedItems,
setSelectedItems,
setPreviousSelected,
finalizedItems,
}: FinalizeTechCardProps) {
const hasMounted = useRef(false);

const handleSelect = () => {
if (selectedItems[categoryId as keyof SelectedItems] === techId) {
setSelectedItems((selectedItems: SelectedItems) => ({
...selectedItems,
[categoryId]: null,
}));
} else {
setSelectedItems((selectedItems: SelectedItems) => ({
...selectedItems,
[categoryId]: techId,
}));
}
};

//if editing the 'selectedItems' variable needs to be set to contain the
// finalized list. This useEffect makes sure that happens. *ref is used because linting error required
// dependencies but dependencies caused loop. The ref is used to workaround that.
useEffect(() => {
if (!hasMounted.current) {
if (finalizedItems) {
let update = {};
for (let i = 0; i < finalizedItems.length; i++) {
const key = finalizedItems[i].id;
const value = finalizedItems[i].techItems[0].id;
update = { ...update, [key as keyof FinalizedItem[]]: value };
}
setPreviousSelected(update);
setSelectedItems(update);
}
}
hasMounted.current = true;
}, [finalizedItems, setSelectedItems, setPreviousSelected]);

return (
<Button
variant={
selectedItems[categoryId as keyof SelectedItems] === techId
? "primary"
: "outline"
}
className="gap-x-0"
aria-label="Finalized Project Idea"
onClick={handleSelect}
>
<div className="flex w-full flex-col items-center justify-center gap-y-2">
<h2 className="text-base font-semibold">{title}</h2>
<AvatarGroup>
{techItemVotes.map((vote: Vote) => (
<Avatar
key={vote.votedBy.member.id}
image={vote.votedBy.member.avatar}
width={24}
height={24}
/>
))}
</AvatarGroup>
</div>
<div className="h-6 w-6">
{selectedItems[categoryId as keyof SelectedItems] === techId && (
<CheckCircleIcon />
)}
</div>
</Button>
);
}
Loading
Loading