Skip to content
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
2f362b5
Merge remote-tracking branch 'upstream/dev-fe' into dev
ShipFriend0516 Nov 21, 2024
953d3d2
Merge remote-tracking branch 'upstream/dev' into dev
ShipFriend0516 Nov 21, 2024
2d5c15a
feat: 질문지 리스트 각 컴포넌트 클릭시 해당하는 리스트 상세페이지로 이동
ShipFriend0516 Nov 22, 2024
7eadb57
feat: 사이드바 로그인 여부에 따른 다른 버튼 구현
ShipFriend0516 Nov 22, 2024
29b98e2
fix: 마이페이지로 뒤로가기시 반복적으로 로그인 페이지로 리다이렉트 되는 현상 수정
ShipFriend0516 Nov 22, 2024
26895f0
refactor: react-query Hook을 api 폴더로 분리
ShipFriend0516 Nov 22, 2024
da6e10d
fix: 질문지 생성 후 상세페이지로 이동이 원활하게 이루어지도록 수정
ShipFriend0516 Nov 22, 2024
2644c64
refactor: questionId를 useEffect 내부에서 검사하도록 수정
ShipFriend0516 Nov 22, 2024
41004fd
refactor: 세션 목록 리스트 컴포넌트로 분리
ShipFriend0516 Nov 23, 2024
c6e56a4
refactor: 세션 리스트 데이터 코드 최적화
ShipFriend0516 Nov 23, 2024
7516133
chore: 세션 리스트 mock 데이터 제거
ShipFriend0516 Nov 23, 2024
1ba7c08
refactor: renderList 매개변수 제거
ShipFriend0516 Nov 23, 2024
b956473
refactor: 세션, 질문지 생성 버튼 컴포넌트로 분리 및 텍스트 추가로 가독성 향상
ShipFriend0516 Nov 23, 2024
4bf3551
style: 다크모드 기본 텍스트 컬러 지정
ShipFriend0516 Nov 23, 2024
3d4e124
fix: sidebar 메뉴가 텍스트만 클릭되던 현상 수정
ShipFriend0516 Nov 23, 2024
533db31
refactor: 로그아웃시 쿠키 제거 및 토스트메시지 출력
ShipFriend0516 Nov 23, 2024
f0fe0c8
feat: select 컴포넌트에 setValue 함수를 props로 받아서 사용하도록 수정
ShipFriend0516 Nov 23, 2024
b2107a8
feat: 질문지 카테고리 검색 기능 추가
ShipFriend0516 Nov 23, 2024
b415bea
refactor: 질문지 생성시 카테고리 추가
ShipFriend0516 Nov 23, 2024
0c85ff9
refactor: 카테고리 select에 들어갈 카테고리 데이터 constraints 폴더로 분리
ShipFriend0516 Nov 23, 2024
9d06917
refactor: 세션 리스트 페이지 select에 카테고리 추가 및 sessions/create 페이지로 가는 링크가 잘못…
ShipFriend0516 Nov 23, 2024
a325b2e
feat: select 컴포넌트 제네릭 타입을 쓰도록 수정 및 value props로 선택된 default 값 조절 가능하도…
ShipFriend0516 Nov 23, 2024
5b7a8c7
feat: searchParams로 질문지 카테고리 유지되도록 구현
ShipFriend0516 Nov 23, 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
22 changes: 22 additions & 0 deletions frontend/src/components/common/CreateButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { IconType } from "react-icons";

interface ButtonProps {
onClick: () => void;
text: string;
icon?: IconType;
}

const CreateButton = ({ onClick, text, icon: Icon }: ButtonProps) => {
return (
<button
className={
"inline-flex text-nowrap justify-center items-center text-semibold-r gap-1 text-gray-white fill-current px-2 bg-green-200 hover:bg-green-200/90 rounded-custom-m box-border"
}
onClick={onClick}
aria-label={`${text} 만들기`}
>
{text} {Icon && <Icon />}
</button>
);
};
export default CreateButton;
31 changes: 23 additions & 8 deletions frontend/src/components/common/Select.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
import { IoChevronDownSharp } from "react-icons/io5";

type Option = {
type Option<T> = {
label: string;
value: string;
value: T;
};

interface SelectProps {
options: Option[];
interface SelectProps<T> {
value: T;
setValue: (value: T) => void;
options: Option<T>[];
backgroundColor?: string;
}

const Select = ({ options, backgroundColor = "bg-green-200" }: SelectProps) => {
const Select = <T,>({
value,
setValue,
options,
backgroundColor = "bg-green-200",
}: SelectProps<T>) => {
return (
<div className="relative inline-block items-center">
<div className="relative inline-flex gap-2 items-center">
<select
className={`rounded-custom-m ${backgroundColor} text-semibold-r text-gray-white appearance-none pl-5 pr-11 h-full`}
defaultValue={value as string}
onChange={(e) => setValue(e.target.value as T)}
className={`rounded-custom-m ${backgroundColor} text-semibold-r text-gray-white appearance-none pl-4 pr-8 h-full`}
>
{options.map((option) => (
<option key={option.value}>{option.label}</option>
<option
key={option.value as string}
value={option.value as string}
selected={option.value === value}
>
{option.label}
</option>
))}
</select>
<span className="absolute top-1/2 -translate-y-1/2 right-3 pointer-events-none">
Expand Down
78 changes: 59 additions & 19 deletions frontend/src/components/common/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,56 @@
import { Link } from "react-router-dom";
import { ReactElement, useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useEffect, useState } from "react";
import { FaClipboardList, FaLayerGroup } from "react-icons/fa";
import { MdDarkMode, MdLightMode, MdLogout } from "react-icons/md";
import { MdDarkMode, MdLightMode, MdLogin, MdLogout } from "react-icons/md";
import { IoPersonSharp, IoHomeSharp } from "react-icons/io5";
import { FaGithub } from "react-icons/fa6";
import useTheme from "@hooks/useTheme.ts";
import useAuth from "@hooks/useAuth.ts";
import { IconType } from "react-icons";
import useToast from "@hooks/useToast.ts";

const Sidebar = () => {
const { isLoggedIn, logOut } = useAuth();
const toast = useToast();
const navigate = useNavigate();
const handleLogout = () => {
logOut();
toast.error("로그아웃 되었습니다.");
};

const routes = [
{
path: "/",
label: "홈",
icon: <IoHomeSharp />,
icon: IoHomeSharp,
},
{
path: "/questions",
label: "질문지 리스트",
icon: <FaClipboardList />,
icon: FaClipboardList,
},
{
path: "/sessions",
label: "스터디 세션 목록",
icon: <FaLayerGroup />,
icon: FaLayerGroup,
},
{
path: "/mypage",
label: "마이페이지",
icon: <IoPersonSharp />,
},
{
path: "/logout",
label: "로그아웃",
icon: <MdLogout />,
icon: IoPersonSharp,
},
isLoggedIn
? {
path: null,
label: "로그아웃",
icon: MdLogout,
onClick: handleLogout,
}
: {
path: "/login",
label: "로그인",
icon: MdLogin,
},
];

const [selected, setSelected] = useState<string>("");
Expand Down Expand Up @@ -68,6 +86,14 @@ const Sidebar = () => {
label={route.label}
icon={route.icon}
isSelected={selected === route.path}
onClick={
route.path
? () =>
navigate(route.path, {
state: { from: route.path ?? "/" },
})
: route.onClick
}
/>
);
})}
Expand Down Expand Up @@ -102,17 +128,19 @@ const Sidebar = () => {
};

interface SidebarMenuProps {
path: string;
path: string | null;
label: string;
icon?: ReactElement;
icon?: IconType;
isSelected?: boolean;
onClick?: () => void;
}

const SidebarMenu = ({
path,
label,
icon,
icon: Icon,
isSelected = false,
onClick,
}: SidebarMenuProps) => {
const activeClass = isSelected
? "bg-green-100 dark:text-gray-black text-white text-semibold-m"
Expand All @@ -122,11 +150,23 @@ const SidebarMenu = ({
<li
className={`${activeClass} flex items-center flex-nowrap text-nowrap px-4 p-2 w-full rounded-lg cursor-pointer`}
aria-label={label + "(으)로 이동하는 버튼"}
onClick={onClick}
>
<Link className={"inline-flex gap-3 items-center w-full"} to={path}>
{icon}
<span>{label}</span>
</Link>
{path === null ? (
<div className={"inline-flex gap-3 items-center w-full"}>
{Icon && <Icon />}
<span>{label}</span>
</div>
) : (
<Link
className={"inline-flex gap-3 items-center w-full"}
to={path}
state={{ from: path ?? "/" }}
>
{Icon && <Icon />}
<span>{label}</span>
</Link>
)}
</li>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,28 @@ export const options = [
value: "프론트엔드",
label: "프론트엔드",
},
{
value: "백엔드",
label: "백엔드",
},
{
value: "데이터베이스",
label: "데이터베이스",
},
{
value: "자료구조",
label: "자료구조",
},
{
value: "알고리즘",
label: "알고리즘",
},
{
value: "보안",
label: "보안",
},
{
value: "기타",
label: "기타",
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AccessSection from "./AccessSection";
import CategorySection from "./CategorySection";
import TitleSection from "./TitleSection";
import QuestionInputSection from "./QuestionInputSection";
import { useCreateQuestion } from "@/hooks/useCreateQuestion";
import { useCreateQuestion } from "@hooks/api/useCreateQuestion.ts";

const QuestionForm = () => {
const isValid = useQuestionFormStore((state) => state.isFormValid());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useGetQuestion } from "@/hooks/useGetQuestion";
import { useGetQuestion } from "@hooks/api/useGetQuestion.ts";
import QuestionItem from "./QuestionItem";

const QuestionList = ({ questionId }: { questionId: string }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useGetQuestion } from "@/hooks/useGetQuestion";
import { useGetQuestion } from "@hooks/api/useGetQuestion.ts";
import { MdEdit } from "react-icons/md";
import { RiDeleteBin6Fill } from "react-icons/ri";
import { FaRegBookmark } from "react-icons/fa";
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/components/sessions/list/SessionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import LoadingIndicator from "@components/common/LoadingIndicator.tsx";
import SessionCard from "@components/sessions/SessionCard.tsx";
import useToast from "@hooks/useToast.ts";
import { useNavigate } from "react-router-dom";

interface Session {
id: number;
title: string;
category?: string;
inProgress: boolean;
host: {
nickname?: string;
socketId: string;
};
participant: number; // 현재 참여자
maxParticipant: number;
createdAt: number;
}

interface SessionListProps {
listTitle: string;
listLoading: boolean;
sessionList: Session[];
}

const SessionList = ({
listTitle,
listLoading,
sessionList,
}: SessionListProps) => {
const toast = useToast();
const navigate = useNavigate();

const renderSessionList = () => {
return sessionList.map((session) => {
return (
<SessionCard
key={session.id}
inProgress={session.inProgress}
category={session.category}
title={session.title}
host={session.host.nickname ?? "익명"}
questionListId={1}
participant={session.participant}
maxParticipant={session.maxParticipant}
onEnter={() => {
toast.success("세션에 참가했습니다.");
navigate(`/session/${session.id}`);
}}
/>
);
});
};

return (
<div>
<h2 className={"text-semibold-l mb-4"}>{listTitle}</h2>
<ul className={"flex flex-col gap-4"}>
{listLoading ? (
<LoadingIndicator loadingState={listLoading} />
) : (
<>
{sessionList.length <= 0 ? (
<li>아직 아무도 세션을 열지 않았어요..!</li>
) : (
renderSessionList()
)}
</>
)}
</ul>
</div>
);
};

export default SessionList;
42 changes: 42 additions & 0 deletions frontend/src/constraints/CategoryData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const options = [
{
value: "전체",
label: "전체",
},
{
value: "프론트엔드",
label: "FE",
},
{
value: "백엔드",
label: "BE",
},
{
value: "운영체제",
label: "운영체제",
},
{
value: "네트워크",
label: "네트워크",
},
{
value: "데이터베이스",
label: "DB",
},
{
value: "자료구조",
label: "자료구조",
},
{
value: "알고리즘",
label: "알고리즘",
},
{
value: "보안",
label: "보안",
},
{
value: "기타",
label: "기타",
},
];
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMutation } from "@tanstack/react-query";
import { createQuestionList } from "@/api/questions/create";
import useToast from "@/hooks/useToast";
import { createQuestionList } from "@/api/questions/create.ts";
import useToast from "@hooks/useToast.ts";
import { useNavigate } from "react-router-dom";

export const useCreateQuestion = () => {
Expand All @@ -12,7 +12,7 @@ export const useCreateQuestion = () => {
onSuccess: (response) => {
const questionListId = response.data.createdQuestionList.id;
toast.success("질문지 생성에 성공했습니다.");
navigate(`questions/${questionListId}`);
navigate(`/questions/${questionListId}`);
},
onError: () => {
toast.error("질문지 생성에 실패했습니다.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import fetchQuestion from "@/api/questions/getQuestion";
import fetchQuestion from "@/api/questions/getQuestion.ts";
import { useQuery } from "@tanstack/react-query";

interface QuestionContent {
Expand Down
Loading