diff --git a/packages/web/next.config.mjs b/packages/web/next.config.mjs
index 308362ab5..5963aab24 100644
--- a/packages/web/next.config.mjs
+++ b/packages/web/next.config.mjs
@@ -20,6 +20,10 @@ const nextConfig = {
hostname: 'opendata.mofa.go.kr',
},
],
+ deviceSizes: [450],
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384, 450],
+ minimumCacheTTL: 31536000,
+ formats: ['image/webp'],
},
};
export default withBundleAnalyzer(withPlaiceholder(nextConfig));
diff --git a/packages/web/public/sprite/sprite.svg b/packages/web/public/sprite/sprite.svg
index 36cf74875..57a9a808c 100644
--- a/packages/web/public/sprite/sprite.svg
+++ b/packages/web/public/sprite/sprite.svg
@@ -1,603 +1,673 @@
-
+
+
diff --git a/packages/web/src/app/[lng]/(main)/community/components/ArticleItem.tsx b/packages/web/src/app/[lng]/(main)/community/components/ArticleItem.tsx
index e8eaae9d0..1b0ab9c47 100644
--- a/packages/web/src/app/[lng]/(main)/community/components/ArticleItem.tsx
+++ b/packages/web/src/app/[lng]/(main)/community/components/ArticleItem.tsx
@@ -1,7 +1,6 @@
'use client';
-import { format, formatDistanceToNow } from 'date-fns';
-import { enUS, ko } from 'date-fns/locale';
+import { ko } from 'date-fns/locale';
import Image from 'next/image';
import { CommunityArticle } from '@/apis/community';
@@ -14,19 +13,7 @@ import { Flex } from '@/components/Layout';
import { Spacing } from '@/components/Spacing';
import useAppRouter from '@/hooks/useAppRouter';
import cn from '@/utils/cn';
-
-const formatDate = (date: string, locale: Locale) => {
- const d = new Date(date);
- const now = Date.now();
- const diff = (now - d.getTime()) / 1000;
- if (diff < 60 * 1) {
- return '방금 전';
- }
- if (diff < 60 * 60 * 24 * 3) {
- return formatDistanceToNow(d, { addSuffix: true, locale });
- }
- return format(d, 'MM/dd', { locale });
-};
+import { formatDate } from '@/utils/formatDate';
interface ArticleItemProps {
articleData: CommunityArticle;
@@ -34,7 +21,7 @@ interface ArticleItemProps {
}
export default function ArticleItem({ articleData, onClick }: ArticleItemProps) {
- const { t, i18n } = useTranslation('community');
+ const { t } = useTranslation('community');
const { push } = useAppRouter();
const { article, writer } = articleData;
@@ -52,8 +39,6 @@ export default function ArticleItem({ articleData, onClick }: ArticleItemProps)
const { isCertifiedStudent, reliabilityLevel, nickName, countryImage, profileImage } = writer;
- const locale = i18n.language === 'ko' ? ko : enUS;
-
return (
{t(`category.${category.name}`)}
- {formatDate(createdAt, locale)}
+ {formatDate(createdAt, ko)}
@@ -72,7 +57,7 @@ export default function ArticleItem({ articleData, onClick }: ArticleItemProps)
{!!images?.length && (
-
+
)}
diff --git a/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleItem.tsx b/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleItem.tsx
index 903103d84..39b5ef194 100644
--- a/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleItem.tsx
+++ b/packages/web/src/app/[lng]/(main)/community/detail/[articleId]/components/ArticleItem.tsx
@@ -114,7 +114,13 @@ export default function ArticleItem({ article }: ArticleItemProps) {
open(() => )
}
>
-
+
))}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatCardList.tsx b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatCardList.tsx
new file mode 100644
index 000000000..809b31461
--- /dev/null
+++ b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatCardList.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import ChatCard from './ChatItem';
+import { chatList } from './dummy';
+
+import { useTranslation } from '@/app/i18n/client';
+import { Empty } from '@/components/Empty';
+import { ItemList } from '@/components/List';
+
+export default function ChatCardList() {
+ const { t } = useTranslation('grouping');
+
+ return (
+ <>
+ }
+ renderEmpty={() => }
+ />
+
+ >
+ );
+}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatItem.tsx b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatItem.tsx
new file mode 100644
index 000000000..0ca7c0eb7
--- /dev/null
+++ b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatItem.tsx
@@ -0,0 +1,64 @@
+import { ko } from 'date-fns/locale';
+import Image from 'next/image';
+
+import { Chat } from './dummy';
+
+import type { HTMLAttributes, PropsWithChildren } from 'react';
+
+import ImageSample from '@/components/Image/ImageSample';
+import { Flex } from '@/components/Layout';
+import { NavLink } from '@/components/NavLink';
+import { Spacing } from '@/components/Spacing';
+import cn from '@/utils/cn';
+import { formatDate } from '@/utils/formatDate';
+
+interface ChatCardProps extends HTMLAttributes {
+ chatData: Chat;
+}
+
+export default function ChatCard({ chatData, children }: PropsWithChildren) {
+ const { title, content, imageUrl, newMessag, groupId, latestMessageTiem } = chatData;
+
+ return (
+
+
+
+ {imageUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ {title}
+ {content}
+
+
+
+ {formatDate(latestMessageTiem, ko)}
+
+ {newMessag && (
+
+ {newMessag}
+
+ )}
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatListHeader.tsx b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatListHeader.tsx
index 31bfb05c8..42ff54c42 100644
--- a/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatListHeader.tsx
+++ b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/ChatListHeader.tsx
@@ -1,15 +1,16 @@
'use client';
-import { useTranslation } from 'react-i18next';
-
import { IconButton } from '@/components/Button';
import { Header } from '@/components/Header';
import { Icon } from '@/components/Icon';
import useAppRouter from '@/hooks/useAppRouter';
-export default function ChatListHeader() {
+interface ChatListHeaderProps {
+ title: string;
+}
+
+export default function ChatListHeader({ title }: ChatListHeaderProps) {
const { back } = useAppRouter();
- const { t } = useTranslation('grouping');
return (
@@ -17,7 +18,7 @@ export default function ChatListHeader() {
back()}>
- {t('chat.listHeader')}
+ {title}
);
diff --git a/packages/web/src/app/[lng]/(main)/grouping/chatList/components/dummy.ts b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/dummy.ts
new file mode 100644
index 000000000..47b0fe742
--- /dev/null
+++ b/packages/web/src/app/[lng]/(main)/grouping/chatList/components/dummy.ts
@@ -0,0 +1,44 @@
+export interface Chat {
+ title: string;
+ content: string;
+ imageUrl: string;
+ newMessag?: number;
+ groupId: number;
+ latestMessageTiem: string;
+}
+
+export const chatList: Chat[] = [
+ {
+ title: '응가 뿌직',
+ content: '응가좀 치는사람 모이셈',
+ newMessag: 2,
+ imageUrl: '/images/approve_character.png',
+ groupId: 10,
+ latestMessageTiem: '2024-04-06T20:32',
+ },
+ {
+ title: '응가 뿌직',
+ content: '응가좀 치는사람 모이셈',
+ newMessag: 1,
+ imageUrl: '/images/approve_character.png',
+ groupId: 10,
+ latestMessageTiem: '2024-04-10T18:44',
+ },
+ {
+ title: '응가 뿌직',
+ content: '응가좀 치는사람 모이셈',
+
+ imageUrl: '/images/approve_character.png',
+ groupId: 10,
+ latestMessageTiem: '2024-04-06T20:32',
+ },
+ {
+ title: '응가 뿌직',
+ content: '응가좀 치는사람 모이셈',
+ newMessag: 3,
+
+ imageUrl: '/images/approve_character.png',
+ groupId: 10,
+ latestMessageTiem: '2024-04-06T20:32',
+ },
+];
diff --git a/packages/web/src/app/[lng]/(main)/grouping/chatList/page.tsx b/packages/web/src/app/[lng]/(main)/grouping/chatList/page.tsx
index c459cccdf..97bbee0f2 100644
--- a/packages/web/src/app/[lng]/(main)/grouping/chatList/page.tsx
+++ b/packages/web/src/app/[lng]/(main)/grouping/chatList/page.tsx
@@ -1,10 +1,21 @@
+import ChatCardList from './components/ChatCardList';
import ChatListHeader from './components/ChatListHeader';
-export default function ChatListPage() {
+import { serverTranslation } from '@/app/i18n';
+
+interface ChatListPageProps {
+ params: {
+ lng: string;
+ };
+}
+
+export default async function ChatListPage({ params: { lng } }: ChatListPageProps) {
+ const { t } = await serverTranslation(lng, 'grouping');
+
return (
<>
-
-
+
+
>
);
}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/chat/page.tsx b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/chat/page.tsx
deleted file mode 100644
index 1137c5471..000000000
--- a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/chat/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function ChatPage() {
- return ;
-}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetail.tsx b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetail.tsx
index e4a42bdf8..aa9d501af 100644
--- a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetail.tsx
+++ b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetail.tsx
@@ -1,8 +1,10 @@
'use client';
-import dynamic from 'next/dynamic';
-import { Suspense } from 'react';
+import { useSearchParams } from 'next/navigation';
+import ArticlesContent from './articles/ArticlesContent';
+import ChatContent from './chat/ChatContent';
+import DetailContent from './detail/DetailContent';
import TopSection from './TopSection';
import { useGetGroupDetail } from '@/apis/groups';
@@ -12,34 +14,34 @@ import { Spacing } from '@/components/Spacing';
import { Tabs } from '@/components/Tabs';
import { useNumberParams } from '@/hooks/useNumberParams';
-const DetailContent = dynamic(() => import('./detail/DetailContent'));
-const ArticlesContent = dynamic(() => import('./articles/ArticlesContent'));
-
-export default function GroupDetailPage() {
+export default function GroupDetail() {
const { t } = useTranslation('groupDetail');
const { groupId } = useNumberParams<['groupId']>();
+
+ const searchParams = useSearchParams().get('tab');
+
const { data: groupDetailData } = useGetGroupDetail(groupId);
const { myGroup } = groupDetailData;
return (
<>
-
+ {searchParams !== 'chat' && }
+ {/* */}
-
-
-
+
-
-
-
+
+ {/*
+
+ */}
>
diff --git a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetailHeader.tsx b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetailHeader.tsx
index 2786ae6bc..068dc0861 100644
--- a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetailHeader.tsx
+++ b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/GroupDetailHeader.tsx
@@ -1,5 +1,6 @@
'use client';
+import { useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
import BlockDoneModal from '../../../components/BlockDoneModal';
@@ -20,9 +21,10 @@ import { useBlockStore } from '@/store/useBlockStore';
export default function GroupDetailHeader() {
const { back } = useAppRouter();
const { groupId } = useNumberParams<['groupId']>();
+ const searchParams = useSearchParams().get('tab');
return (
-
+
back()}>
diff --git a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/chat/ChatContent.tsx b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/chat/ChatContent.tsx
new file mode 100644
index 000000000..d5d2c389f
--- /dev/null
+++ b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/components/chat/ChatContent.tsx
@@ -0,0 +1,29 @@
+import { usePathname } from 'next/navigation';
+
+import MessageForm from '@/components/Form/MessageForm';
+import { Spacing } from '@/components/Spacing';
+import { useNumberParams } from '@/hooks/useNumberParams';
+
+export default function ChatContent() {
+ const pathname = usePathname();
+ const { groupId } = useNumberParams<['groupId']>();
+
+ return (
+
+ );
+}
diff --git a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/page.tsx b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/page.tsx
index 819ebbb12..c130d07c9 100644
--- a/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/page.tsx
+++ b/packages/web/src/app/[lng]/(main)/grouping/detail/[groupId]/page.tsx
@@ -1,6 +1,6 @@
import { ErrorBoundary } from 'react-error-boundary';
-import GroupDetailPage from './components/GroupDetail';
+import GroupDetail from './components/GroupDetail';
import GroupDetailHeader from './components/GroupDetailHeader';
import { Keys, getGroupDetail, getGroupMembers, getNotices } from '@/apis/groups';
@@ -32,7 +32,7 @@ export default async function GroupingDetailPage({ params }: GroupingDetailPageP
Keys.getNotices(groupId),
]}
>
-
+
>
diff --git a/packages/web/src/app/[lng]/(main)/profile/components/ProfileDetailSection.tsx b/packages/web/src/app/[lng]/(main)/profile/components/ProfileDetailSection.tsx
index 1cda6e9bc..0c35fb7b5 100644
--- a/packages/web/src/app/[lng]/(main)/profile/components/ProfileDetailSection.tsx
+++ b/packages/web/src/app/[lng]/(main)/profile/components/ProfileDetailSection.tsx
@@ -88,7 +88,7 @@ export default function ProfileDetailSection({ profileData }: ProfileDetailProps
{countryImage && (
-
+
)}
{nickname}
diff --git a/packages/web/src/app/i18n/locales/ko/groupDetail.json b/packages/web/src/app/i18n/locales/ko/groupDetail.json
index 2b84abf08..00b5f21d4 100644
--- a/packages/web/src/app/i18n/locales/ko/groupDetail.json
+++ b/packages/web/src/app/i18n/locales/ko/groupDetail.json
@@ -78,6 +78,7 @@
"board": {
"tab": "게시판",
"notice": "공지사항",
+ "chat": "채팅방",
"emptyNotice": "등록된 공지사항이 없어요.",
"commentCount": "댓글 {{commentCount}}개"
},
diff --git a/packages/web/src/components/Avatar/Avatar.tsx b/packages/web/src/components/Avatar/Avatar.tsx
index 9be69fa1d..1e878463e 100644
--- a/packages/web/src/components/Avatar/Avatar.tsx
+++ b/packages/web/src/components/Avatar/Avatar.tsx
@@ -74,7 +74,7 @@ export default function Avatar({
)}
{countryImage && (
-
+
)}
@@ -84,7 +84,6 @@ export default function Avatar({
}
const AvatarImage = memo(function ({
- size,
imageUrl,
isPending,
}: Pick) {
@@ -96,19 +95,12 @@ const AvatarImage = memo(function ({
);
}
- const imageSize = {
- 'x-small': '1.7rem',
- small: '2.5rem',
- medium: '3.5rem',
- large: '6rem',
- };
-
if (imageUrl) {
return (
diff --git a/packages/web/src/components/Card/GroupingCard.tsx b/packages/web/src/components/Card/GroupingCard.tsx
index b9a53c15e..c07be6575 100644
--- a/packages/web/src/components/Card/GroupingCard.tsx
+++ b/packages/web/src/components/Card/GroupingCard.tsx
@@ -63,9 +63,8 @@ export default function GroupingCard({
src={imageUrl}
alt="group"
className="rounded-8 object-cover"
+ sizes="64px"
loading="lazy"
- placeholder="blur"
- blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFklEQVR42mN8//HLfwYiAOOoQvoqBABbWyZJf74GZgAAAABJRU5ErkJggg=="
/>
) : (
diff --git a/packages/web/src/components/Footer/Footer.tsx b/packages/web/src/components/Footer/Footer.tsx
index 442305cc8..6f2d912ca 100644
--- a/packages/web/src/components/Footer/Footer.tsx
+++ b/packages/web/src/components/Footer/Footer.tsx
@@ -1,4 +1,5 @@
'use client';
+import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ButtonAnimation } from '../Animation';
@@ -65,7 +66,7 @@ export default function Footer({ isSpacing = true, spacingColor }: FooterProps)
'text-sign-tertiary': !isSelected(tab),
})}
>
-
+
{t(tab.name)}
-
+
))}
diff --git a/packages/web/src/components/Form/MessageForm.tsx b/packages/web/src/components/Form/MessageForm.tsx
new file mode 100644
index 000000000..4111fc8dc
--- /dev/null
+++ b/packages/web/src/components/Form/MessageForm.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+import { useRef } from 'react';
+import { useForm } from 'react-hook-form';
+
+import { useTranslation } from '@/app/i18n/client';
+import { Icon } from '@/components/Icon';
+import { TextFieldController } from '@/components/TextField';
+import cn from '@/utils/cn';
+
+export type CreateCommentRequest = {
+ content: string;
+};
+
+export default function MessageForm() {
+ const { t } = useTranslation('community');
+ const textareaRef = useRef(null);
+ const hookForm = useForm({
+ mode: 'onChange',
+ defaultValues: {
+ content: '',
+ },
+ });
+
+ const { handleSubmit, reset, watch, register, setFocus } = hookForm;
+
+ return (
+
+ );
+}
diff --git a/packages/web/src/components/Modal/BottomSheet.tsx b/packages/web/src/components/Modal/BottomSheet.tsx
index 3222fbea2..bff415823 100644
--- a/packages/web/src/components/Modal/BottomSheet.tsx
+++ b/packages/web/src/components/Modal/BottomSheet.tsx
@@ -1,4 +1,5 @@
'use client';
+
import { forwardRef } from 'react';
import Sheet, { type SheetRef } from 'react-modal-sheet';
diff --git a/packages/web/src/components/Modal/ImageModal.tsx b/packages/web/src/components/Modal/ImageModal.tsx
index a34331ba5..6e1b93d04 100644
--- a/packages/web/src/components/Modal/ImageModal.tsx
+++ b/packages/web/src/components/Modal/ImageModal.tsx
@@ -48,7 +48,7 @@ export default function ImageModal({ images, currentImage, onClose }: ImageModal
>
{images.map((image, index) => (
-
+
))}
diff --git a/packages/web/src/utils/formatDate.ts b/packages/web/src/utils/formatDate.ts
new file mode 100644
index 000000000..05cb85b30
--- /dev/null
+++ b/packages/web/src/utils/formatDate.ts
@@ -0,0 +1,14 @@
+import { format, formatDistanceToNow } from 'date-fns';
+
+export const formatDate = (date: string, locale: Locale) => {
+ const d = new Date(date);
+ const now = Date.now();
+ const diff = (now - d.getTime()) / 1000;
+ if (diff < 60 * 1) {
+ return '방금 전';
+ }
+ if (diff < 60 * 60 * 24 * 3) {
+ return formatDistanceToNow(d, { addSuffix: true, locale });
+ }
+ return format(d, 'MM/dd', { locale });
+};
diff --git a/packages/web/src/utils/formatMeetingDate.ts b/packages/web/src/utils/formatMeetingDate.ts
index 91e61acbb..33bd49716 100644
--- a/packages/web/src/utils/formatMeetingDate.ts
+++ b/packages/web/src/utils/formatMeetingDate.ts
@@ -1,6 +1,7 @@
-import { DAY_OF_WEEK } from '@/constants';
import { format, getDay, parseISO } from 'date-fns';
+import { DAY_OF_WEEK } from '@/constants';
+
export function formatMeetingDate(meetDate: string, startTime: string) {
const startDate = parseISO(meetDate);
const dayOfWeekIndex = getDay(startDate);