Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"emoji-picker-react": "^4.15.0",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-infinite-scroll-component": "^6.1.0",
"react-router": "^7.9.5",
"styled-components": "^6.1.19"
},
Expand Down
8 changes: 6 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ function App() {
<Route element={<GlobalLayout />}>
<Route path="/" element={<TempPage />} />
<Route path="/main" element={<MainPage />} />
<Route path="/list" element={<ListPage />} />
<Route path="/rolling" element={<RollingPage />} />
{/* <Route path="/list" element={<ListPage />} /> */}

{/* 롤링 페이퍼 뷰어/편집 모드 */}
<Route path="/post/:id" element={<RollingPage />} />
<Route path="/post/:id/edit" element={<RollingPage />} />

<Route path="/post" element={<PostPage />} />
<Route path="/message" element={<MessagePage />} />
<Route path="/test" element={<TestPage />} />
Expand Down
22 changes: 22 additions & 0 deletions src/api/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import axios from "axios";

// 기수-팀 번호 설정 (환경변수로 관리 가능)
const TEAM_CODE = "2-1"; // 추후 환경변수로 변경 가능

// API 기본 설정
const BASE_URL = `https://rolling-api.vercel.app/${TEAM_CODE}`;

/**
* API 클라이언트
* 책임: axios 인스턴스 생성 및 기본 설정
*/
const apiClient = axios.create({
baseURL: BASE_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});

export default apiClient;
export { TEAM_CODE };
86 changes: 86 additions & 0 deletions src/api/rolling-page-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import apiClient from "./client";

/**
* Recipients API 함수들
* 책임: Recipients 관련 API 호출
*/

// 유저 상세 조회

export const getRecipientById = async (recipientId) => {
try {
const response = await apiClient.get(`/recipients/${recipientId}/`);
return response.data;
} catch (error) {
console.error(`Failed to fetch recipient ${recipientId}:`, error);
throw error;
}
};

// 롤링 페이퍼 전체 삭제

export const deleteRecipient = async (recipientId) => {
try {
const response = await apiClient.delete(`/recipients/${recipientId}/`);
return response.data;
} catch (error) {
console.error(`Failed to delete recipient ${recipientId}:`, error);
throw error;
}
};


// 유저의 모든 리액션 조회

export const getReactions = async (recipientId, params = {}) => {
try {
const response = await apiClient.get(`/recipients/${recipientId}/reactions/`, {
params,
});
return response.data;
} catch (error) {
console.error(`Failed to fetch reactions for recipient ${recipientId}:`, error);
throw error;
}
};


// 수신자에게 리액션 추가/감소

export const addReaction = async (recipientId, data) => {
try {
const response = await apiClient.post(`/recipients/${recipientId}/reactions/`, data);
return response.data;
} catch (error) {
console.error(`Failed to add reaction to recipient ${recipientId}:`, error);
throw error;
}
};


// 수신자의 메시지 목록 조회

export const getRecipientMessages = async (recipientId, { limit = 6, offset = 0 } = {}) => {
try {
const response = await apiClient.get(`/recipients/${recipientId}/messages/`, {
params: { limit, offset },
});
return response.data;
} catch (error) {
console.error(`Failed to fetch messages for recipient ${recipientId}:`, error);
throw error;
}
};

// 메시지 삭제

export const deleteMessage = async (messageId) => {
try {
const response = await apiClient.delete(`/messages/${messageId}/`);
return response.data;
} catch (error) {
console.error(`Failed to delete message ${messageId}:`, error);
throw error;
}
};

4 changes: 1 addition & 3 deletions src/components/common/global-layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ const PAGES_WITH_BUTTON = ["main", "list"];

export default function GlobalLayout() {
const location = useLocation();
const showButton = PAGES_WITH_BUTTON.some((page) =>
location.pathname.includes(page)
);
const showButton = PAGES_WITH_BUTTON.some((page) => location.pathname.includes(page));

return (
<>
Expand Down
12 changes: 10 additions & 2 deletions src/components/common/header.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { Link } from "react-router";
import { Link, useLocation } from "react-router";
import styled from "styled-components";
import logo from "@/assets/icons/logo.svg";
import Button from "@/components/common/button";
import media from "@/styles/media";

const ContainWrapper = styled.div`
position: sticky;
top: 0;
background-color: white;
border-bottom: 1px solid #ededed;
z-index: 1003;

${props => props.$isRollingPage && media.small`
display: none;
`}
`;

const Contain = styled.div`
Expand All @@ -35,9 +40,12 @@ const ButtonWrapper = styled.div`
`;

export default function Header({ showButton }) {
const location = useLocation();
const isRollingPage = location.pathname.startsWith('/post/');

return (
<>
<ContainWrapper>
<ContainWrapper $isRollingPage={isRollingPage}>
<Contain>
<HeaderWrapper>
<Link to="/">
Expand Down
166 changes: 84 additions & 82 deletions src/components/common/modal-layout.jsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,84 @@
import React from 'react';
import styled from 'styled-components';
import { colors } from '@/styles/colors';
import { font } from '@/styles/font';
import media from '@/styles/media';

const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: transparent;
z-index: 999;
`;

const ModalContainer = styled.div`
background: white;
border-radius: 16px;
padding: 40px;
width: 480px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);

${media.medium`
width: 400px;
padding: 30px;
`}

${media.small`
width: 320px;
padding: 24px;
`}
`;

const ModalTitle = styled.h2`
${font.bold24}
color: ${colors.gray[900]};
margin-bottom: 24px;
text-align: center;
`;

const ModalContent = styled.div`
width: 100%;
`;

const CloseButton = styled.button`
width: 100%;
margin-top: 16px;
padding: 6px;
background: transparent;
border: 1px solid ${colors.gray[300]};
border-radius: 8px;
cursor: pointer;
${font.regular16}
color: ${colors.gray[700]};
transition: all 0.2s;

&:hover {
background: ${colors.gray[50]};
}
`;

/**
* 공통 모달 레이아웃 컴포넌트
* 책임: 모달의 기본 구조와 레이아웃 제공
*/
export default function ModalLayout({ isOpen, onClose, title, children, showCloseButton = true }) {
if (!isOpen) return null;

return (
<Overlay onClick={onClose}>
<ModalContainer onClick={(e) => e.stopPropagation()}>
{title && <ModalTitle>{title}</ModalTitle>}
<ModalContent>{children}</ModalContent>
{showCloseButton && (
<CloseButton onClick={onClose}>닫기</CloseButton>
)}
</ModalContainer>
</Overlay>
);
}

import React from "react";
import styled from "styled-components";
import { colors } from "@/styles/colors";
import { font } from "@/styles/font";
import media from "@/styles/media";

const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
`;

const ModalContainer = styled.div`
background: white;
border-radius: 16px;
padding: 40px;
width: 600px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);

${media.medium`
width: 600px;
padding: 30px;
`}

${media.small`
width: 320px;
padding: 24px;
`}
`;

const ModalTitle = styled.h2`
${font.bold24}
color: ${colors.gray[900]};
margin-bottom: 24px;
text-align: center;
`;

const ModalContent = styled.div`
width: 100%;
`;

const CloseButton = styled.button`
width: 100%;
margin-top: 16px;
padding: 6px;
background: transparent;
border: 1px solid ${colors.gray[300]};
border-radius: 8px;
cursor: pointer;
${font.regular16}
color: ${colors.gray[700]};
transition: all 0.2s;

&:hover {
background: ${colors.gray[50]};
}
`;

/**
* 공통 모달 레이아웃 컴포넌트
* 책임: 모달의 기본 구조와 레이아웃 제공
*/
export default function ModalLayout({ isOpen, onClose, title, children, showCloseButton = true }) {
if (!isOpen) return null;

return (
<Overlay onClick={onClose}>
<ModalContainer onClick={(e) => e.stopPropagation()}>
{title && <ModalTitle>{title}</ModalTitle>}
<ModalContent>{children}</ModalContent>
{showCloseButton && <CloseButton onClick={onClose}>닫기</CloseButton>}
</ModalContainer>
</Overlay>
);
}
2 changes: 1 addition & 1 deletion src/components/common/toast-provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function ToastProvider({ children }) {
</Toast>
)}
</>,
toastContainer
toastContainer,
)}
</ToastContext.Provider>
);
Expand Down
Loading