Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b2ad3ff
refact: 전체 코드 리팩토링
hyesngy Nov 30, 2025
bc4cda7
Merge branch 'develop' into refactor/82
hyesngy Nov 30, 2025
0d20df0
refactor: 사용되지 않는 아이콘 제거
hyesngy Nov 30, 2025
1e90d90
refactor: remove unused func
hyesngy Nov 30, 2025
6bc7970
Merge remote-tracking branch 'origin' into refactor/82
hyesngy Nov 30, 2025
884d030
style: format code
hyesngy Nov 30, 2025
fbe2edf
refactor: auth context 리팩토링
hyesngy Nov 30, 2025
b6f8050
refactor: context 리팩토링
hyesngy Nov 30, 2025
89bcb8d
refactor: 레거시 호환성 파일 제거
hyesngy Nov 30, 2025
2b35998
refactor: UserDataContext 책임 분리
hyesngy Nov 30, 2025
151eb1a
Refactor: QuizResult 컴포넌트 분할
hyesngy Nov 30, 2025
0726a21
Refactor: Header 드롭다운 로직 분리
hyesngy Nov 30, 2025
e4e3ba2
Refactor: QuizSession 로직 분리
hyesngy Nov 30, 2025
f8309b3
style: format code
hyesngy Nov 30, 2025
50481a8
Refactor: ChatBot 상태 분리
hyesngy Nov 30, 2025
d2ac0d1
refactor: terms 타입 분리
hyesngy Nov 30, 2025
f849595
refactor: quiz 타입 분리
hyesngy Nov 30, 2025
7305da3
refactor: useCaseTab roleConfig 분리
hyesngy Nov 30, 2025
205e681
refactor: header 컴포넌트 분리
hyesngy Nov 30, 2025
c8f4891
refactor: CategoryChip 적용
hyesngy Nov 30, 2025
4c1d75b
refactor: ScrapButton 컴포넌트화 및 분리
hyesngy Nov 30, 2025
79224a4
style: format coe
hyesngy Nov 30, 2025
c5e2ed7
fix: lint error
hyesngy Nov 30, 2025
7093b9e
refactor: 코드 리뷰 반영
hyesngy Nov 30, 2025
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
64 changes: 10 additions & 54 deletions src/app/chatbot/components/ChatBot.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,22 @@
"use client";

import { useState, useRef, useEffect } from "react";
import { getChatResponse } from "@/app/chatbot/utils/actions";
import { FireIcon, StarIcon, SearchIcon, SendIcon } from "@/components/icons";
import { useChatBot } from "@/hooks/useChatBot";
import ChatMessage from "./ChatMessage";
import UserMessage from "./UserMessage";
import QuickActionButton from "./QuickActionButton";
import BotLoading from "./BotLoading";

interface Message {
role: "user" | "bot";
content: string;
recommendations?: string[];
}

export default function ChatBot() {
const [messages, setMessages] = useState<Message[]>([
{
role: "bot",
content: "안녕하세요! 기술 용어에 대해 궁금한 점을 물어보세요.",
recommendations: ["REST API란?", "Docker는 뭐야?", "GraphQL 설명해줘"],
},
]);
const [input, setInput] = useState("");
const [isLoading, setIsLoading] = useState(false);

const messagesEndRef = useRef<HTMLDivElement>(null);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};

useEffect(() => {
scrollToBottom();
}, [messages]);

const handleSubmit = async (e?: React.FormEvent, customInput?: string) => {
e?.preventDefault();
const userMessage = customInput || input;

if (!userMessage.trim() || isLoading) return;

setMessages((prev) => [...prev, { role: "user", content: userMessage }]);
setInput("");
setIsLoading(true);

const result = await getChatResponse(userMessage);

setMessages((prev) => [
...prev,
{
role: "bot",
content: result.answer,
recommendations: result.recommendations,
},
]);
setIsLoading(false);
};

const handleRecommendationClick = (question: string) => {
handleSubmit(undefined, question);
};
const {
messages,
input,
isLoading,
messagesEndRef,
setInput,
handleSubmit,
handleRecommendationClick,
} = useChatBot();

return (
<div className="mx-auto min-h-screen w-full max-w-260 px-4 pt-20">
Expand Down
27 changes: 6 additions & 21 deletions src/app/dashboard/components/CategoryEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import React, { useState, useEffect } from "react";
import {
type CategoryType,
categoryConfig,
categoryLabels,
} from "@/components/ui/category/config";
CATEGORIES,
CATEGORY_KEYS,
} from "@/config/categories";

interface CategoryEditModalProps {
isOpen: boolean;
Expand All @@ -14,19 +14,6 @@ interface CategoryEditModalProps {
onSave: (category: CategoryType) => Promise<void>;
}

const selectableCategories: CategoryType[] = [
"all",
"frontend",
"backend",
"uxui",
"ai",
"cloud",
"data",
"security",
"devops",
"business",
];

export default function CategoryEditModal({
isOpen,
onClose,
Expand Down Expand Up @@ -73,8 +60,8 @@ export default function CategoryEditModal({
</h2>

<div className="mb-8 flex flex-wrap justify-center gap-3">
{selectableCategories.map((category) => {
const config = categoryConfig[category];
{CATEGORY_KEYS.map((category) => {
const config = CATEGORIES[category];
const IconComponent = config.icon;
const isSelected = selectedCategory === category;

Expand All @@ -93,9 +80,7 @@ export default function CategoryEditModal({
>
<IconComponent className="h-4 w-4 text-white" />
</div>
<span className="text-sm text-white">
{categoryLabels[category]}
</span>
<span className="text-sm text-white">{config.label}</span>
</button>
);
})}
Expand Down
43 changes: 14 additions & 29 deletions src/app/dashboard/components/CategoryTag.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
"use client";

import {
categoryIcons,
categoryColors,
categoryHoverStyles,
categoryActiveStyles,
} from "@/types/category";
import { CATEGORIES, getCategoryType } from "@/config/categories";

interface CategoryTagProps {
category: string;
Expand All @@ -18,33 +13,23 @@ export default function CategoryTag({
isActive,
onClick,
}: CategoryTagProps) {
const IconComponent = categoryIcons[category];
const colorClass = categoryColors[category];
const hoverStyle =
categoryHoverStyles[category] ||
"hover:bg-gray-400/10 hover:outline-white-50";
const activeStyle =
categoryActiveStyles[category] || "bg-gray-400/50 outline-white";
const defaultStyle = "bg-white/5 outline-white-30";
const finalClasses = isActive
? activeStyle + " transition-colors"
: defaultStyle + " " + hoverStyle + " transition-colors";
const categoryType = getCategoryType(category);
const config = CATEGORIES[categoryType];
const IconComponent = config.icon;

const baseClasses =
"glass inline-flex cursor-pointer items-center justify-center gap-2 rounded-xl px-5 py-2 outline-[0.25px] outline-offset-[-0.25px] transition-colors shrink-0";

const stateClasses = isActive
? `${config.selectedColor} outline-white`
: `bg-white/5 outline-white-30 ${config.hoverColor} hover:outline-white-50`;

return (
<div
onClick={onClick}
className={`glass inline-flex cursor-pointer items-center justify-center gap-2 rounded-xl px-5 py-2 outline-[0.25px] outline-offset-[-0.25px] ${finalClasses} shrink-0`}
>
<div onClick={onClick} className={`${baseClasses} ${stateClasses}`}>
<div
className={`w-6 p-1 ${colorClass} flex items-center justify-start gap-2.5 overflow-hidden rounded-[100px] outline-[0.25px] outline-offset-[-0.25px] outline-white`}
className={`w-6 p-1 ${config.bgColor} flex items-center justify-start gap-2.5 overflow-hidden rounded-[100px] outline-[0.25px] outline-offset-[-0.25px] outline-white`}
>
{IconComponent && (
<IconComponent
className="h-4 w-4 text-white"
width={16}
height={16}
/>
)}
<IconComponent className="h-4 w-4 text-white" width={16} height={16} />
</div>
<span className="text-sm leading-6 font-bold text-white">
#{category}
Expand Down
17 changes: 10 additions & 7 deletions src/app/dashboard/components/DashboardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import React, { useState, useEffect, type ReactNode } from "react";
import { useRouter } from "next/navigation";
import ProfileCard from "@/app/dashboard/components/ProfileCard";
import ScrapSection from "@/app/dashboard/components/ScrapSection";
import { useAuth } from "@/contexts/AuthContext";
import { useAuthCore, useUserData } from "@/contexts/auth";
import { getRelatedTerms } from "@/lib/terms";
import { termToScrapCard } from "@/lib/scrap";
import { type ScrapCardData } from "@/types/category";
import { type ScrapCardData } from "@/types/scrapCard";

interface DashboardClientProps {
todayTermCard: ReactNode;
Expand All @@ -17,16 +17,19 @@ export default function DashboardClient({
todayTermCard,
}: DashboardClientProps) {
const router = useRouter();
const { user, userData, loading } = useAuth();
const { user, loading: authLoading } = useAuthCore();
const { userData, userDataLoading } = useUserData();
const [selectedCategory, setSelectedCategory] = useState("전체");
const [scrapCards, setScrapCards] = useState<ScrapCardData[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [scrapLoading, setScrapLoading] = useState(true);

const loading = authLoading || userDataLoading;

useEffect(() => {
async function loadScrapTerms() {
if (!userData || userData.scrapList.length === 0) {
setScrapCards([]);
setIsLoading(false);
setScrapLoading(false);
return;
}

Expand All @@ -38,7 +41,7 @@ export default function DashboardClient({
console.error("스크랩 목록 로드 실패:", error);
setScrapCards([]);
} finally {
setIsLoading(false);
setScrapLoading(false);
}
}

Expand Down Expand Up @@ -82,7 +85,7 @@ export default function DashboardClient({
selectedCategory={selectedCategory}
onCategorySelect={setSelectedCategory}
cards={filteredCards}
isLoading={isLoading}
isLoading={scrapLoading}
/>
</div>
</div>
Expand Down
15 changes: 6 additions & 9 deletions src/app/dashboard/components/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import React, { useState } from "react";
import Image from "next/image";
import { UserIcon } from "@/components/icons/ic_user";
import { EditIcon } from "@/components/icons/ic_edit";
import {
type CategoryType,
categoryConfig,
categoryLabels,
} from "@/components/ui/category/config";
import { useAuth } from "@/contexts/AuthContext";
import { CATEGORIES, type CategoryType } from "@/config/categories";
import { useAuthCore, useUserData } from "@/contexts/auth";
import CategoryEditModal from "./CategoryEditModal";

const SimpleProfileCard: React.FC = () => {
const { userData, user, updateCategory } = useAuth();
const { user } = useAuthCore();
const { userData, updateCategory } = useUserData();
const [isModalOpen, setIsModalOpen] = useState(false);

const selectedCategory = userData?.selectedCategory || "all";
Expand All @@ -23,7 +20,7 @@ const SimpleProfileCard: React.FC = () => {
await updateCategory(category);
};

const config = categoryConfig[selectedCategory];
const config = CATEGORIES[selectedCategory];
const IconComponent = config?.icon;

return (
Expand Down Expand Up @@ -69,7 +66,7 @@ const SimpleProfileCard: React.FC = () => {
<IconComponent className="h-4 w-4 text-white" />
</div>
<span className="text-sm leading-6 font-bold text-white">
#{categoryLabels[selectedCategory]}
#{config.label}
</span>
</div>
) : (
Expand Down
20 changes: 5 additions & 15 deletions src/app/dashboard/components/ScrapCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
import { useRouter } from "next/navigation";
import { ScrapIcon } from "@/components/icons/ic_scrap";
import { ArrowRightIcon } from "@/components/icons/ic_arrow_right";
import { categoryIcons, categoryColors, ScrapCardData } from "@/types/category";
import { getCategoryType } from "@/config/categories";
import { CategoryChip } from "@/components/ui/category";
import type { ScrapCardData } from "@/types/scrapCard";

interface ScrapCardProps {
card: ScrapCardData;
}

export default function ScrapCard({ card }: ScrapCardProps) {
const router = useRouter();
const IconComponent = categoryIcons[card.category];
const colorClass = categoryColors[card.category];
const categoryType = getCategoryType(card.category);

const handleClick = () => {
if (card.slug) {
Expand All @@ -27,18 +28,7 @@ export default function ScrapCard({ card }: ScrapCardProps) {
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div
className={`h-6 w-6 ${colorClass} flex items-center justify-center rounded-full p-1 outline-[0.25px] outline-offset-[-0.25px] outline-white`}
>
{IconComponent && (
<IconComponent
className="h-4 w-4 text-white"
width={16}
height={16}
/>
)}
</div>

<CategoryChip category={categoryType} />
<div className="h-6 w-43">
<span className="text-subtitle1 line-clamp-1 text-gray-50">
{card.term}
Expand Down
13 changes: 9 additions & 4 deletions src/app/dashboard/components/ScrapSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import Link from "next/link";
import { useState } from "react";
import { ScrapIcon } from "@/components/icons/ic_scrap";
import { categoryIcons, ScrapCardData } from "@/types/category";
import { CATEGORIES, CATEGORY_KEYS } from "@/config/categories";
import type { ScrapCardData } from "@/types/scrapCard";
import CategoryTag from "./CategoryTag";
import ScrapCard from "./ScrapCard";
import { sortCards, SortType } from "../utils/order";
import SortDropdown from "@/components/ui/SortDropdown";
import { BRAND_GRADIENT } from "@/constants/theme";

interface ScrapSectionProps {
totalCount: number;
Expand All @@ -24,7 +26,8 @@ export default function ScrapSection({
cards,
isLoading = false,
}: ScrapSectionProps) {
const categories = Object.keys(categoryIcons);
// 한글 라벨 목록 생성
const categories = CATEGORY_KEYS.map((key) => CATEGORIES[key].label);
const [sortType, setSortType] = useState<SortType>("latest");

const sortedCards = sortCards(cards, sortType);
Expand All @@ -33,7 +36,9 @@ export default function ScrapSection({
<div className="glass flex w-full flex-col gap-8 rounded-3xl bg-white/10 px-9 py-10">
<div className="flex items-center justify-between">
<div className="flex items-center gap-[0.69rem]">
<div className="flex items-center justify-center rounded-[0.6rem] bg-linear-to-r from-[#6E50C8] to-[#CE5E61] p-[0.58rem]">
<div
className={`flex items-center justify-center rounded-[0.6rem] ${BRAND_GRADIENT.bg} p-[0.58rem]`}
>
<ScrapIcon
className="h-4 w-4 text-white"
width={16}
Expand Down Expand Up @@ -86,7 +91,7 @@ export default function ScrapSection({
</p>
<Link
href="/search"
className="text-button-medium mt-5 rounded-lg bg-linear-to-r from-[#6E50C8] to-[#CE5E61] px-5 py-2 text-white"
className={`text-button-medium mt-5 rounded-lg ${BRAND_GRADIENT.bg} px-5 py-2 text-white`}
>
용어 검색하기
</Link>
Expand Down
Loading