Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
8 changes: 4 additions & 4 deletions src/components/badge/badge-type.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const BADGE_TYPE = Object.freeze({
acquaintance: "acquaintance",
coworker: "coworker",
family: "family",
friend: "friend",
지인: "지인",
동료: "동료",
가족: "가족",
친구: "친구",
});

export default BADGE_TYPE;
17 changes: 5 additions & 12 deletions src/components/badge/badge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,24 @@ import Colors from "../color/colors";
import BADGE_TYPE from "./badge-type";

const styles = {
[BADGE_TYPE.acquaintance]: {
[BADGE_TYPE.지인]: {
backgroundColor: Colors.beige(100),
color: Colors.beige(500),
},
[BADGE_TYPE.coworker]: {
[BADGE_TYPE.동료]: {
backgroundColor: Colors.purple(100),
color: Colors.purple(600),
},
[BADGE_TYPE.family]: {
[BADGE_TYPE.가족]: {
backgroundColor: Colors.green(100),
color: Colors.green(500),
},
[BADGE_TYPE.friend]: {
[BADGE_TYPE.친구]: {
backgroundColor: Colors.blue(100),
color: Colors.blue(500),
},
};

const title = {
[BADGE_TYPE.acquaintance]: "지인",
[BADGE_TYPE.coworker]: "동료",
[BADGE_TYPE.family]: "가족",
[BADGE_TYPE.friend]: "친구",
};

const StyledBadge = styled.div`
background-color: ${({ $type }) => styles[$type].backgroundColor};
color: ${({ $type }) => styles[$type].color};
Expand All @@ -39,7 +32,7 @@ const StyledBadge = styled.div`
`;

function Badge({ type }) {
return <StyledBadge $type={type}>{title[type]}</StyledBadge>;
return <StyledBadge $type={type}>{type}</StyledBadge>;
}

export default Badge;
10 changes: 10 additions & 0 deletions src/components/color/background-color.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Colors from "./colors";

const BACKGROUND_COLOR = Object.freeze({
beige: Colors.beige(200),
purple: Colors.purple(200),
blue: Colors.blue(200),
green: Colors.green(200),
});

export default BACKGROUND_COLOR;
13 changes: 7 additions & 6 deletions src/components/option/background-select.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect, useState } from "react";
import styled from "styled-components";
import Colors from "../color/colors";
import CheckImage from "../../assets/ic-check.svg";
import { useEffect, useState } from "react";
import { OutlinedButton } from "../button/button";
import BUTTON_SIZE from "../button/button-size";
import BACKGROUND_COLOR from "../color/background-color";
import Colors from "../color/colors";

const BackgroundWrapper = styled.div`
padding-top: 50px;
Expand Down Expand Up @@ -44,10 +45,10 @@ function BackgroundSelect({ type, selected, onSelect }) {
const [imageUrls, setImageUrls] = useState([]);

const colorOptions = [
{ color: Colors.beige(200) },
{ color: Colors.purple(200) },
{ color: Colors.blue(200) },
{ color: Colors.green(200) },
{ color: BACKGROUND_COLOR.beige },
{ color: BACKGROUND_COLOR.purple },
{ color: BACKGROUND_COLOR.blue },
{ color: BACKGROUND_COLOR.green },
];

/* useEffect(() => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/popover/popover.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function Popover({ id, alignment, action, children }) {

const handleClick = () => handleTargetClick(true);
const handleBackdropClick = () => setShowsPopover(false);
const handlePopoverClick = (event) => event.stopPropagation();

return (
<>
Expand All @@ -46,7 +47,9 @@ function Popover({ id, alignment, action, children }) {
{showsPopover && (
<Portal id="popover">
<Container onClick={handleBackdropClick}>
<StyledPopover $position={position}>{children}</StyledPopover>
<StyledPopover $position={position} onClick={handlePopoverClick}>
{children}
</StyledPopover>
</Container>
</Portal>
)}
Expand Down
21 changes: 21 additions & 0 deletions src/features/message/api/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { apiClient } from "../../../api/client";

async function getMessages({ recipientId, limit, page = 1 }) {
const searchParams = new URLSearchParams();
searchParams.append("page", page);
if (limit) {
searchParams.append("limit", limit);
}

const response = await apiClient.get(
`recipients/${recipientId}/messages/?${searchParams.toString()}`
);
if (response.status !== 200) {
throw new Error("Message data를 가져오는데 실패했습니다.");
}

const data = response.data;
return data.results;
}

export { getMessages };
3 changes: 2 additions & 1 deletion src/features/message/components/message-card-detail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ function MessageCardDetail({ message }) {
<StyledMessageCardDetail>
<Header>
<MessageSender
profileImage={message.profileImage}
profileImageUrl={message.profileImageURL}
relationship={message.relationship}
name={message.sender}
/>
<CreatedDate>{formatDate(message.createdAt, ".")}</CreatedDate>
Expand Down
3 changes: 2 additions & 1 deletion src/features/message/components/message-card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ function MessageCard({ isEditing, message, onDelete }) {
<StyledMessageCard>
<Header>
<MessageSender
profileImage={message.profileImage}
profileImageUrl={message.profileImageURL}
relationship={message.relationship}
name={message.sender}
/>
{isEditing && (
Expand Down
6 changes: 3 additions & 3 deletions src/features/message/components/message-sender.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ const StyledMessageSender = styled.div`
gap: 14px;
`;

function MessageSender({ profileImage, name }) {
function MessageSender({ profileImageUrl, relationship, name }) {
return (
<StyledMessageSender>
<Avatar source={profileImage} />
<SenderInfo name={name} type={BADGE_TYPE.coworker} />
<Avatar source={profileImageUrl} />
<SenderInfo name={name} type={BADGE_TYPE[relationship]} />
</StyledMessageSender>
);
}
Expand Down
29 changes: 29 additions & 0 deletions src/features/reaction/api/reaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { apiClient } from "../../../api/client";

async function getReactions({ recipientId }) {
const response = await apiClient.get(`recipients/${recipientId}/reactions/`);
if (response.status !== 200) {
throw new Error("Reactions data를 가져오는데 실패했습니다.");
}

const data = response.data;
return data.results;
}

async function addReaction({ recipientId, emoji }) {
const response = await apiClient.post(
`recipients/${recipientId}/reactions/`,
{
emoji,
type: "increase",
}
);
if (response.status !== 201) {
throw new Error("Reaction을 추가하는데 실패했습니다.");
}

const data = response.data;
return data;
}

export { addReaction, getReactions };
45 changes: 45 additions & 0 deletions src/features/reaction/components/add-reaction-popover.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import EmojiPicker from "emoji-picker-react";
import styled from "styled-components";
import addImage from "../../../assets/ic-face-smile-add.svg";
import { OutlinedButton } from "../../../components/button/button";
import BUTTON_SIZE from "../../../components/button/button-size";
import Popover from "../../../components/popover/popover";
import POPOVER_ALIGNMENT from "../../../components/popover/popover-alignment";
import { useMedia } from "../../../hooks/use-media";
import { media } from "../../../utils/media";

const StyledPopoverAction = styled(OutlinedButton)`
${media.mobile} {
padding: 0 8px;
}
`;

function PopoverAction() {
const { isMobile } = useMedia();

return (
<StyledPopoverAction
size={BUTTON_SIZE.small}
title={isMobile ? null : "추가"}
icon={addImage}
/>
);
}

function AddReactionPopover({ onSelect }) {
const handleEmojiClick = (data) => {
onSelect(data.emoji);
};

return (
<Popover
id="emoji-picker-popover"
alignment={POPOVER_ALIGNMENT.right}
action={<PopoverAction />}
>
<EmojiPicker onEmojiClick={handleEmojiClick} />
</Popover>
);
}

export default AddReactionPopover;
53 changes: 53 additions & 0 deletions src/features/reaction/components/all-reactions-popover.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import styled from "styled-components";
import arrowDownImage from "../../../assets/ic-chevron-down.svg";
import EmojiBadge from "../../../components/badge/emoji-badge";
import Popover from "../../../components/popover/popover";
import POPOVER_ALIGNMENT from "../../../components/popover/popover-alignment";
import { media } from "../../../utils/media";

const StyledPopoverAction = styled.button`
background: none;
border: none;
width: 36px;
height: 36px;
cursor: pointer;
padding: 0;
`;

function PopoverAction() {
return (
<StyledPopoverAction>
<img src={arrowDownImage} alt="전체 리액션 보기" />
</StyledPopoverAction>
);
}

const StyledAllReactions = styled.div`
padding: 24px;
display: grid;
grid-template-columns: repeat(4, min-content);
row-gap: 10px;
column-gap: 8px;

${media.mobile} {
grid-template-columns: repeat(3, min-content);
}
`;

function AllReactionsPopover({ reactions }) {
return (
<Popover
id="rolling-paper-reactions-popover"
alignment={POPOVER_ALIGNMENT.right}
action={<PopoverAction />}
>
<StyledAllReactions>
{reactions.map(({ id, emoji, count }) => (
<EmojiBadge key={id} emoji={emoji} count={count} />
))}
</StyledAllReactions>
</Popover>
);
}

export default AllReactionsPopover;
23 changes: 23 additions & 0 deletions src/features/reaction/components/received-reactions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from "styled-components";
import { useMedia } from "../../../hooks/use-media";
import AllReactionsPopover from "./all-reactions-popover";
import TopReactions from "./top-reactions";

const StyledRollingPaperReactions = styled.div`
display: flex;
gap: 2px;
align-items: center;
`;

function ReceivedReactions({ reactions }) {
const { isMobile } = useMedia();

return (
<StyledRollingPaperReactions>
<TopReactions reactions={reactions.slice(0, 3)} />
<AllReactionsPopover reactions={reactions.slice(0, isMobile ? 6 : 8)} />
</StyledRollingPaperReactions>
);
}

export default ReceivedReactions;
6 changes: 6 additions & 0 deletions src/features/reaction/components/top-reactions-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const TOP_REACTIONS_LAYOUT = Object.freeze({
normal: "normal",
compact: "compact",
});

export default TOP_REACTIONS_LAYOUT;
25 changes: 25 additions & 0 deletions src/features/reaction/components/top-reactions.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from "styled-components";
import EmojiBadge from "../../../components/badge/emoji-badge";
import TOP_REACTIONS_LAYOUT from "./top-reactions-layout";

const gap = {
[TOP_REACTIONS_LAYOUT.normal]: "8px",
[TOP_REACTIONS_LAYOUT.compact]: "4px",
};

const StyledTopReactions = styled.div`
display: flex;
gap: ${({ $layout }) => gap[$layout]};
`;

function TopReactions({ reactions, layout = TOP_REACTIONS_LAYOUT.normal }) {
return (
<StyledTopReactions $layout={layout}>
{reactions.map(({ id, emoji, count }) => (
<EmojiBadge key={id} emoji={emoji} count={count} />
))}
</StyledTopReactions>
);
}

export default TopReactions;
12 changes: 8 additions & 4 deletions src/features/rolling-paper/api/recipients.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import mockRecipient from "./mock-recipient.json";
import { apiClient } from "../../../api/client";

async function getRecipient() {
// NOTE: API를 연동하기 전에 mock data로 먼저 개발합니다.
return mockRecipient;
async function getRecipient({ id }) {
const response = await apiClient.get(`recipients/${id}/`);
if (response.status !== 200) {
throw new Error("Recipient data를 가져오는데 실패했습니다.");
}

return response.data;
}

export { getRecipient };
Loading