diff --git a/package-lock.json b/package-lock.json index 10971b45..573a7e54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "next": "13.5.6", "react": "^18", "react-dom": "^18", - "recoil": "^0.7.7" + "recoil": "^0.7.7", + "swiper": "^11.1.3" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", @@ -4665,6 +4666,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swiper": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.3.tgz", + "integrity": "sha512-80MSxonyTxrGcaWj9YgvvhD8OG0B9/9IVZP33vhIEvyWvmKjnQDBieO+29wKvMx285sAtvZyrWBdkxaw6+D3aw==", + "funding": [ + { + "type": "patreon", + "url": "https://www.patreon.com/swiperjs" + }, + { + "type": "open_collective", + "url": "http://opencollective.com/swiper" + } + ], + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/tailwind-scrollbar": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.0.5.tgz", diff --git a/package.json b/package.json index 82dfa214..51cc8e99 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "next": "13.5.6", "react": "^18", "react-dom": "^18", - "recoil": "^0.7.7" + "recoil": "^0.7.7", + "swiper": "^11.1.3" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.3.0", diff --git a/public/PublicBadge.svg b/public/PublicBadge.svg new file mode 100644 index 00000000..a86721a8 --- /dev/null +++ b/public/PublicBadge.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/api/public/data.json b/src/app/api/public/data.json new file mode 100644 index 00000000..4435ee73 --- /dev/null +++ b/src/app/api/public/data.json @@ -0,0 +1,62 @@ +[ + { + "id": "1", + "title": "데이터 1", + "description": "이것은 데이터 1입니다.", + "time": "10:00" + }, + { + "id": "2", + "title": "데이터 2", + "description": "이것은 데이터 2입니다.", + "time": "11:00" + }, + { + "id": "3", + "title": "데이터 3", + "description": "이것은 데이터 3입니다.", + "time": "12:00" + }, + { + "id": "4", + "title": "데이터 4", + "description": "이것은 데이터 4입니다.", + "time": "13:00" + }, + { + "id": "5", + "title": "데이터 5", + "description": "이것은 데이터 5입니다.", + "time": "14:00" + }, + { + "id": "6", + "title": "데이터 6", + "description": "이것은 데이터 6입니다.", + "time": "15:00" + }, + { + "id": "7", + "title": "데이터 7", + "description": "이것은 데이터 7입니다.", + "time": "16:00" + }, + { + "id": "8", + "title": "데이터 8", + "description": "이것은 데이터 8입니다.", + "time": "17:00" + }, + { + "id": "9", + "title": "데이터 9", + "description": "이것은 데이터 9입니다.", + "time": "18:00" + }, + { + "id": "10", + "title": "데이터 10", + "description": "이것은 데이터 10입니다.", + "time": "19:00" + } +] diff --git a/src/app/api/public/route.ts b/src/app/api/public/route.ts new file mode 100644 index 00000000..7e50d2d9 --- /dev/null +++ b/src/app/api/public/route.ts @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server'; + +import data from './data.json'; + +export async function GET(request: any) { + return NextResponse.json(data, { status: 200 }); +} diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx index a59d4cac..2b8dab5f 100644 --- a/src/app/create/page.tsx +++ b/src/app/create/page.tsx @@ -13,6 +13,7 @@ import { logging } from '@/services/mixpanel'; import { axiosPostBoard } from '@/utils/apiInterface'; import { confirm } from '@/utils/confirm'; import { defaultState } from '@/utils/theme/default'; +import { toggleDialog } from '@/utils/toggleDialo'; export default function Create() { const [openModal, closeModal] = useModal(); @@ -29,6 +30,12 @@ export default function Create() { }; const handleConfirm = async () => { + const isPublic = await toggleDialog( + openModal, + closeModal, + '잠깐! 🙌', + '공개하기를 선택하면 내가 만든\n 스트링캣이 랜덤으로 홈에 공개돼요!', + ); const isConfirmed = await confirm( openModal, closeModal, @@ -38,6 +45,7 @@ export default function Create() { if (isConfirmed) { logging('click_submit_board_confirm', 'create'); const data = { + public: isPublic, theme: themelist[preview - 1], title: `${title}`, }; diff --git a/src/app/globals.css b/src/app/globals.css index 8eb6162c..c85d7e48 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -23,6 +23,7 @@ body { -ms-overflow-style: none; } + ::-webkit-scrollbar { display: none; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9720239c..e19520ee 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,3 @@ -'use client'; - -import { RecoilRoot } from 'recoil'; - import Script from 'next/script'; import './globals.css'; @@ -10,6 +6,7 @@ import Description from '@/component/Common/Description'; import Modal from '@/component/Common/Modal'; import OpenGraph from '@/component/Common/OpenGraph'; import InApp from '@/component/InApp'; +import RecoilWrapper from '@/component/RecoilWrapper'; export default function RootLayout({ children, @@ -46,11 +43,11 @@ export default function RootLayout({
- + {children} - +
diff --git a/src/app/page.tsx b/src/app/page.tsx index e9cd5dce..81dc96d8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,19 +5,21 @@ import { useEffect, useRef, useState } from 'react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; -import { AnimationVideo } from '@/component/Common/AnimationVideo'; import HeaderLayout from '@/component/Common/HeaderLayout'; import MainManStrcat from '@/component/MainManStrcat'; +import OpenStrcatBoard from '@/component/OpenStrcatBoard'; import { useLogin } from '@/hooks/useLogin'; import { bodyFontState } from '@/recoil/font/body'; import { titleFontState } from '@/recoil/font/title'; import { logging } from '@/services/mixpanel'; +import { axiosGetPublicBoard } from '@/utils/apiInterface'; import { focusToHighlight } from '@/utils/focusToHighlight'; import { defaultState } from '@/utils/theme/default'; export default function Home() { const [isLogin] = useLogin(); const router = useRouter(); + const [openBoard, setOpenBoard] = useState([]); const ref = useRef(null); const [windowHeight, setWindowHeight] = useState(0); @@ -26,6 +28,11 @@ export default function Home() { if (window) setWindowHeight(window.innerHeight); }, []); + useEffect(() => { + axiosGetPublicBoard().then((res) => { + setOpenBoard(res.data); + }); + }, []); const handleClickPersonal = () => { logging('click_create_board', 'main'); if (isLogin) router.push('create', { scroll: false }); @@ -51,25 +58,14 @@ export default function Home() { > 함께 문장을 이어가는 롤링페이퍼 -
-
- mainStrcatIcon -
- -
-
+
+ 다른 사람이 공개한 스트링켓이에요 +
+ + +
내 롤링페이퍼에서
친구들의 이야기를 듣고 싶다면
diff --git a/src/component/Common/AxiosInterceptor.tsx b/src/component/Common/AxiosInterceptor.tsx index a6ef76ff..d9233a3e 100644 --- a/src/component/Common/AxiosInterceptor.tsx +++ b/src/component/Common/AxiosInterceptor.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useInterceptor } from '@/hooks/useInterceptor'; export default function AxiosInterceptor() { diff --git a/src/component/Common/Icon/PublicBadge.tsx b/src/component/Common/Icon/PublicBadge.tsx new file mode 100644 index 00000000..6db5af62 --- /dev/null +++ b/src/component/Common/Icon/PublicBadge.tsx @@ -0,0 +1,26 @@ +export default function PublicBadge() { + return ( + + + + + + ); +} diff --git a/src/component/InApp.tsx b/src/component/InApp.tsx index e9a94946..96461ca2 100644 --- a/src/component/InApp.tsx +++ b/src/component/InApp.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useEffect } from 'react'; export default function InApp() { diff --git a/src/component/OpenStrcat.tsx b/src/component/OpenStrcat.tsx new file mode 100644 index 00000000..a09bfcc3 --- /dev/null +++ b/src/component/OpenStrcat.tsx @@ -0,0 +1,82 @@ +interface Props { + id: string; + OpenBoardThemeColor: string; + OpenBoardTextColor: string; + theme: string; + contentCount: string; + contentTextCount: string; + title: string; + lastContentCreatedAt: string; +} + +export default function OpenStrcat({ + id, + OpenBoardThemeColor, + OpenBoardTextColor, + title, + contentCount, + contentTextCount, + lastContentCreatedAt, +}: Props) { + const truncatedTitle = truncateText(title, 19); + const contentText = `${contentCount}개의 마음이, ${contentTextCount}자 이어졌어요!`; + const truncatedContentText = truncateText(contentText, 37); + + return ( +
+
+
+
+ {truncatedTitle} +
+
+ {truncatedContentText} +
+
+
+
+ {timeString(lastContentCreatedAt)} +
+
+
+
+ ); +} + +function truncateText(text: string, maxLength: number) { + if (text.length > maxLength) { + return text.substring(0, maxLength - 1) + '...'; + } + return text; +} + +const timeString = (date: string) => { + const now = new Date(); + const lastContentCreatedAt = new Date(date); + const diff = now.getTime() - lastContentCreatedAt.getTime(); + const diffSeconds = Math.floor(diff / 1000); + const diffMinutes = Math.floor(diffSeconds / 60); + const diffHours = Math.floor(diffMinutes / 60); + const diffDays = Math.floor(diffHours / 24); + const diffWeeks = Math.floor(diffDays / 7); + const diffMonths = Math.floor(diffDays / 30); + const diffYears = Math.floor(diffMonths / 12); + + if (diffSeconds < 11 * 60) { + return '방금'; + } else if (diffMinutes < 60) { + return `${diffMinutes}분 전`; + } else if (diffHours < 24) { + return `${diffHours}시간 전`; + } else if (diffDays < 7) { + return `${diffDays}일 전`; + } else if (diffWeeks < 4) { + return `${diffWeeks}주 전`; + } else if (diffMonths < 12) { + return `${diffMonths}개월 전`; + } else { + return `${diffYears}년 전`; + } +}; diff --git a/src/component/OpenStrcatBoard.tsx b/src/component/OpenStrcatBoard.tsx new file mode 100644 index 00000000..ab57434d --- /dev/null +++ b/src/component/OpenStrcatBoard.tsx @@ -0,0 +1,40 @@ +import 'react'; + +import Link from 'next/link'; + +import OpenStrcat from './OpenStrcat'; +import { openBoard } from '@/types/openBoard'; +import { OpenBoardTextColor, OpenBoardThemeColor } from '@/types/theme'; +import 'swiper/css'; +import { Swiper, SwiperSlide } from 'swiper/react'; + +interface Props { + openBoard: openBoard[]; +} + +export default function OpenStrcatBoard({ openBoard }: Props) { + return ( +
+ + {openBoard.map((item, i) => { + return ( + + + + + + ); + })} + +
+ ); +} diff --git a/src/component/RecoilWrapper.tsx b/src/component/RecoilWrapper.tsx new file mode 100644 index 00000000..dd3336e3 --- /dev/null +++ b/src/component/RecoilWrapper.tsx @@ -0,0 +1,11 @@ +'use client'; + +import { RecoilRoot } from 'recoil'; + +export default function RecoilWrapper({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/src/component/ToggleDialog.tsx b/src/component/ToggleDialog.tsx new file mode 100644 index 00000000..01bb4048 --- /dev/null +++ b/src/component/ToggleDialog.tsx @@ -0,0 +1,80 @@ +import { useState } from 'react'; + +import BottomButton from './Common/BottomButton'; +import { bodyFontState } from '@/recoil/font/body'; +import { titleFontState } from '@/recoil/font/title'; + +interface Props { + resolve: (value: boolean | PromiseLike) => void; + closeModal: () => void; + title: string; + description: string; +} + +export default function ToggleDialog({ + resolve, + title, + description, + closeModal, +}: Props) { + const [isPublic, setIsPublic] = useState(false); + return ( +
+
+

+ {title} +

+ {description && ( +

+ {description} +

+ )} +
+ +
+
+ { + resolve(isPublic); + closeModal(); + }} + /> +
+
+
+ ); +} diff --git a/src/types/openBoard.ts b/src/types/openBoard.ts new file mode 100644 index 00000000..301608fa --- /dev/null +++ b/src/types/openBoard.ts @@ -0,0 +1,8 @@ +export interface openBoard { + contentCount: string; + contentTextCount: string; + id: string; + lastContentCreatedAt: string; + theme: string; + title: string; +} diff --git a/src/types/theme.ts b/src/types/theme.ts index 01a4659b..8395c0a8 100644 --- a/src/types/theme.ts +++ b/src/types/theme.ts @@ -75,3 +75,23 @@ export const themeObj = { mas: mas, sul: sul, }; + +export const OpenBoardThemeColor: Record = { + night: 'bg-strcat-night/50 border-strcat-night', + peach: 'bg-strcat-peach/50 border-strcat-peach', + lilac: 'bg-strcat-lilac/50 border-strcat-lilac', + chris: 'bg-strcat-chris/50 border-strcat-chris', + mas: 'bg-strcat-mas/50 border-strcat-mas', + sul: 'bg-strcat-sul/50 border-strcat-sul', + spring: 'bg-strcat-spring/50 border-strcat-spring', +}; + +export const OpenBoardTextColor: Record = { + night: 'text-strcat-night', + peach: 'text-strcat-peach', + lilac: 'text-strcat-lilac', + chris: 'text-white/70', + mas: 'text-white/70', + sul: 'text-strcat-sul', + spring: 'text-strcat-spring', +}; diff --git a/src/utils/apiInterface.tsx b/src/utils/apiInterface.tsx index 6d46bc08..dc473073 100644 --- a/src/utils/apiInterface.tsx +++ b/src/utils/apiInterface.tsx @@ -4,6 +4,10 @@ export const axiosGetBoard = (id: string) => { return axiosInstance.get(`/boards/${id}`); }; +export const axiosGetPublicBoard = () => { + return axiosInstance.get(`/boards/public`); +}; + export const axiosGetBoardSummaries = (id: string) => { return axiosInstance.get(`/boards/${id}/summaries`); }; @@ -34,7 +38,7 @@ export const axiosPostBoardContentPicture = (id: string, requestData: any) => { export const axiosPostUserHistory = (requestData: any) => { return axiosInstance.post('/users/history', requestData); -} +}; export const axoisDeleteContents = (id: string, requestData: any) => { return axiosInstance.delete(`boards/${id}/contents`, requestData); diff --git a/src/utils/toggleDialo.tsx b/src/utils/toggleDialo.tsx new file mode 100644 index 00000000..27402e3c --- /dev/null +++ b/src/utils/toggleDialo.tsx @@ -0,0 +1,23 @@ +import { Dispatch, SetStateAction } from 'react'; + +import ToggleDialog from '@/component/ToggleDialog'; + +export const toggleDialog = ( + openModal: (modalComponent: JSX.Element) => void, + closeModal: () => void, + title: string, + description: string, +): Promise => { + return new Promise((resolve) => { + openModal( + { + closeModal(); + }} + resolve={resolve} + title={title} + description={description} + />, + ); + }); +}; diff --git a/tailwind.config.js b/tailwind.config.js index 986ab833..c6b17643 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -39,6 +39,7 @@ module.exports = { 'strcat-chris': '#246F50', 'strcat-mas': '#DE6565', 'strcat-sul': '#82CBFF', + 'strcat-spring': '#FFBACF', }, keyframes: { slide: {