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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

/certificates/
10 changes: 5 additions & 5 deletions src/app/groups/[groupId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GroupDescription } from '@/components/atoms/group-description';
import { GroupActionButtons } from '@/components/molecules/gorup-action-buttons';
import { Empty } from '@/components/organisms/empty';
import { GroupDetailCard } from '@/components/organisms/group-detail-card';
import { ReplySection } from '@/components/organisms/reply/reply-section';
import { GroupActionButtons } from '@/features/group/components/group-action-buttons';
import { GroupDescription } from '@/features/group/components/group-description';
import { GroupDetailCard } from '@/features/group/components/group-detail-card';
import { ReplySection } from '@/features/reply/components/reply-section';
import { GroupDetail } from '@/types';
import { getAuthCookieHeader } from '@/utils/cookie';
import { isBeforeToday } from '@/utils/dateUtils';
Expand Down Expand Up @@ -154,7 +154,7 @@ export default async function GroupDetailPage({
<div className="bg-gray-50 items-center py-15 px-5 sm:px-10">
<GroupDetailCard info={data} isRecruiting={isRecruiting} />
</div>
<div className="mx-auto flex flex-col gap-10 w-full max-w-[900px] max-[900px]:px-10 px-6">
<div className="mx-auto flex flex-col gap-15 w-full max-w-[900px] max-[900px]:px-10 px-6">
<GroupDescription
description={group.description}
groupType={group.type}
Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default async function Home({
</div> */}
<QueryErrorBoundary
fallback={
<div>
<div className="text-center text-gray-500 mt-30">
⚠️ 그룹을 불러오는 중 문제가 발생했습니다. 다시 시도해주세요.
</div>
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/atoms/share-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export const ShareButton = () => {
const shareButtonClickHandler = async () => {
try {
await navigator.clipboard.writeText(currentUrl);
toast.success('클립보드에 복사되었습니다');
toast.success('링크가 복사되었습니다.');
} catch {
toast.error('클립보드 복사 실패!');
toast.error('링크 복사에 실패하였습니다.');
}
};

Expand Down
45 changes: 28 additions & 17 deletions src/components/error-fallback/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use client';

import { ReactNode } from "react";
import useAuthStore from '@/stores/useAuthStore';
import { useRouter } from 'next/navigation';
import useAuthStore from "@/stores/useAuthStore";
import { ReactNode } from 'react';
import { Button } from '../ui/button';

interface ErrorFallbackProps {
error: Error | null;
Expand All @@ -15,38 +16,48 @@ interface ErrorFallbackProps {
* @param error 에러 객체
* @param resetErrorBoundary 에러 재시도 함수
* @param children 표시할 메시지
* @returns
* @returns
*/
export const ErrorFallback = ({ error, resetErrorBoundary, children }: ErrorFallbackProps) => {
const {clearUser} = useAuthStore();

export const ErrorFallback = ({
error,
resetErrorBoundary,
children,
}: ErrorFallbackProps) => {
const { clearUser } = useAuthStore();

const router = useRouter();

const handleClick = () => {
if (error?.message.includes('401') || error?.message.toLowerCase().includes('unauthorized')) {
if (
error?.message.includes('401') ||
error?.message.toLowerCase().includes('unauthorized')
) {
clearUser();
console.log('401 에러 발생');
router.push('/login'); // 401 에러면 로그인 페이지로 이동
router.push('/login'); // 401 에러면 로그인 페이지로 이동
} else if (error?.message.includes('Network')) {
resetErrorBoundary(); // 네트워크 에러면 재시도
resetErrorBoundary(); // 네트워크 에러면 재시도
} else {
resetErrorBoundary(); // 기타 에러는 기본적으로 재시도
resetErrorBoundary(); // 기타 에러는 기본적으로 재시도
}
};

return (
<div role="alert" className="p-4">
<div role="alert" className="p-4 flex flex-col items-center mx-auto my-15">
{/* 에러 메시지 */}
<p className="text-sm text-red-500">{children}</p>
<p className="text-center text-gray-500">{children}</p>
{/* 에러 객체가 존재하는 경우 에러 상세 메시지 */}
{/* {error && <pre className="text-sm">{error.message}</pre>} */}
{/* resetErrorBoundary이 존재하는 경우 재시도 버튼 */}
<button
<Button
onClick={handleClick}
className="mt-2 px-2 py-1 bg-blue-500 text-white text-sm cursor-pointer rounded hover:bg-blue-600"
className="mt-2 text-white text-sm cursor-pointer bg-green-600"
>
{error?.message.includes('401') || error?.message.toLowerCase().includes('unauthorized') ? '로그인하기' : '다시 시도'}
</button>
{error?.message.includes('401') ||
error?.message.toLowerCase().includes('unauthorized')
? '로그인하기'
: '다시 시도'}
</Button>
</div>
);
};
};
20 changes: 0 additions & 20 deletions src/components/organisms/reply/reply-section.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { request } from '@/api/request';
import { BookmarkButton } from '@/components/atoms/bookmark-button';
import { BookmarkButton } from '@/features/bookmark/components/bookmark-button';
import useAuthStore from '@/stores/useAuthStore';
import { useMutation } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client';

import { request } from '@/api/request';
import { LoginRequireButton } from '@/components/atoms/login-require-button';
import { useMutation } from '@tanstack/react-query';
import { useParams } from 'next/navigation';
import { toast } from 'sonner';
import { LoginRequireButton } from '../login-require-button';

export const ApplyJoinButton = ({ onSuccess }: { onSuccess: () => void }) => {
const { groupId } = useParams<{ groupId: string }>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client';

import { invalidateTag } from '@/actions/invalidate';
import { ApplyJoinButton } from '@/components/atoms/apply-join-button.tsx';
import { CancelGroupButton } from '@/components/atoms/cancel-group-button';
import { CancelJoinButton } from '@/components/atoms/cancel-join-button';
import { ShareButton } from '@/components/atoms/share-button';
import { ApplyJoinButton } from '@/features/group/components/apply-join-button';
import { CancelGroupButton } from '@/features/group/components/cancel-group-button';
import { CancelJoinButton } from '@/features/group/components/cancel-join-button';
import useAuthStore from '@/stores/useAuthStore';
import { useState } from 'react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Badge } from '@/components/atoms/badge';
import { GroupProgress } from '@/components/atoms/group/particiapant-progress';
import { PositionBadge } from '@/components/molecules/position-badge';
import { SkillBadge } from '@/components/molecules/skill-badge';
import { ParticipantListModal } from '@/components/organisms/participant-list-modal';
import { BookmarkButtonContainer } from '@/features/bookmark/components/bookmark-button-container';
import { ParticipantListModal } from '@/features/group/components/participant-list-modal';
import { GroupDetail, GroupTypeName } from '@/types';
import { Position, Skill } from '@/types/enums';
import { formatYearMonthDayWithDot } from '@/utils/dateUtils';
Expand Down Expand Up @@ -102,7 +102,7 @@ export const GroupDetailCard = ({
<div className="flex justify-between">
<div className="flex">
{info.group.participants
.slice(0, 3)
.slice(0, 5)
.map(({ userId, profileImage, email, nickname }, index) => (
<Avatar
key={userId}
Expand All @@ -115,7 +115,7 @@ export const GroupDetailCard = ({
))}
<ParticipantListModal
participants={info.group.participants}
className={`z-50 ${
className={`z-100 ${
info.group.participants.length > 0 ? '-ml-3' : ''
}`}
/>
Expand All @@ -128,7 +128,7 @@ export const GroupDetailCard = ({
};

const getZIndexClass = (index: number) => {
const zIndexMap = ['z-10', 'z-20', 'z-30'];
const zIndexMap = ['z-10', 'z-20', 'z-30', 'z-40', 'z-50'];
return zIndexMap[index] ?? 'z-0';
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client';

import { request } from '@/api/request';
import { ReplyMeta } from '@/components/molecules/reply/reply-meta';
import { Button } from '@/components/ui/button';
import useAuthStore from '@/stores/useAuthStore';
import { Reply } from '@/types';
import { useMutation } from '@tanstack/react-query';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { toast } from 'sonner';
import { ReplyMeta } from './reply-meta';

type ReplyContentProps = Reply & { parentId?: number; onDelete?: () => void };

Expand Down Expand Up @@ -110,13 +110,13 @@ export const ReplyContent = ({
className="max-h-20 w-full border-2 border-slate-800 rounded-sm p-3 resize-none"
/>
) : (
<p
<h3
className={`${
isLocallyDeleted ? 'text-gray-500' : ''
} break-words whitespace-pre-wrap`}
>
{isLocallyDeleted ? '삭제된 댓글입니다.' : content}
</p>
</h3>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReplyContent } from '@/components/molecules/reply/reply-content';
import { Reply } from '@/types';
import { ReplyThread } from './reply-thread';
import { ReplyContent } from './reply-content';
import { RereplySection } from './rereply-section';

export const ReplyItem = ({
content,
Expand All @@ -10,15 +10,15 @@ export const ReplyItem = ({
deleted,
}: Reply) => {
return (
<div className="border-2 rounded-lg">
<section className="border-2 rounded-lg">
<ReplyContent
content={content}
writer={writer}
createdAt={createdAt}
replyId={replyId}
deleted={deleted}
/>
<ReplyThread parentReplyId={replyId} />
</div>
<RereplySection parentReplyId={replyId} />
</section>
);
};
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
'use client';

import { ReplyItem } from '@/components/organisms/reply/reply-item';
import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView';
import { useTargetReplyParams } from '@/features/reply/hooks/useTargetReplyParams ';
import { useFetchInView } from '@/hooks/useFetchInView';
import { useFetchItems } from '@/hooks/useFetchItems';
import { useReplyScrollIntoView } from '@/hooks/useReplyScrollIntoView';
import { useTargetReplyParams } from '@/hooks/useTargetReplyParams ';
import { ReplyItem } from './reply-item';

import { Loading } from '@/components/organisms/loading';
import { useTargetReplyStore } from '@/stores/useTargetReply';
import { Reply } from '@/types';
import flattenPages from '@/utils/flattenPages';
Expand Down Expand Up @@ -53,8 +54,24 @@ export const ReplyList = () => {

const replies = flattenPages(data.pages);

if (isLoading) {
return (
<div className="my-15">
<Loading />
</div>
);
}

if (replies.length === 0) {
return (
<div className="my-15 text-center text-gray-500">
아직 댓글이 없습니다.
</div>
);
}

return (
<section className="my-15">
<div className="my-15">
<ul className="flex flex-col gap-10">
{replies.map((reply) => (
<li
Expand All @@ -72,6 +89,6 @@ export const ReplyList = () => {
{hasNextPage && !isFetchingNextPage && (
<div ref={ref} className="h-2 -translate-y-100 bg-transparent" />
)}
</section>
</div>
);
};
28 changes: 28 additions & 0 deletions src/features/reply/components/reply-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { ErrorBoundary } from '@/components/error-boundary';
import { handleError } from '@/components/error-boundary/error-handler';
import { ReplyForm } from './reply-form';
import { ReplyList } from './reply-list';

export const ReplySection = () => {
return (
<div className="flex flex-col gap-10">
<h2 className="text-2xl font-bold pb-6 border-b-2 border-gray-100">
댓글
</h2>
<ErrorBoundary
fallback={({ error, resetErrorBoundary }) =>
handleError({
error,
resetErrorBoundary,
defaultMessage: '댓글을 불러오는 중 문제가 발생했습니다',
})
}
>
<ReplyForm />
<ReplyList />
</ErrorBoundary>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { AddRereplyButton } from '@/components/atoms/reply/add-rereply-button';
import { useState } from 'react';
import { AddRereplyButton } from './add-rereply-button';
import { ReplyForm } from './reply-form';

type RereplyFormToggleProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReplyContent } from '@/components/molecules/reply/reply-content';
import { Reply } from '@/types';
import { forwardRef, useState } from 'react';
import { ReplyContent } from './reply-content';

/** 대댓글 삭제를 위해 ReplyContent를 둘러싸기 위해 만든 컴포넌트 */
export const RereplyItem = forwardRef<HTMLLIElement, Reply>((props, ref) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import { useReplyScrollIntoView } from '@/features/reply/hooks/useReplyScrollIntoView';
import { useFetchInView } from '@/hooks/useFetchInView';
import { useFetchItems } from '@/hooks/useFetchItems';
import { useReplyScrollIntoView } from '@/hooks/useReplyScrollIntoView';
import { Reply } from '@/types';
import flattenPages from '@/utils/flattenPages';
import { useParams } from 'next/navigation';
Expand Down
Loading