지금 바로 시작해보세요
diff --git a/src/app/(route)/_landing/_components/CooperationSection/CooperationSection.tsx b/src/app/(route)/_landing/_components/CooperationSection/CooperationSection.tsx
index d87c5298..5213bafa 100644
--- a/src/app/(route)/_landing/_components/CooperationSection/CooperationSection.tsx
+++ b/src/app/(route)/_landing/_components/CooperationSection/CooperationSection.tsx
@@ -1,20 +1,63 @@
+"use client";
+
+import { useRef } from "react";
+import gsap from "gsap";
+import { useGSAP } from "@gsap/react";
+import ScrollTrigger from "gsap/ScrollTrigger";
import { DeviceImage, FeatureBlock } from "../_internal";
import { COOPERATION_SECTION } from "../../_constants";
+if (typeof window !== "undefined") {
+ gsap.registerPlugin(ScrollTrigger);
+}
+
const CooperationSection = () => {
+ const container = useRef(null);
+
+ useGSAP(
+ () => {
+ gsap.from(".feature-block", {
+ scrollTrigger: {
+ trigger: ".feature-block",
+ start: "top 95%",
+ toggleActions: "play none none reverse",
+ },
+ y: 50,
+ opacity: 0,
+ duration: 0.8,
+ });
+
+ gsap.from(".feature-image", {
+ scrollTrigger: {
+ trigger: ".feature-image",
+ start: "top 95%",
+ toggleActions: "play none none reverse",
+ },
+ y: 30,
+ opacity: 0,
+ duration: 1,
+ delay: 0.2,
+ ease: "power3.out",
+ });
+ },
+ { scope: container },
+ );
+
return (
-
+
-
-
+
+
+
+
{
+ const container = useRef(null);
+
+ useGSAP(
+ () => {
+ gsap.from(".feature-block", {
+ scrollTrigger: {
+ trigger: ".feature-block",
+ start: "top 85%",
+ toggleActions: "play none none reverse",
+ },
+ y: 50,
+ opacity: 0,
+ duration: 0.8,
+ });
+
+ gsap.from(".feature-image", {
+ scrollTrigger: {
+ trigger: ".feature-image",
+ start: "top 85%",
+ toggleActions: "play none none reverse",
+ },
+ y: 30,
+ opacity: 0,
+ duration: 1,
+ delay: 0.2,
+ ease: "power3.out",
+ });
+ },
+ { scope: container },
+ );
+
return (
-
+
-
-
+
+
+
+
{
+ const container = useRef(null);
+
+ useGSAP(
+ () => {
+ const tl = gsap.timeline();
+
+ tl.from(".hero-text-item", {
+ x: -50,
+ opacity: 0,
+ duration: 0.8,
+ stagger: 0.2,
+ ease: "power2.out",
+ })
+
+ .from(
+ ".hero-image",
+ {
+ x: 50,
+ opacity: 0,
+ duration: 1,
+ ease: "power2.out",
+ },
+ "-=0.4",
+ );
+ },
+ { scope: container },
+ );
+
return (
-
-
-
-
+
+
+
+
{
className="size-9 pc:size-12"
draggable={false}
/>
-
+
- 함께 만들어가는 To Do list
+ 함께 만들어가는 투두 리스트
Coworkers
- {/* TODO(김원선): 버튼 컴포넌트 as prop이 따로 구현되면 변경 예정 */}
지금 시작하기
-
+
{
tabletSrc: "/landing/img-1-1.png",
desktopSrc: "/landing/img-1.png",
}}
- ImageClassName="object-fill object-left w-full h-full tablet:object-cover"
+ ImageClassName="object-contain object-left w-full h-full"
/>
지금 시작하기
diff --git a/src/app/(route)/_landing/_components/KanbanSection/KanbanSection.tsx b/src/app/(route)/_landing/_components/KanbanSection/KanbanSection.tsx
index 415274df..bb1eb402 100644
--- a/src/app/(route)/_landing/_components/KanbanSection/KanbanSection.tsx
+++ b/src/app/(route)/_landing/_components/KanbanSection/KanbanSection.tsx
@@ -1,21 +1,64 @@
+"use client";
+
+import { useRef } from "react";
+import gsap from "gsap";
+import { useGSAP } from "@gsap/react";
+import ScrollTrigger from "gsap/ScrollTrigger";
import { DeviceImage, FeatureBlock } from "../_internal";
import { KANBAN_SECTION } from "../../_constants";
+if (typeof window !== "undefined") {
+ gsap.registerPlugin(ScrollTrigger);
+}
+
const KanbanSection = () => {
+ const container = useRef
(null);
+
+ useGSAP(
+ () => {
+ gsap.from(".feature-block", {
+ scrollTrigger: {
+ trigger: ".feature-block",
+ start: "top 85%",
+ toggleActions: "play none none reverse",
+ },
+ y: 0,
+ opacity: 0,
+ duration: 0.8,
+ });
+
+ gsap.from(".feature-image", {
+ scrollTrigger: {
+ trigger: ".feature-image",
+ start: "top 85%",
+ toggleActions: "play none none reverse",
+ },
+ y: 0,
+ opacity: 0,
+ duration: 1,
+ delay: 0.2,
+ ease: "back.out(1.7)",
+ });
+ },
+ { scope: container },
+ );
+
return (
-
+
-
-
+
+
+
+
-
+
diff --git a/src/app/(route)/dashboard/[id]/_components/ArticleDetail.tsx b/src/app/(route)/dashboard/[id]/_components/ArticleDetail.tsx
index ef3eb80b..d3076906 100644
--- a/src/app/(route)/dashboard/[id]/_components/ArticleDetail.tsx
+++ b/src/app/(route)/dashboard/[id]/_components/ArticleDetail.tsx
@@ -1,11 +1,17 @@
import ArticleBody from "./_internal/ArticleBody";
import ArticleComments from "./_internal/ArticleComments";
+import { LinkButton } from "@/common";
const ArticleDetail = () => {
return (
);
};
diff --git a/src/app/(route)/dashboard/[id]/_components/Modal/ArticleEditModal.tsx b/src/app/(route)/dashboard/[id]/_components/Modal/ArticleEditModal.tsx
new file mode 100644
index 00000000..bb14eb87
--- /dev/null
+++ b/src/app/(route)/dashboard/[id]/_components/Modal/ArticleEditModal.tsx
@@ -0,0 +1,154 @@
+"use client";
+
+import { usePatchArticle } from "@/api/hooks";
+import { BaseButton, Input, InputBox, Modal, Icon, FloatingButton } from "@/common";
+import { ArticleDetail } from "@/types/ArticleType";
+import { ChangeEvent, useState } from "react";
+import Image from "next/image";
+import useImageUpload from "@/hooks/useImageUpload";
+
+interface ArticleEditModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ article: ArticleDetail;
+}
+
+interface FormStateType {
+ title: string;
+ content: string;
+ image: string | null;
+}
+
+const ArticleEditModal = ({ isOpen, onClose, article }: ArticleEditModalProps) => {
+ const { mutate: patchArticle, isPending } = usePatchArticle();
+
+ const [formState, setFormState] = useState({
+ title: article.title,
+ content: article.content,
+ image: article.image,
+ });
+
+ const { preview, file, handleImageChange, uploadImage, isUploading, clear } = useImageUpload(
+ article.image ?? undefined,
+ );
+
+ const isDisabledEditButton = isPending || isUploading || !formState.title.trim() || !formState.content.trim();
+
+ const handleTextChange = (e: ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormState((prev) => ({ ...prev, [name]: value }));
+ };
+
+ const handleRemoveImage = () => {
+ clear();
+ setFormState((prev) => ({
+ ...prev,
+ image: null,
+ }));
+ };
+
+ const handleEditClick = async () => {
+ const { title, content } = formState;
+
+ let imageUrl: string | null = null;
+
+ if (file) {
+ imageUrl = await uploadImage();
+ }
+ const newForm = {
+ title,
+ content,
+ image: imageUrl !== null ? imageUrl : formState.image === null ? null : formState.image,
+ };
+ patchArticle(
+ {
+ articleId: article.id,
+ body: newForm,
+ },
+ {
+ onSuccess: () => {
+ onClose();
+ },
+ },
+ );
+ };
+
+ return (
+
+
+ 게시글 수정하기
+
+
+
+
+
+
+ 닫기
+
+
+
+ {isPending || isUploading ? "수정 중..." : "수정하기"}
+
+
+
+ );
+};
+
+export default ArticleEditModal;
diff --git a/src/app/(route)/dashboard/[id]/_components/_internal/ArticleBody.tsx b/src/app/(route)/dashboard/[id]/_components/_internal/ArticleBody.tsx
index bd5e22fd..5d18ee31 100644
--- a/src/app/(route)/dashboard/[id]/_components/_internal/ArticleBody.tsx
+++ b/src/app/(route)/dashboard/[id]/_components/_internal/ArticleBody.tsx
@@ -8,7 +8,7 @@ import ArticleTitle from "../../../_components/Article/_internal/ArticleTitle";
import ArticleWriter from "../../../_components/Article/_internal/ArticleWriter";
import ArticleContent from "../../../_components/Article/_internal/ArticleContent";
import ArticleLikeButton from "./ArticleLikeButton";
-import ArticleEditModal from "./ArticleEditModal";
+import ArticleEditModal from "../Modal/ArticleEditModal";
import { useState } from "react";
const ArticleBody = () => {
@@ -46,12 +46,19 @@ const ArticleBody = () => {
-
setIsOpenEditModal(false)} />
+ {isOpenEditModal && (
+ setIsOpenEditModal(false)}
+ article={article}
+ />
+ )}
);
};
diff --git a/src/app/(route)/dashboard/[id]/_components/_internal/ArticleEditModal.tsx b/src/app/(route)/dashboard/[id]/_components/_internal/ArticleEditModal.tsx
deleted file mode 100644
index 29ef4832..00000000
--- a/src/app/(route)/dashboard/[id]/_components/_internal/ArticleEditModal.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Modal } from "@/common";
-
-interface ArticleEditModalProps {
- isOpen: boolean;
- onClose: () => void;
-}
-
-const ArticleEditModal = ({ isOpen, onClose }: ArticleEditModalProps) => {
- return (
-
-
- 게시글 수정하기
-
-
- );
-};
-
-export default ArticleEditModal;
diff --git a/src/app/(route)/dashboard/[id]/page.tsx b/src/app/(route)/dashboard/[id]/page.tsx
index 93232390..2f6d81ff 100644
--- a/src/app/(route)/dashboard/[id]/page.tsx
+++ b/src/app/(route)/dashboard/[id]/page.tsx
@@ -1,5 +1,11 @@
import { PageLayout } from "@/common";
import { ArticleDetail } from "./_components";
+import { Metadata } from "next";
+
+export const metadata: Metadata = {
+ title: "Coworkers | 자유게시판",
+ description: "게시글을 통해 팀원들과 소통해보세요.",
+};
const DashBoardDetail = () => {
return (
diff --git a/src/app/(route)/dashboard/_components/Article/BestArticleCard.tsx b/src/app/(route)/dashboard/_components/Article/BestArticleCard.tsx
index 9ceff105..8208028b 100644
--- a/src/app/(route)/dashboard/_components/Article/BestArticleCard.tsx
+++ b/src/app/(route)/dashboard/_components/Article/BestArticleCard.tsx
@@ -1,5 +1,4 @@
import Link from "next/link";
-import Image from "next/image";
import { useGetArticle } from "@/api/hooks";
import { Icon } from "@/common";
import ArticleBestBadge from "./_internal/ArticleBestBadge";
@@ -17,14 +16,12 @@ const BestArticleCard = ({ articleId }: { articleId: number }) => {
return (
-
+
-
-
- {/* {article.image &&
} */}
+
+