-
-
- {folders?.map((item) => (
- -
-
- {item.name}
-
-
- ))}
-
+ <>
+ {showModal && (
+ <>
+
setShowModal(false)}>{modalContent}
+
setShowModal(false)}
+ >
+ >
+ )}
+
+
+
+ {folders?.map((item) => (
+ -
+ {
+ if (item.id < 0) {
+ router.push('/folder');
+ return;
+ }
-
-
-
-
{currentFolderName}
-
- {Object.entries(UTIL_BUTTONS_PROPS).map(([key, btn]) => (
- -
- {currentFolderId !== -1 && (
-
- {btn.btnText}
-
- )}
-
- ))}
-
+ {item.name}
+
+
+ ))}
+
+
+
+
+
+
{currentFolderName}
+ {currentFolderId !== -1 && (
+
+ {Object.entries(UTIL_BUTTONS_PROPS).map(([key, btn]) => (
+ -
+
+ {btn.btnText}
+
+
+ ))}
+
+ )}
+
-
+ >
);
}
diff --git a/components/Footer/Footer.tsx b/components/Footer/Footer.tsx
index 395528595..f2a16f7f5 100644
--- a/components/Footer/Footer.tsx
+++ b/components/Footer/Footer.tsx
@@ -49,7 +49,6 @@ export default function Footer() {
rel='noreferrer noopener'
>
- {' '}
@@ -61,7 +60,6 @@ export default function Footer() {
rel='noreferrer noopener'
>
- {' '}
diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx
index bebeda533..6dc8d6c11 100644
--- a/components/Header/Header.tsx
+++ b/components/Header/Header.tsx
@@ -1,51 +1,44 @@
-import Button from '../Button/Button';
+'use client';
+
+import Button from '@/components/Button/Button';
import Image from 'next/image';
import styles from './Header.module.css';
import Link from 'next/link';
-
import Account from '@/components/Account/Account';
-import { useUserInfo } from '@/contexts/UserInfoContext';
-import { axiosInstance } from '@/utils/axiosInstance';
+import { useAuth } from '@/contexts/AuthContext';
import { useEffect } from 'react';
+import { useUserInfo } from '@/hooks/useUserInfo';
export default function Header() {
- const { userInfo, setUserInfo } = useUserInfo();
+ const { getUserInfo, signOut } = useAuth();
+ const { user } = useUserInfo();
const loadUser = async () => {
- const accessToken = localStorage.getItem('accessToken');
- if (accessToken) {
- const response = await axiosInstance.get('/users', {
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- });
- setUserInfo(response.data.data[0]);
- }
+ await getUserInfo();
};
useEffect(() => {
loadUser();
}, []);
-
return (
- <>
-
-
-
-
-
+
- >
+
+ >
+ ) : (
+
+
+
+ )}
+
+
);
}
diff --git a/components/LinkCardList/LinkCardList.module.scss b/components/LinkCardList/LinkCardList.module.scss
index f6e9e80c0..af22d5a1a 100644
--- a/components/LinkCardList/LinkCardList.module.scss
+++ b/components/LinkCardList/LinkCardList.module.scss
@@ -51,3 +51,13 @@
text-align: center;
padding: 40px;
}
+
+.overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 2;
+}
diff --git a/components/LinkCardList/LinkCardList.tsx b/components/LinkCardList/LinkCardList.tsx
index e5adf6aea..763916b38 100644
--- a/components/LinkCardList/LinkCardList.tsx
+++ b/components/LinkCardList/LinkCardList.tsx
@@ -1,45 +1,24 @@
import FolderToolBar from '@/components/FolderToolBar/FolderToolBar';
import LinkCard from '@/components/LinkCard/LinkCard';
-import SearchBar from '@/components/SearchBar/SearchBar';
import styles from './LinkCardList.module.scss';
-import { FolderObj, LinkObj } from '@/utils/interfaces';
-import { useState } from 'react';
+import { useKeywordState } from '@/hooks/useSearchValue';
+import useLinks from '../../hooks/useLinks';
+import useModal from '../../hooks/useModal';
+import Modal from '../Modal/Modal';
+import useFolders from '../../hooks/useFolders';
-interface LinkCardListProp {
- items: LinkObj[] | undefined;
- folders?: FolderObj[];
- folderNameOnClick: (id: number) => void;
- currentFolderId: number;
- onFolderAddClick: () => void;
- onFolderNameChangeClick: () => void;
- onFolderDeleteClick: () => void;
- onLinkDelete: (link: string) => void;
- onAddtoFolder: (link: string) => void;
- onShare: () => void;
-}
-
-const LinkCardList = ({
- items,
- folders,
- folderNameOnClick,
- currentFolderId,
- onFolderAddClick,
- onFolderNameChangeClick,
- onFolderDeleteClick,
- onLinkDelete,
- onAddtoFolder,
- onShare,
-}: LinkCardListProp) => {
- const [searchText, setSearchText] = useState
('');
+interface LinkCardListProp {}
- const handleSearchInput = (text: string) => {
- setSearchText(text);
- };
+export default function LinkCardList({}: LinkCardListProp) {
+ const { keyword } = useKeywordState();
+ const { links } = useLinks();
+ const { showModal, setShowModal, modalContent, setModal } = useModal();
+ const { folders } = useFolders();
function filterLinksByKeyword(keyword: string) {
- if (!items) return;
- if (searchText === '') return items;
- return items.filter(
+ if (!links) return;
+ if (keyword === '') return links;
+ return links.filter(
(item) =>
item.url?.includes(keyword) ||
item.description?.includes(keyword) ||
@@ -47,32 +26,41 @@ const LinkCardList = ({
);
}
- const curItems = filterLinksByKeyword(searchText);
+ const curItems = filterLinksByKeyword(keyword);
+
+ const handleAddToFolder = (url: string) => {
+ setModal('add', {
+ headerText: '폴더에 추가',
+ subHeaderText: url,
+ folders: folders,
+ buttonText: '추가하기',
+ });
+ };
+
+ const handleLinkDelete = (url: string) => {
+ setModal('delete', { headerText: '링크 삭제', subHeaderText: url });
+ };
return (
+ {showModal && (
+ <>
+
setShowModal(false)}>{modalContent}
+
setShowModal(false)}
+ >
+ >
+ )}
-
- {folders && (
-
- )}
-
{curItems && curItems.length > 0 ? (
{curItems.map((item) => (
-
))}
@@ -83,6 +71,4 @@ const LinkCardList = ({
);
-};
-
-export default LinkCardList;
+}
diff --git a/components/ModalContents/ModalContents.module.scss b/components/ModalContents/ModalContents.module.scss
index e924721f0..0de88085d 100644
--- a/components/ModalContents/ModalContents.module.scss
+++ b/components/ModalContents/ModalContents.module.scss
@@ -145,7 +145,7 @@
font-weight: 500;
line-height: 18px;
position: fixed;
- bottom: 120px;
+ bottom: -100px;
opacity: 0;
transition: opacity 0.5s ease-in-out;
z-index: 1010;
diff --git a/components/ModalContents/ShareModal.tsx b/components/ModalContents/ShareModal.tsx
index 465bcac75..a56e46e06 100644
--- a/components/ModalContents/ShareModal.tsx
+++ b/components/ModalContents/ShareModal.tsx
@@ -35,7 +35,7 @@ export default function ShareModal({
}: ModalContentProps) {
const [showToast, setShowToast] = useState(false);
- const SHARE_URL = `http://localhost:3000/shared/${folderNum}`;
+ const SHARE_URL = `https://5-weekly-mission-eta-two.vercel.app/shared/${folderNum}`;
const handleCopyLinkClipBoard = async () => {
try {
diff --git a/components/SearchBar/SearchBar.tsx b/components/SearchBar/SearchBar.tsx
index 1d0b87ca9..20fd1f4f2 100644
--- a/components/SearchBar/SearchBar.tsx
+++ b/components/SearchBar/SearchBar.tsx
@@ -3,24 +3,22 @@ import searchIcon from '@/public/assets/images/search_icon.svg';
import { ChangeEvent } from 'react';
import deleteTextIcon from '@/public/assets/images/delete_text.png';
import Image from 'next/image';
+import { useKeywordState } from '@/hooks/useSearchValue';
export const SEARCH_INPUT_ID = 'search-link';
const SEARCH_INPUT_PLACEHOLDER = '링크를 검색하세요';
const SEARCH_INPUT_ICON_ALT = 'Search Icon';
-interface SearchBarProps {
- onChange: (keyword: string) => void;
- searchText: string;
-}
+export default function SearchBar() {
+ const { keyword, setKeyword } = useKeywordState();
-const SearchBar = ({ onChange, searchText }: SearchBarProps) => {
const handleChange = (e: ChangeEvent) => {
e.preventDefault();
- onChange(e.target.value);
+ setKeyword(e.target.value);
};
const handleSearchDelete = () => {
- onChange('');
+ setKeyword('');
};
const handleSubmit = (e: React.FormEvent) => {
@@ -38,16 +36,14 @@ const SearchBar = ({ onChange, searchText }: SearchBarProps) => {
id={SEARCH_INPUT_ID}
placeholder={SEARCH_INPUT_PLACEHOLDER}
className={styles.searchInput}
- value={searchText}
onChange={handleChange}
+ value={keyword}
/>
- {searchText !== '' && (
+ {keyword !== '' && (
)}
);
-};
-
-export default SearchBar;
+}
diff --git a/components/SharedLinkCard/SharedLinkCard.tsx b/components/SharedLinkCard/SharedLinkCard.tsx
new file mode 100644
index 000000000..72aafb474
--- /dev/null
+++ b/components/SharedLinkCard/SharedLinkCard.tsx
@@ -0,0 +1,50 @@
+import { useState, useEffect, useRef } from 'react';
+import Image from 'next/image';
+import styles from '@/components/LinkCard/LinkCard.module.scss';
+import getTimeDifference from '@/utils/time-functions/getTimeDifference';
+import formatDate from '@/utils/time-functions/formatDate';
+import { LinkObj } from '@/utils/interfaces';
+
+const noImagePlaceholder = '/assets/images/no-image.png';
+
+interface SharedLinkCardProp {
+ linkCardInfo: LinkObj;
+}
+
+export default function SharedLinkCard({ linkCardInfo }: SharedLinkCardProp) {
+ const thumbnailURL = linkCardInfo.image_source
+ ? linkCardInfo.image_source
+ : noImagePlaceholder;
+ const description = linkCardInfo.description;
+ const url = linkCardInfo.url;
+ const createdDate = new Date(linkCardInfo.created_at);
+ const timestamp = getTimeDifference(createdDate);
+ const altMessage = linkCardInfo.title;
+
+ return (
+
+
+
+
+
+
+ {timestamp}
+
+
{description}
+
+ {formatDate(createdDate)}
+
+
+
+ );
+}
diff --git a/components/SharedLinkCardList/SharedLinkCardList.tsx b/components/SharedLinkCardList/SharedLinkCardList.tsx
new file mode 100644
index 000000000..de1d08006
--- /dev/null
+++ b/components/SharedLinkCardList/SharedLinkCardList.tsx
@@ -0,0 +1,46 @@
+'use client';
+
+import { LinkObj } from '@/utils/interfaces';
+import SearchBar from '@/components/SearchBar/SearchBar';
+import styles from '@/components/LinkCardList/LinkCardList.module.scss';
+import SharedLinkCard from '@/components/SharedLinkCard/SharedLinkCard';
+import { useKeywordState } from '@/hooks/useSearchValue';
+interface SharedLinkCardListProps {
+ items: LinkObj[] | undefined;
+}
+
+export default function SharedLinkCardList({ items }: SharedLinkCardListProps) {
+ const { keyword } = useKeywordState();
+
+ function filterLinksByKeyword(keyword: string) {
+ if (!items) return;
+ if (keyword === '') return items;
+ return items.filter(
+ (item) =>
+ item.url?.includes(keyword) ||
+ item.description?.includes(keyword) ||
+ item.title?.includes(keyword)
+ );
+ }
+
+ const curItems = filterLinksByKeyword(keyword);
+
+ return (
+
+
+
+ {curItems && curItems.length > 0 ? (
+
+ {curItems.map((item) => (
+ -
+
+
+ ))}
+
+ ) : (
+
저장된 링크가 없습니다
+ )}
+
+
+ );
+}
diff --git a/components/SharedProfile/SharedProfile.module.scss b/components/SharedProfile/SharedProfile.module.scss
new file mode 100644
index 000000000..fbae9b69d
--- /dev/null
+++ b/components/SharedProfile/SharedProfile.module.scss
@@ -0,0 +1,20 @@
+@use '@/styles/mixins' as m;
+
+.profileWrapper {
+ @include m.flex(column, flex-start, center);
+ gap: 12px;
+}
+
+.profileImage {
+ border-radius: 50%;
+}
+.folderInfo {
+ @include m.flex(column, flex-start, center);
+ gap: 20px;
+ padding: 20px 0 60px;
+}
+
+.folderName {
+ font-size: 40px;
+ font-weight: 600;
+}
diff --git a/components/SharedProfile/SharedProfile.tsx b/components/SharedProfile/SharedProfile.tsx
new file mode 100644
index 000000000..6ada4a010
--- /dev/null
+++ b/components/SharedProfile/SharedProfile.tsx
@@ -0,0 +1,29 @@
+import { UserInfoObj } from '@/utils/interfaces';
+import styles from './SharedProfile.module.scss';
+import Image from 'next/image';
+
+interface SharedProfileProps {
+ userInfo: UserInfoObj;
+ folderName: string;
+}
+
+export default function SharedProfile({
+ userInfo,
+ folderName,
+}: SharedProfileProps) {
+ return (
+
+
+
+ @{userInfo.name}
+
+
{folderName}
+
+ );
+}
diff --git a/components/ShowTextToggle/ShowTextToggle.tsx b/components/ShowTextToggle/ShowTextToggle.tsx
index 2ebe7d982..09fbfc9fc 100644
--- a/components/ShowTextToggle/ShowTextToggle.tsx
+++ b/components/ShowTextToggle/ShowTextToggle.tsx
@@ -10,7 +10,12 @@ export default function ShowTextToggle({
onClick,
}: ShowTextToggleProps) {
return (
-
{errors.password && (