Skip to content

Commit 6585876

Browse files
authored
Merge pull request #50 from Nexters/feat/gathering-page-api
feat: 모임 참여 페이지 api 연결
2 parents 22bfb0a + e5238a1 commit 6585876

File tree

14 files changed

+215
-64
lines changed

14 files changed

+215
-64
lines changed

apps/tuk-web/src/app/gathering/[gatheringId]/invites/[proposalId]/src/components/GatheringProposal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useInfiniteQuery } from '@tanstack/react-query';
44
import Link from 'next/link';
55
import { useEffect, useMemo, useState } from 'react';
66

7-
import { gatheringAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
7+
import { gatheringProposalAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
88
import { InviteCard } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
99
import { CloseIcon32, Header } from '@/shared/components';
1010
import InviteCardFrame from '@/shared/components/InviteCardFrame';
@@ -19,7 +19,7 @@ const GatheringProposal = () => {
1919
queryKey: ['getGatheringProposals', gatheringId, 'RECEIVED'],
2020
initialPageParam: 0,
2121
queryFn: ({ pageParam = 0 }) =>
22-
gatheringAPIService.getGatheringProposals(gatheringId, 'RECEIVED', {
22+
gatheringProposalAPIService.getGatheringProposals(gatheringId, 'RECEIVED', {
2323
pageNumber: pageParam,
2424
pageSize: 10,
2525
}),

apps/tuk-web/src/app/gathering/[gatheringId]/invites/src/components/ReceiveInviteListContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
22
import Link from 'next/link';
33
import { useMemo } from 'react';
44

5-
import { gatheringAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
5+
import { gatheringProposalAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
66
import { InviteCard } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
77
import InviteCardFrame from '@/shared/components/InviteCardFrame';
88
import { useIntersectionObserver } from '@/shared/hooks/useIntersectionObserver';
@@ -15,7 +15,7 @@ const ReceiveInviteListContent = () => {
1515
queryKey: ['getGatheringProposals', gatheringId, 'RECEIVED'],
1616
initialPageParam: 1,
1717
queryFn: ({ pageParam = 1 }) =>
18-
gatheringAPIService.getGatheringProposals(gatheringId, 'RECEIVED', {
18+
gatheringProposalAPIService.getGatheringProposals(gatheringId, 'RECEIVED', {
1919
pageNumber: pageParam,
2020
pageSize: 10,
2121
}),

apps/tuk-web/src/app/gathering/[gatheringId]/invites/src/components/SendInviteListContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
22
import { useMemo } from 'react';
33

4-
import { gatheringAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
4+
import { gatheringProposalAPIService } from '@/app/gathering/[gatheringId]/invites/src/service';
55
import InviteCardFrame from '@/shared/components/InviteCardFrame';
66
import { useIntersectionObserver } from '@/shared/hooks/useIntersectionObserver';
77
import { useParam } from '@/shared/hooks/useParam';
@@ -12,7 +12,7 @@ const SendInviteListContent = () => {
1212
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = useSuspenseInfiniteQuery({
1313
queryKey: ['getGatheringProposals', gatheringId, 'SENT'],
1414
queryFn: ({ pageParam = 1 }) =>
15-
gatheringAPIService.getGatheringProposals(gatheringId, 'SENT', {
15+
gatheringProposalAPIService.getGatheringProposals(gatheringId, 'SENT', {
1616
pageNumber: pageParam,
1717
pageSize: 10,
1818
}),

apps/tuk-web/src/app/gathering/[gatheringId]/invites/src/service/gathering-api.service.ts renamed to apps/tuk-web/src/app/gathering/[gatheringId]/invites/src/service/gathering-proposal-api.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ProposalPageResponse } from '@/app/gathering/[gatheringId]/invites/src/
22
import { RestAPIProtocol } from '@/shared/lib/api/rest/types';
33
import { PaginationQuery } from '@/shared/lib/api/types';
44

5-
export class GatheringAPIService {
5+
export class GatheringProposalAPIService {
66
constructor(private fetch: RestAPIProtocol) {}
77

88
getGatheringProposals(gatheringId: number, type: 'SENT' | 'RECEIVED', page: PaginationQuery) {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { GatheringAPIService } from '@/app/gathering/[gatheringId]/invites/src/service/gathering-api.service';
1+
import { GatheringProposalAPIService } from '@/app/gathering/[gatheringId]/invites/src/service/gathering-proposal-api.service';
22
import CONFIG from '@/shared/config';
33
import { generateRestAPI } from '@/shared/lib/api/rest';
44

5-
export const gatheringAPIService = new GatheringAPIService(
5+
export const gatheringProposalAPIService = new GatheringProposalAPIService(
66
generateRestAPI({ baseURL: 'api/v1/gatherings' }, false, CONFIG.BASE_URL)
77
);

apps/tuk-web/src/app/invite/gathering/[gatheringId]/src/components/InviteGathering.tsx

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,35 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
4-
5-
import { InviteCard, TukLogo } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
3+
import { QueryErrorResetBoundary } from '@tanstack/react-query';
4+
import { Suspense } from 'react';
5+
import { ErrorBoundary } from 'react-error-boundary';
6+
7+
import InviteGatheringContent from '@/app/invite/gathering/[gatheringId]/src/components/InviteGatheringContent';
8+
import InviteGatheringErrorFallback from '@/app/invite/gathering/[gatheringId]/src/components/InviteGatheringErrorFallback';
9+
import InviteGatheringSkeleton from '@/app/invite/gathering/[gatheringId]/src/components/InviteGatheringSkeleton';
10+
import { TukLogo } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
11+
import SkeletonGuard from '@/app/invite/meet/[meetId]/src/components/SkeletonGuard';
612
import { Button } from '@/shared/components';
7-
import AppInstallBanner from '@/shared/components/AppInstallBanner';
8-
import InviteCardFrame from '@/shared/components/InviteCardFrame';
913
import { useParam } from '@/shared/hooks/useParam';
10-
import { cn } from '@/shared/lib';
11-
12-
const BANNER_KEY = 'gathering-banner-dismissed-at';
13-
const BANNER_RESHOW_MINUTES = 30;
1414

1515
const InviteGathering = () => {
1616
const gatheringId = Number(useParam('gatheringId'));
1717

18-
const [showBanner, setShowBanner] = useState(false);
19-
const [animateCardIn, setAnimateCardIn] = useState(false);
20-
21-
const handleCloseBanner = () => {
22-
localStorage.setItem(BANNER_KEY, Date.now().toString());
23-
setShowBanner(false);
24-
};
25-
26-
useEffect(() => {
27-
const dismissedAt = localStorage.getItem(BANNER_KEY);
28-
const now = Date.now();
29-
30-
if (!dismissedAt) {
31-
setShowBanner(true);
32-
} else {
33-
const dismissedTime = parseInt(dismissedAt, 10);
34-
const thirtyMinutes = BANNER_RESHOW_MINUTES * 60 * 1000;
35-
if (now - dismissedTime > thirtyMinutes) {
36-
setShowBanner(true);
37-
}
38-
}
39-
40-
const timeout = setTimeout(() => setAnimateCardIn(true), 100);
41-
42-
return () => clearTimeout(timeout);
43-
}, []);
4418
return (
4519
<div className="relative w-full overflow-y-auto overflow-x-hidden bg-gray-50 px-5">
4620
<div className="pointer-events-none absolute left-1/2 top-1/2 h-[340px] w-[706px] -translate-x-1/2 -translate-y-1/2 rotate-[-21deg] bg-gradient-to-b from-[#FFA098] to-[#FDD27C] opacity-60 blur-[100px]" />
4721

48-
{showBanner && <AppInstallBanner onClose={handleCloseBanner} />}
49-
50-
<h2
51-
className={cn(
52-
'serif-title-22-M font-bold text-gray-900',
53-
showBanner ? 'mt-[6.875rem]' : 'mt-[1.875rem]'
22+
<QueryErrorResetBoundary>
23+
{({ reset }) => (
24+
<ErrorBoundary onReset={reset} FallbackComponent={InviteGatheringErrorFallback}>
25+
<SkeletonGuard minMs={250} skeleton={<InviteGatheringSkeleton />}>
26+
<Suspense fallback={<InviteGatheringSkeleton />}>
27+
<InviteGatheringContent />
28+
</Suspense>
29+
</SkeletonGuard>
30+
</ErrorBoundary>
5431
)}
55-
>
56-
모임에
57-
<br />
58-
참여하시겠어요?
59-
</h2>
60-
61-
<div className="relative mt-12 flex justify-center">
62-
<InviteCardFrame animateCardIn={animateCardIn} />
63-
64-
<div className="absolute left-1/2 top-12 -translate-x-1/2">
65-
<InviteCard />
66-
</div>
67-
</div>
32+
</QueryErrorResetBoundary>
6833

6934
<div className="mt-[4.125rem] flex justify-center pb-[9.0625rem]">
7035
<TukLogo />
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useSuspenseQuery } from '@tanstack/react-query';
2+
import { useEffect, useState } from 'react';
3+
4+
import { gatheringAPIService } from '@/app/invite/gathering/[gatheringId]/src/service';
5+
import { InviteCard } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
6+
import AppInstallBanner from '@/shared/components/AppInstallBanner';
7+
import { useParam } from '@/shared/hooks/useParam';
8+
import { cn } from '@/shared/lib';
9+
10+
const BANNER_KEY = 'gathering-banner-dismissed-at';
11+
const BANNER_RESHOW_MINUTES = 30;
12+
13+
const InviteGatheringContent = () => {
14+
const gatheringId = Number(useParam('gatheringId'));
15+
16+
const { data } = useSuspenseQuery({
17+
queryKey: ['getGatheringName', gatheringId],
18+
queryFn: () => gatheringAPIService.getGatheringName(gatheringId),
19+
});
20+
21+
const [showBanner, setShowBanner] = useState(false);
22+
23+
const handleCloseBanner = () => {
24+
localStorage.setItem(BANNER_KEY, Date.now().toString());
25+
setShowBanner(false);
26+
};
27+
28+
useEffect(() => {
29+
const dismissedAt = localStorage.getItem(BANNER_KEY);
30+
const now = Date.now();
31+
32+
if (!dismissedAt) {
33+
setShowBanner(true);
34+
} else {
35+
const dismissedTime = parseInt(dismissedAt, 10);
36+
const thirtyMinutes = BANNER_RESHOW_MINUTES * 60 * 1000;
37+
if (now - dismissedTime > thirtyMinutes) {
38+
setShowBanner(true);
39+
}
40+
}
41+
}, []);
42+
43+
return (
44+
<>
45+
{showBanner && <AppInstallBanner onClose={handleCloseBanner} />}
46+
47+
<h2
48+
className={cn(
49+
'serif-title-22-M font-bold text-gray-900',
50+
showBanner ? 'mt-[6.875rem]' : 'mt-[1.875rem]'
51+
)}
52+
>
53+
모임에
54+
<br />
55+
참여하시겠어요?
56+
</h2>
57+
58+
<div className="relative mt-12 flex justify-center">
59+
<div className="relative mt-6 h-[300px] w-[260px] rounded-[10px] border border-black-default/5 bg-[#f0f1f3]">
60+
<h3 className="serif-body-20-R mt-[50px] text-center">{data?.data.gatheringName}</h3>
61+
62+
<div className="absolute bottom-0 left-0 flex w-full justify-between px-4 pb-4">
63+
<p className="serif-body-12-R text-gray-500">연락이</p>
64+
<p className="serif-body-12-R text-gray-500">뜸해진 우리</p>
65+
</div>
66+
</div>
67+
68+
<div className="absolute left-1/2 top-0 -translate-x-1/2">
69+
<InviteCard />
70+
</div>
71+
</div>
72+
</>
73+
);
74+
};
75+
76+
export default InviteGatheringContent;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const InviteGatheringErrorFallback = ({
2+
resetErrorBoundary,
3+
}: {
4+
error: unknown;
5+
resetErrorBoundary: () => void;
6+
}) => {
7+
return (
8+
<div className="relative mt-12 flex justify-center">
9+
<div className="flex h-[23.125rem] w-[16.25rem] flex-col items-center justify-center gap-6 rounded-[0.625rem] bg-[#f0f1f3] px-4 py-3">
10+
<span className="pretendard-body-14-R text-gray-800">
11+
초대장을 불러오는 중 오류가 발생했어요
12+
</span>
13+
<button
14+
type="button"
15+
onClick={resetErrorBoundary}
16+
className="rounded-md border border-gray-300 px-3 py-2 text-sm text-gray-700"
17+
aria-label="초대장 다시 불러오기"
18+
>
19+
재시도
20+
</button>
21+
</div>
22+
</div>
23+
);
24+
};
25+
26+
export default InviteGatheringErrorFallback;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const InviteGatheringSkeleton = () => {
2+
return (
3+
<div className="mt-12 flex flex-col items-center justify-center">
4+
<div
5+
className="relative h-[321px] w-[288px] animate-pulse overflow-hidden rounded-[0.625rem] bg-[#f0f1f3] px-4 py-3"
6+
aria-busy="true"
7+
aria-label="Loading invitation"
8+
>
9+
<div className="flex justify-end">
10+
<div className="flex flex-col items-end gap-2">
11+
<div className="h-3 w-32 rounded bg-gray-200" />
12+
<div className="h-3 w-40 rounded bg-gray-200" />
13+
</div>
14+
</div>
15+
16+
<div className="mt-[4.375rem]">
17+
<div className="size-5 rounded bg-gray-200" />
18+
<div className="mt-[0.8125rem] flex flex-col gap-[0.3125rem]">
19+
<div className="h-4 w-40 rounded bg-gray-200" />
20+
<div className="h-4 w-36 rounded bg-gray-200" />
21+
<div className="h-4 w-44 rounded bg-gray-200" />
22+
</div>
23+
<div className="serif-body-16-M mt-5 h-4 w-16 rounded bg-gray-200" />
24+
</div>
25+
26+
<div className="absolute bottom-0 left-0 flex w-full justify-between px-4 pb-4">
27+
<div className="h-3 w-10 rounded bg-gray-200" />
28+
<div className="h-3 w-16 rounded bg-gray-200" />
29+
</div>
30+
31+
<div className="pointer-events-none absolute inset-0 -skew-x-12 bg-gradient-to-r from-transparent via-[rgba(255,255,255,0.3)] to-transparent" />
32+
</div>
33+
</div>
34+
);
35+
};
36+
37+
export default InviteGatheringSkeleton;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { GatheringNameResponse } from '@/app/invite/gathering/[gatheringId]/src/service/schema/get-gathering-name.schema';
2+
import { RestAPIProtocol } from '@/shared/lib/api/rest';
3+
4+
export class GatheringAPIService {
5+
constructor(private fetch: RestAPIProtocol) {}
6+
7+
getGatheringName(gatheringId: number) {
8+
// throw new Error('강제 에러 발생: GatheringAPIService.getGatheringName');
9+
10+
return this.fetch.get({
11+
url: ':gatheringId/name',
12+
param: {
13+
gatheringId,
14+
},
15+
validate: GatheringNameResponse.parse,
16+
});
17+
}
18+
}

0 commit comments

Comments
 (0)