diff --git a/.github/workflows/auto-merge-develop-pr.yml b/.github/workflows/auto-merge-develop-pr.yml index daec7f9..86de8d7 100644 --- a/.github/workflows/auto-merge-develop-pr.yml +++ b/.github/workflows/auto-merge-develop-pr.yml @@ -1,23 +1,32 @@ -name: Auto Merge PR from Develop to Main +name: Close Linked Issues if Checklist Complete on: pull_request: - branches: - - main - types: [opened, synchronize, reopened] + types: [closed] permissions: - contents: write - pull-requests: write + issues: write + pull-requests: read + contents: read jobs: - auto-merge: - if: github.event.pull_request.head.ref == 'develop' + close-linked-issues: + if: github.event.pull_request.merged == true runs-on: ubuntu-latest + steps: - - name: Enable auto-merge - uses: peter-evans/enable-pull-request-automerge@v3 + - name: Check if checklist is fully complete + id: checklist + run: | + BODY="${{ github.event.pull_request.body }}" + UNCHECKED=$(echo "$BODY" | grep -c '\[ \]' || true) + if [ "$UNCHECKED" -eq 0 ]; then + echo "checklist-complete=true" >> $GITHUB_OUTPUT + else + echo "checklist-complete=false" >> $GITHUB_OUTPUT + fi + - name: Close linked issues if checklist is complete + if: steps.checklist.outputs.checklist-complete == 'true' + uses: peter-evans/close-issue@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - merge-method: merge - pull-request-number: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/closed-issue.yml b/.github/workflows/closed-issue.yml new file mode 100644 index 0000000..375418c --- /dev/null +++ b/.github/workflows/closed-issue.yml @@ -0,0 +1,41 @@ +name: Close Mentioned Issues if Checklist Complete + +on: + pull_request: + types: [closed] + +permissions: + issues: write + pull-requests: read + contents: read + +jobs: + close-mentioned-issues: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Check if checklist is fully complete + id: checklist + run: | + BODY="${{ github.event.pull_request.body }}" + UNCHECKED=$(echo "$BODY" | grep -c '\[ \]') + if [ "$UNCHECKED" -eq 0 ]; then + echo "checklist-complete=true" >> $GITHUB_OUTPUT + else + echo "checklist-complete=false" >> $GITHUB_OUTPUT + fi + + - name: Extract issue numbers + id: issues + run: | + BODY="${{ github.event.pull_request.body }}" + echo "ISSUES=$(echo "$BODY" | grep -oE '#[0-9]+' | tr -d '#' | tr '\n' ' ')" >> $GITHUB_OUTPUT + + - name: Close mentioned issues + if: steps.checklist.outputs.checklist-complete == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + for issue in ${{ steps.issues.outputs.ISSUES }}; do + gh issue close "$issue" --repo "${{ github.repository }}" + done diff --git a/.gitignore b/.gitignore index a547bf3..3b0b403 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ dist-ssr *.njsproj *.sln *.sw? + +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e7a3fb4..714014e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^22.15.24", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@types/react-router-dom": "^5.3.3", @@ -1269,6 +1270,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.15.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.24.tgz", + "integrity": "sha512-w9CZGm9RDjzTh/D+hFwlBJ3ziUaVw7oufKA3vOFSOZlzmW9AkZnfjPb+DLnrV6qtgL/LNmP0/2zBNCFHL3F0ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/react": { "version": "19.1.5", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz", @@ -3280,6 +3291,13 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 7c109d4..6b2d3a1 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@types/node": "^22.15.24", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@types/react-router-dom": "^5.3.3", diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..f824337 --- /dev/null +++ b/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git "a/public/images/characters/\354\225\231\352\270\200\354\235\264.png" "b/public/images/characters/\354\225\231\352\270\200\354\235\264.png" new file mode 100644 index 0000000..04a4cfd Binary files /dev/null and "b/public/images/characters/\354\225\231\352\270\200\354\235\264.png" differ diff --git "a/public/images/characters/\354\233\205\354\235\264.png" "b/public/images/characters/\354\233\205\354\235\264.png" new file mode 100644 index 0000000..6699d14 Binary files /dev/null and "b/public/images/characters/\354\233\205\354\235\264.png" differ diff --git "a/public/images/characters/\355\213\260\353\260\224\353\205\270.png" "b/public/images/characters/\355\213\260\353\260\224\353\205\270.png" new file mode 100644 index 0000000..9c0c4b7 Binary files /dev/null and "b/public/images/characters/\355\213\260\353\260\224\353\205\270.png" differ diff --git a/src/App.tsx b/src/App.tsx index 6c80a53..3d55f46 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import SettingPage from "./pages/chatting/SettingPage"; import ChatPage from "./pages/chatting/ChatPage"; import styled from "styled-components"; import WritingPage from "./pages/writing/WritingPage"; +import Testpage from "./pages/Testpage"; function App() { return ( @@ -19,13 +20,14 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> + } /> diff --git a/src/components/home/DiaryList.tsx b/src/components/home/DiaryList.tsx index ebdbba6..1735393 100644 --- a/src/components/home/DiaryList.tsx +++ b/src/components/home/DiaryList.tsx @@ -1,49 +1,48 @@ +import { useEffect, useState } from "react"; import styled from "styled-components"; +import { getAllDiary } from "../../services/apis/diary/diary"; +import { useNavigate } from "react-router-dom"; -const diaries = [ - { - date: "2025.05.01.", - title: "오늘은 영화 보러 간 날", - tags: ["#취미", "#휴식"], - content: "재밌었다!".repeat(30), - }, - { - date: "2025.05.01.", - title: "오늘은 영화 보러 간 날", - tags: ["#취미", "#휴식"], - content: "재밌었다!".repeat(30), - }, - { - date: "2025.05.01.", - title: "오늘은 영화 보러 간 날", - tags: ["#취미", "#휴식"], - content: "재밌었다!".repeat(30), - }, - { - date: "2025.05.01.", - title: "오늘은 영화 보러 간 날", - tags: ["#취미", "#휴식"], - content: "재밌었다!".repeat(30), - }, - { - date: "2025.05.01.", - title: "오늘은 영화 보러 간 날", - tags: ["#취미", "#휴식"], - content: "재밌었다!".repeat(30), - }, -]; +interface Diary { + id: number; + date: string; + title: string; + hashTags: string[]; + content: string; +} const DiaryList = () => { + const [diaries, setDiaries] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + getAllDiary() + .then((data) => { + setDiaries(data); + setLoading(false); + }) + .catch((err) => { + console.error("일기 불러오기 실패:", err); + setError("일기를 불러오지 못했습니다."); + setLoading(false); + }); + }, []); + + if (loading) return
로딩 중...
; + if (error) return
{error}
; + return ( - {diaries.map((diary, index) => ( - + {diaries.map((diary) => ( + navigate(`/diary/${diary.id}`)}> {diary.date} {diary.title} - {diary.tags.map((tag, i) => ( - {tag} + {diary.hashTags.map((tag, i) => ( + #{tag} ))} {diary.content} diff --git a/src/components/login/Login.tsx b/src/components/login/Login.tsx index 86f9f38..4b33738 100644 --- a/src/components/login/Login.tsx +++ b/src/components/login/Login.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; const Login = () => { // const REST_API_KEY = 'b57e43877ad662a5a26d85d3b6ff834e'; // const REDIRECT_URI = 'http://localhost:5173/oauth/kakao/callback'; - const link = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=b57e43877ad662a5a26d85d3b6ff834e&redirect_uri=http://localhost:5173/oauth/kakao/callback`; + const link = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=b57e43877ad662a5a26d85d3b6ff834e&redirect_uri=https://withsoulmate.netlify.app/oauth/kakao/callback`; const loginHandler = () => { window.location.href = link; diff --git a/src/pages/DiaryDetail.tsx b/src/pages/DiaryDetail.tsx index e4e7093..ca72115 100644 --- a/src/pages/DiaryDetail.tsx +++ b/src/pages/DiaryDetail.tsx @@ -1,51 +1,93 @@ import styled from "styled-components"; import { IoHomeOutline } from "react-icons/io5"; import { BsPencil } from "react-icons/bs"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { getDiary } from "../services/apis/diary/diary"; +import { generateAIComment } from "../services/gpt/openai"; + +interface DiaryResponse { + id: number; + date: string; + title: string; + content: string; + comment: string; + character: string; + hashTags: string[]; +} const DiaryDetail = () => { const navigate = useNavigate(); + const { diaryId } = useParams(); + + const [diary, setDiary] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + const [aiComment, setAiComment] = useState(null); + + useEffect(() => { + if (diaryId) { + getDiary(diaryId) + .then((data) => { + setDiary(data); + setLoading(false); + }) + .catch((err) => { + console.error("Error fetching diary:", err); + setError("일기를 불러오는 데 실패했습니다."); + setLoading(false); + }); + } + }, [diaryId]); + + useEffect(() => { + if (diary) { + generateAIComment(diary.content, diary.title) + .then((comment) => setAiComment(comment)) + .catch(() => setAiComment("AI 코멘트를 생성하는 데 실패했습니다.")); + } + }, [diary]); + + const formatDate = (rawDate: string) => { + const date = new Date(rawDate); + return `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, "0")}.${String(date.getDate()).padStart(2, "0")}.`; + }; + + if (loading) return
로딩 중...
; + if (error) return
{error}
; + if (!diary) return
일기 데이터가 없습니다.
; return (
navigate("/")} /> - 2025.05.01. - + {formatDate(diary.date)} + navigate(`/edit/${diary.id}`)} />
- 오늘은 영화 보러 간 날 + {diary.title} - #취미 - #휴식 + {diary.hashTags && diary.hashTags.length > 0 ? ( + diary.hashTags.map((tag, idx) => #{tag}) + ) : ( + #태그없음 + )} - - 오늘은 오랜만에 영화관에 가서 영화를 보고 왔다. 요즘 바빠서 제대로 - 쉬지도 못했는데, 이렇게 여유롭게 시간을 보내니 기분이 참 좋았다. - 친구와 약속을 잡고 미리 예매까지 해두었는데, 다행히 좋은 자리에서 - 관람할 수 있었다.
-
- 보고 싶었던 영화라 기대가 컸는데, 그 기대를 저버리지 않고 정말 - 재미있었다. 배우들의 연기도 훌륭했고, 스토리도 탄탄해서 중간에 지루할 - 틈이 없었다. 영화관 특유의 분위기, 어두운 조명과 커다란 스크린, 웅장한 - 사운드까지 모든 게 몰입감을 더해줬다.
-
- 팝콘과 콜라도 빠질 수 없어서, 먹으면서 영화 보는 재미도 쏠쏠했다. 엔딩 - 크레딧이 올라갈 때는 아쉬운 마음도 들었지만, 오랜만에 힐링되는 시간을 - 보낸 것 같아 만족스럽다. 다음엔 다른 장르의 영화도 보러 가고 싶다. -
+ {diary.content} AI 친구의 코멘트 - - 웅이 + + {diary.character} - 오랜만에 영화관에서 좋은 시간 보냈다니 내가 다 기쁘다! 너의 여유로운 - 하루가 참 따뜻하게 느껴져 :) + {aiComment || "AI 코멘트를 생성 중입니다..."} diff --git a/src/pages/Testpage.tsx b/src/pages/Testpage.tsx new file mode 100644 index 0000000..f607683 --- /dev/null +++ b/src/pages/Testpage.tsx @@ -0,0 +1,30 @@ +import { useState } from "react"; +import { sendMessageToGPT } from "../services/gpt/openai"; + +const Testpage = () => { + const [input, setInput] = useState(""); + const [response, setResponse] = useState(""); + + const handleSubmit = async () => { + const result = await sendMessageToGPT(input); + setResponse(result); + }; + + return ( +
+

GPT와 대화하기

+