Skip to content

Commit

Permalink
Merge pull request #545 from depromeet/feat/reaction
Browse files Browse the repository at this point in the history
[Feat] 리액션 기능 추가
  • Loading branch information
sumi-0011 committed Feb 12, 2024
2 parents 708c486 + 525d873 commit f73ef39
Show file tree
Hide file tree
Showing 31 changed files with 786 additions and 6 deletions.
Binary file added public/images/reaction/BLUE_HEART.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/EYES.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/FIRE.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/GLOWING_STAR.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/PARTYING_FACE.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/PARTY_POPPER.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/PURPLE_HEART.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/SOMETHING_SPECIAL.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/SPARKLING_HEART.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/THUMBS_UP.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/reaction/UNICORN.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/apis/getQueryKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ type QueryList = {
feed: {
memberId: number;
};

// reaction
reactions: {
recordId: number;
};

missionSummaryList: {
date: string;
};
Expand Down
9 changes: 9 additions & 0 deletions src/apis/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ export const useGetMembersMe = (option?: UseQueryOptions<MemberMeResponse>) => {
});
};

export const useGetMyId = (): {
memberId?: number;
isLoading: boolean;
} => {
const { data, isLoading } = useGetMembersMe();
const memberId = data?.memberId;
return { memberId, isLoading };
};

export const useWithdrawalMember = (option?: UseMutationOptions<unknown, unknown>) => {
return useMutation({
mutationFn: MEMBER_API.withdrawalMember,
Expand Down
83 changes: 83 additions & 0 deletions src/apis/reaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import getQueryKey from '@/apis/getQueryKey';
import { type EmojiType } from '@/apis/schema/reaction';
import useMutationHandleError from '@/hooks/query/useMutationHandleError';
import { type UseMutationOptions, useQuery } from '@tanstack/react-query';

import apiInstance from './instance.api';

interface MemberProfileType {
memberId: number;
nickname: string;
profileImageUrl: string;
}

interface ReactionItemType {
reactionId: number;
createdAt: string;
updatedAt: string;
memberProfile: MemberProfileType;
}

export interface ReactionType {
emojiType: EmojiType;
count: number;
reactions: ReactionItemType[];
}

export type GetReactionsResponse = Array<ReactionType>;

interface AddReactionRequest {
missionRecordId: number;
emojiType: EmojiType;
}

interface AddReactionResponse {
reactionId: number;
emojiType: EmojiType;
memberId: number;
missionRecordId: number;
}

interface ModifyReactionRequest {
reactionId: number;
emojiType: EmojiType;
}

export const REACTION_API = {
getReactions: async (recordId: number): Promise<GetReactionsResponse> => {
const { data } = await apiInstance.get<GetReactionsResponse>(`/reactions?missionRecordId=${recordId}`);
return data;
},
addReaction: async (request: AddReactionRequest) => {
const { data } = await apiInstance.post<AddReactionResponse>('/reactions', request);
return data;
},
modifyReaction: async ({ reactionId, emojiType }: ModifyReactionRequest) => {
console.log('reactionId: ', reactionId);
const { data } = await apiInstance.put<AddReactionResponse>(`/reactions/${reactionId}`, { emojiType });
return data;
},
};

export const useGetReactions = (recordId: number) => {
return useQuery<GetReactionsResponse>({
queryKey: getQueryKey('reactions', { recordId }),
queryFn: () => REACTION_API.getReactions(recordId),
});
};

export const useAddReaction = (options?: UseMutationOptions<AddReactionResponse, unknown, AddReactionRequest>) =>
useMutationHandleError(
{ mutationFn: REACTION_API.addReaction, ...options },
{
offset: 'default',
},
);

export const useModifyReaction = (options?: UseMutationOptions<AddReactionResponse, unknown, ModifyReactionRequest>) =>
useMutationHandleError(
{ mutationFn: REACTION_API.modifyReaction, ...options },
{
offset: 'default',
},
);
27 changes: 27 additions & 0 deletions src/apis/schema/reaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export enum EmojiType {
BLUE_HEART = 'BLUE_HEART',
THUMBS_UP = 'THUMBS_UP',
FIRE = 'FIRE',
PARTY_POPPER = 'PARTY_POPPER',
UNICORN = 'UNICORN',
PARTYING_FACE = 'PARTYING_FACE',
SOMETHING_SPECIAL = 'SOMETHING_SPECIAL',
GLOWING_STAR = 'GLOWING_STAR',
SPARKLING_HEART = 'SPARKLING_HEART',
EYES = 'EYES',
}

export const REACTION_EMOJI_IMAGE: Record<EmojiType, string> = {
[EmojiType.BLUE_HEART]: '/images/reaction/BLUE_HEART.png',
[EmojiType.GLOWING_STAR]: '/images/reaction/GLOWING_STAR.png',
[EmojiType.SPARKLING_HEART]: '/images/reaction/SPARKLING_HEART.png',
[EmojiType.UNICORN]: '/images/reaction/UNICORN.png',
[EmojiType.EYES]: '/images/reaction/EYES.png',
[EmojiType.FIRE]: '/images/reaction/FIRE.png',
[EmojiType.PARTY_POPPER]: '/images/reaction/PARTY_POPPER.png',
[EmojiType.PARTYING_FACE]: '/images/reaction/PARTYING_FACE.png',
[EmojiType.SOMETHING_SPECIAL]: '/images/reaction/SOMETHING_SPECIAL.png',
[EmojiType.THUMBS_UP]: '/images/reaction/THUMBS_UP.png',
};

export const REACTION_EMOJI_LIST = Object.keys(EmojiType) as EmojiType[];
14 changes: 13 additions & 1 deletion src/app/feed/FeedItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use client';

import Link from 'next/link';
import { useGetMyId } from '@/apis/member';
import { type FeedItemType } from '@/apis/schema/feed';
import HistoryThumbnail from '@/app/record/[id]/detail/HistoryThumbnail';
import ReactionBar from '@/components/ReactionBar/ReactionBar';
import Thumbnail from '@/components/Thumbnail/Thumbnail';
import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog';
import { ROUTER } from '@/constants/router';
Expand All @@ -22,13 +24,17 @@ function FeedItem({
startedAt,
recordId,
}: FeedItemType) {
const { memberId: myId } = useGetMyId();
const isMyFeed = memberId === myId;

const handleClickFeedItem = () => {
eventLogger.logEvent(EVENT_LOG_CATEGORY.FEED, EVENT_LOG_NAME.FEED.CLICK_FEED);
};

const handleClickFollowProfile = () => {
eventLogger.logEvent(EVENT_LOG_CATEGORY.FEED, EVENT_LOG_NAME.FEED.CLICK_PROFILE);
};

return (
<li>
<Link href={ROUTER.PROFILE.DETAIL(memberId)} onClick={handleClickFollowProfile}>
Expand All @@ -37,7 +43,12 @@ function FeedItem({
<p>{nickname}</p>
</div>
</Link>
<Link href={ROUTER.RECORD.DETAIL.FOLLOW(recordId.toString())} onClick={handleClickFeedItem}>
<Link
href={
isMyFeed ? ROUTER.RECORD.DETAIL.HOME(recordId.toString()) : ROUTER.RECORD.DETAIL.FOLLOW(recordId.toString())
}
onClick={handleClickFeedItem}
>
<HistoryThumbnail imageUrl={recordImageUrl} missionDuration={duration} />
<div className={textWrapperCss}>
<p className={missionNameCss}>{name}</p>
Expand All @@ -47,6 +58,7 @@ function FeedItem({
</p>
</div>
</Link>
<ReactionBar memberId={memberId} recordId={recordId} />
</li>
);
}
Expand Down
18 changes: 16 additions & 2 deletions src/app/record/[id]/detail/MissionRecordDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@
import { useMemo } from 'react';
import { useParams } from 'next/navigation';
import { useGetRecordDetail } from '@/apis';
import MyReactionBar from '@/components/ReactionBar/MyReactionBar/MyReactionBar';
import OtherReactionBar from '@/components/ReactionBar/OtherReactionBar/OtherReactionBar';
import { css } from '@styled-system/css';
import dayjs from 'dayjs';

import HistoryThumbnail from './HistoryThumbnail';

function MissionRecordDetail() {
interface Props {
isFollow: boolean;
}

function MissionRecordDetail(props: Props) {
const params = useParams();
const { data } = useGetRecordDetail(params.id as string);

const recordId = params.id as string;
const { data } = useGetRecordDetail(recordId);

const { sinceDay, imageUrl, remark, duration, startedAt: missionDate } = data;

Expand All @@ -23,6 +31,12 @@ function MissionRecordDetail() {
</div>
<HistoryThumbnail imageUrl={imageUrl} missionDuration={duration} />
<span className={textSecondaryColorCss}>{remark}</span>

{props.isFollow ? (
<OtherReactionBar recordId={Number(recordId)} />
) : (
<MyReactionBar recordId={Number(recordId)} />
)}
</section>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/record/[id]/detail/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function MissionRecordDetailPage({ params }: { params: { id: string } }) {
<main className={mainWrapperCss}>
<MissionRecordHeader recordId={params.id} />
<Suspense>
<MissionRecordDetail />
<MissionRecordDetail isFollow={false} />
</Suspense>
</main>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/record/[id]/follow/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function FollowMissionRecordDetailPage() {
<main className={mainWrapperCss}>
<Header rightAction={'none'} title={'미션 내역'} />
<Suspense>
<MissionRecordDetail />
<MissionRecordDetail isFollow />
</Suspense>
</main>
);
Expand Down
2 changes: 2 additions & 0 deletions src/app/result/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import FinishedMissionList from '@/app/result/FinishedMissionList';
import OverallStatus from '@/app/result/OverallStatus';
import { ResultTabId } from '@/app/result/result.constants';
import AppBarBottom from '@/components/AppBarBottom/AppBarBottom';
import BottomDim from '@/components/BottomDim/BottomDim';
import Tab from '@/components/Tab/Tab';
import { useTab } from '@/components/Tab/Tab.hooks';
import { EVENT_LOG_CATEGORY, EVENT_LOG_NAME } from '@/constants/eventLog';
Expand Down Expand Up @@ -39,6 +40,7 @@ function ResultPage() {
{tabProps.activeTab === ResultTabId.FINISHED_MISSION && (
<FinishedMissionList queryData={finishedMissionQueryData} />
)}
<BottomDim />
<AppBarBottom />
</>
);
Expand Down
1 change: 1 addition & 0 deletions src/components/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const headerWrapperCss = css({
display: 'none',
},
});

// TODO : 토큰이 입력 안되는 이슈
const bottomSheetCss = {
'--rsbs-backdrop-bg': 'rgba(0, 0, 0, 0.60)',
Expand Down
102 changes: 102 additions & 0 deletions src/components/Icon/NavigationFeedIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,105 @@ function NavigationFeedIcon(props: IconComponentProps) {
}

export default NavigationFeedIcon;

export function FeedOutlineIcon(props: IconComponentProps) {
const { color, size = 24, ...restProps } = props;

const iconColor = color ? token.var(`colors.${color}`) : DEFAULT_ICON_COLOR;

return (
<svg
width={size}
height={size}
viewBox="0 0 20 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
color={iconColor}
{...restProps}
>
<rect x="2.75" y="3.25" width="14.5" height="14.5" rx="4.75" stroke="currentColor" strokeWidth="0.5" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.6263 12.4594C7.80902 12.2538 8.12326 12.2345 8.3297 12.4157L8.42225 12.4853C8.49372 12.535 8.60407 12.6045 8.74939 12.675C9.04 12.8159 9.4655 12.9582 10 12.9582C10.5345 12.9582 10.96 12.8159 11.2506 12.675C11.3959 12.6045 11.5063 12.535 11.5777 12.4853L11.6703 12.4157C11.8767 12.2345 12.191 12.2538 12.3737 12.4594C12.5572 12.6658 12.5386 12.9818 12.3322 13.1653L12.2763 13.2117C12.2457 13.2361 12.203 13.2685 12.1488 13.3062C12.0406 13.3815 11.8853 13.4786 11.6869 13.5748C11.29 13.7672 10.7155 13.9582 10 13.9582C9.28451 13.9582 8.71001 13.7672 8.31312 13.5748C8.11469 13.4786 7.95941 13.3815 7.85119 13.3062L7.66782 13.1653C7.46143 12.9818 7.44284 12.6658 7.6263 12.4594Z"
fill="currentColor"
/>
<circle cx="13" cy="9" r="1" fill="currentColor" />
<circle cx="7.04102" cy="9" r="1" fill="currentColor" />
</svg>
);
}

export function GradientFeedIcon() {
return (
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect
x="2.75"
y="3.25"
width="14.5"
height="14.5"
rx="4.75"
stroke="url(#paint0_linear_4980_14754)"
strokeWidth="0.5"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.6263 12.4594C7.80902 12.2538 8.12326 12.2346 8.3297 12.4157L8.42225 12.4853C8.49372 12.535 8.60407 12.6045 8.74939 12.675C9.04 12.8159 9.4655 12.9582 10 12.9582C10.5345 12.9582 10.96 12.8159 11.2506 12.675C11.3959 12.6045 11.5063 12.535 11.5777 12.4853L11.6703 12.4157C11.8767 12.2346 12.191 12.2538 12.3737 12.4594C12.5572 12.6658 12.5386 12.9818 12.3322 13.1653L12.2763 13.2117C12.2457 13.2361 12.203 13.2685 12.1488 13.3062C12.0406 13.3815 11.8853 13.4786 11.6869 13.5748C11.29 13.7672 10.7155 13.9582 10 13.9582C9.28451 13.9582 8.71001 13.7672 8.31312 13.5748C8.11469 13.4786 7.95941 13.3815 7.85119 13.3062L7.66782 13.1653C7.46143 12.9818 7.44284 12.6658 7.6263 12.4594Z"
fill="url(#paint1_linear_4980_14754)"
/>
<circle cx="13" cy="9" r="1" fill="url(#paint2_linear_4980_14754)" />
<circle cx="7.04102" cy="9" r="1" fill="url(#paint3_linear_4980_14754)" />
<defs>
<linearGradient
id="paint0_linear_4980_14754"
x1="2.5"
y1="3"
x2="17.7404"
y2="3.24837"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FAD0DE" />
<stop offset="0.46875" stopColor="#CCCBFF" />
<stop offset="1" stopColor="#ABD2FF" />
</linearGradient>
<linearGradient
id="paint1_linear_4980_14754"
x1="7.5"
y1="12.2916"
x2="12.5694"
y2="12.5394"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FAD0DE" />
<stop offset="0.46875" stopColor="#CCCBFF" />
<stop offset="1" stopColor="#ABD2FF" />
</linearGradient>
<linearGradient
id="paint2_linear_4980_14754"
x1="12"
y1="8"
x2="14.0321"
y2="8.03312"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FAD0DE" />
<stop offset="0.46875" stopColor="#CCCBFF" />
<stop offset="1" stopColor="#ABD2FF" />
</linearGradient>
<linearGradient
id="paint3_linear_4980_14754"
x1="6.04102"
y1="8"
x2="8.07307"
y2="8.03312"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FAD0DE" />
<stop offset="0.46875" stopColor="#CCCBFF" />
<stop offset="1" stopColor="#ABD2FF" />
</linearGradient>
</defs>
</svg>
);
}
3 changes: 2 additions & 1 deletion src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import CopyLinkIcon from '@/components/Icon/CopyLinkIcon';
import LoginInInformationIcon from '@/components/Icon/LoginInInformationIcon';
import LogOutIcon from '@/components/Icon/LogOutIcon';
import MenuIcon from '@/components/Icon/MenuIcon';
import NavigationFeedIcon from '@/components/Icon/NavigationFeedIcon';
import NavigationFeedIcon, { FeedOutlineIcon } from '@/components/Icon/NavigationFeedIcon';
import NavigationMissionIcon from '@/components/Icon/NavigationMissionIcon';
import NavigationMypageIcon from '@/components/Icon/NavigationMypageIcon';
import NavigationResultIcon from '@/components/Icon/NavigationResultIcon';
Expand Down Expand Up @@ -61,6 +61,7 @@ export const IconComponentMap = {
'navigation-search': NavigationSearchIcon,
'navigation-mission': NavigationMissionIcon,
'navigation-feed': NavigationFeedIcon,
'navigation-feed-outline': FeedOutlineIcon,
camera: CameraIcon,
'plus-circle': PlusCircleIcon,
close: CloseIcon,
Expand Down
Loading

0 comments on commit f73ef39

Please sign in to comment.