diff --git a/app/components/SearchBar.module.scss b/app/components/SearchBar.module.scss index 3db5b16..3893eae 100644 --- a/app/components/SearchBar.module.scss +++ b/app/components/SearchBar.module.scss @@ -4,11 +4,18 @@ position: sticky; top: 0; z-index: 20; - padding: 12px 8px; + padding: 12px; background: #fff; display: flex; - align-items: center; justify-content: center; + width: auto; + gap: 15px; +} + +.inner { + width: 100%; + display: flex; + align-items: center; gap: 15px; } diff --git a/app/components/SearchBar.tsx b/app/components/SearchBar.tsx index 65b2bda..d325338 100644 --- a/app/components/SearchBar.tsx +++ b/app/components/SearchBar.tsx @@ -2,19 +2,22 @@ import { Form } from '@remix-run/react'; import styles from './SearchBar.module.scss'; -export const SearchBar = () => { +export const SearchBar = ({ defaultQuery = '' }: { defaultQuery?: string }) => { return (
- - Sonnets - - +
+ + Sonnets + + +
); }; diff --git a/app/features/music/pages/music.user.module.scss b/app/features/music/pages/music.user.module.scss index b4a7e02..59680eb 100644 --- a/app/features/music/pages/music.user.module.scss +++ b/app/features/music/pages/music.user.module.scss @@ -26,7 +26,6 @@ } } -// 유저 정보(작성자) 스타일 .author-info { margin-bottom: 2rem; display: flex; diff --git a/app/features/profile/components/SearchSongInput.tsx b/app/features/profile/components/SearchSongInput.tsx deleted file mode 100644 index bacaa8a..0000000 --- a/app/features/profile/components/SearchSongInput.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Await, Form } from '@remix-run/react'; -import { Suspense } from 'react'; - -import type { MusicInfo } from 'app/external/music/IMusicSearchAPI'; -import styles from 'app/features/profile/pages/searchSong.module.scss'; - -const PLACEHOLDER = '/images/features/profile/album_default2.png'; - -interface Props { - songs: Promise | undefined; - onSelect: (song: MusicInfo, index: number) => void; -} - -export default function SearchSongInput({ onSelect, songs }: Props) { - return ( -
-
- -
- - -
- ); -} - -const SearchedSongList = ({ - songs, - onSelect, -}: { - songs: Promise | undefined; - onSelect: (song: MusicInfo, index: number) => void; -}) => { - return ( - - ); -}; diff --git a/app/features/profile/components/SearchSongInputloader.tsx b/app/features/profile/components/SearchSongInputloader.tsx deleted file mode 100644 index 312abcb..0000000 --- a/app/features/profile/components/SearchSongInputloader.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { searchMusic } from 'app/external/music/SearchMusic'; - -export const searchSongInputLoader = ({ request }: { request: Request }) => { - const searchQuery = new URL(request.url).searchParams.get('query'); - if (!searchQuery) { - return undefined; - } - - return searchMusic.searchSongWithQuery(searchQuery); -}; diff --git a/app/features/profile/loader.ts b/app/features/profile/loader.ts index b94fe38..800add3 100644 --- a/app/features/profile/loader.ts +++ b/app/features/profile/loader.ts @@ -5,7 +5,6 @@ import { getCurrentDBUser, getCurrentUser, requireUserOwnership } from 'app/exte import { buildSpotifyTrackUrl, getSpotifyEmbed } from 'app/external/music/SpotifyOEmbed'; import createLoader from 'app/utils/createLoader'; -import { searchSongInputLoader } from './components/SearchSongInputloader'; import { fetchAccountSettingsData, fetchFollowers, diff --git a/app/features/profile/pages/addTodaySong.module.scss b/app/features/profile/pages/addTodaySong.module.scss index 5c9921d..14c8106 100644 --- a/app/features/profile/pages/addTodaySong.module.scss +++ b/app/features/profile/pages/addTodaySong.module.scss @@ -1,48 +1,48 @@ -@use 'styles/layout' as *; +// @use 'styles/layout' as *; -.add-today-song-container { - display: flex; - padding: 36px 16px; - flex-direction: column; - border-radius: 8px; -} +// .add-today-song-container { +// display: flex; +// padding: 36px 16px; +// flex-direction: column; +// border-radius: 8px; +// } -.title-box { - display: flex; - align-items: center; - .back-button { - color: $black-500; - font-size: 24px; - font-weight: 600; - margin-left: 16px; - margin: 0; - text-decoration: none; - } - .title { - flex: 1 0 auto; - font-size: 24px; - font-weight: 600; - margin-left: 16px; - margin: 0; - text-align: center; - } -} +// .title-box { +// display: flex; +// align-items: center; +// .back-button { +// color: $black-500; +// font-size: 24px; +// font-weight: 600; +// margin-left: 16px; +// margin: 0; +// text-decoration: none; +// } +// .title { +// flex: 1 0 auto; +// font-size: 24px; +// font-weight: 600; +// margin-left: 16px; +// margin: 0; +// text-align: center; +// } +// } -.search-section { - margin-top: 24px; - h2 { - font-size: 20px; - font-weight: 600; - margin-bottom: 16px; - } -} +// .search-section { +// margin-top: 24px; +// h2 { +// font-size: 20px; +// font-weight: 600; +// margin-bottom: 16px; +// } +// } -.initial-song-container { - margin-top: 24px; - h3 { - margin: 0; - font-size: 20px; - font-weight: 600; - margin-bottom: 16px; - } -} +// .initial-song-container { +// margin-top: 24px; +// h3 { +// margin: 0; +// font-size: 20px; +// font-weight: 600; +// margin-bottom: 16px; +// } +// } diff --git a/app/features/profile/pages/addTodaySong.tsx b/app/features/profile/pages/addTodaySong.tsx index c88f914..3eaf4b9 100644 --- a/app/features/profile/pages/addTodaySong.tsx +++ b/app/features/profile/pages/addTodaySong.tsx @@ -1,60 +1,60 @@ -import { Link, useLoaderData, useSubmit } from '@remix-run/react'; - -import type { MusicInfo } from 'app/external/music/IMusicSearchAPI'; -import SongItem, { type SimpleSong } from 'app/features/profile/components/SongItem'; -import styles from 'app/features/profile/pages/addTodaySong.module.scss'; - -import SearchSongInput from '../components/SearchSongInput'; -import { addTodaySongLoader } from '../loader'; - -export default function AddTodaySongPage() { - const { initialSong, songs } = useLoaderData(); - const savePickedSong = useSavePickedSong(); - - return ( -
-
- - {'<'} - -

오늘의 추천곡 수정

-
- -
- -
- - {initialSong && ( -
-

현재 추천곡

- -
- )} -
- ); -} - -const useSavePickedSong = () => { - const submit = useSubmit(); - - return async (song: MusicInfo) => { - const ok = window.confirm(`"${song.title}" – ${song.artist}\n오늘의 추천곡으로 등록하시겠습니까?`); - if (!ok) return; - - const simple: SimpleSong = { - id: 0, - title: song.title, - artist: song.artist, - album: song.album ?? null, - thumbnailUrl: song.albumCover ? await song.albumCover : null, - }; - - const fd = new FormData(); - fd.append('title', simple.title); - fd.append('artist', simple.artist); - fd.append('album', simple.album ?? ''); - fd.append('thumbnailUrl', simple.thumbnailUrl ?? ''); - fd.append('spotifyId', song.spotifyId ?? ''); - submit(fd, { method: 'post' }); - }; -}; +// import { Link, useLoaderData, useSubmit } from '@remix-run/react'; + +// import type { MusicInfo } from 'app/external/music/IMusicSearchAPI'; +// import styles from 'app/features/profile/pages/addTodaySong.module.scss'; +// import SongItem, { type SimpleSong } from 'app/features/search/components/SongItem'; + +// import SearchSongInput from '../../search/components/SearchSongInput'; +// import { addTodaySongLoader } from '../loader'; + +// export default function AddTodaySongPage() { +// const { initialSong, songs } = useLoaderData(); +// const savePickedSong = useSavePickedSong(); + +// return ( +//
+//
+// +// {'<'} +// +//

오늘의 추천곡 수정

+//
+ +//
+// +//
+ +// {initialSong && ( +//
+//

현재 추천곡

+// +//
+// )} +//
+// ); +// } + +// const useSavePickedSong = () => { +// const submit = useSubmit(); + +// return async (song: MusicInfo) => { +// const ok = window.confirm(`"${song.title}" – ${song.artist}\n오늘의 추천곡으로 등록하시겠습니까?`); +// if (!ok) return; + +// const simple: SimpleSong = { +// id: 0, +// title: song.title, +// artist: song.artist, +// album: song.album ?? null, +// thumbnailUrl: song.albumCover ? await song.albumCover : null, +// }; + +// const fd = new FormData(); +// fd.append('title', simple.title); +// fd.append('artist', simple.artist); +// fd.append('album', simple.album ?? ''); +// fd.append('thumbnailUrl', simple.thumbnailUrl ?? ''); +// fd.append('spotifyId', song.spotifyId ?? ''); +// submit(fd, { method: 'post' }); +// }; +// }; diff --git a/app/features/profile/pages/editList.tsx b/app/features/profile/pages/editList.tsx index 0f4be99..81f535b 100644 --- a/app/features/profile/pages/editList.tsx +++ b/app/features/profile/pages/editList.tsx @@ -17,7 +17,7 @@ import { Link } from '@remix-run/react'; import { useState } from 'react'; import styles from './editList.module.scss'; -import SongItem from '../components/SongItem'; +import SongItem from '../../search/components/SongItem'; import { SortableItem } from '../components/SortableItem'; // 새로 추가된 컴포넌트 export default function EditList() { diff --git a/app/features/profile/pages/searchSong.module.scss b/app/features/profile/pages/searchSong.module.scss deleted file mode 100644 index 8a14db4..0000000 --- a/app/features/profile/pages/searchSong.module.scss +++ /dev/null @@ -1,90 +0,0 @@ -.searchWrapper { - background-color: #f2f4f5; - padding: 24px; - border-radius: 8px; - - margin: 16px 24px; -} - -.formRow { - display: flex; - gap: 8px; -} - -.input { - flex: 1; - padding: 8px 12px; - border: 1px solid #d1d5db; - border-radius: 100px; - font-size: 14px; -} - -.button { - padding: 8px 16px; - border: none; - background-color: #2563eb; - color: #fff; - border-radius: 4px; - cursor: pointer; - - &:disabled { - opacity: 0.6; - cursor: default; - } -} - -.songList { - margin-top: 16px; - display: flex; - flex-direction: column; - gap: 12px; - padding: 0; -} - -.songItem { - display: flex; - gap: 16px; - align-items: center; - padding: 12px; - background-color: #fff; - border: 1px solid #e5e7eb; - border-radius: 6px; - cursor: pointer; - - &:hover { - background-color: #f9fafb; - } -} - -.cover { - width: 64px; - height: 64px; - object-fit: cover; - border-radius: 4px; - background-color: #f3f4f6; -} - -.texts { - overflow: hidden; -} - -.title { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 2px; -} - -.artist, -.album { - font-size: 12px; - color: #6b7280; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.empty { - color: #9ca3af; -} diff --git a/app/features/search/action.ts b/app/features/search/action.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/features/search/components/SearchSongInput.tsx b/app/features/search/components/SearchSongInput.tsx new file mode 100644 index 0000000..f0badd2 --- /dev/null +++ b/app/features/search/components/SearchSongInput.tsx @@ -0,0 +1,73 @@ +// import { Await, Form } from '@remix-run/react'; +// import { Suspense } from 'react'; + +// import type { MusicInfo } from 'app/external/music/IMusicSearchAPI'; +// import styles from 'app/features/profile/pages/searchSong.module.scss'; + +// const PLACEHOLDER = '/images/features/profile/album_default2.png'; + +// interface Props { +// songs: Promise | undefined; +// onSelect: (song: MusicInfo, index: number) => void; +// } + +// export default function SearchSongInput({ onSelect, songs }: Props) { +// return ( +//
+//
+// +//
+ +// +//
+// ); +// } + +// const SearchedSongList = ({ +// songs, +// onSelect, +// }: { +// songs: Promise | undefined; +// onSelect: (song: MusicInfo, index: number) => void; +// }) => { +// return ( +//
    +// 검색 중...

    }> +// +// {(resolvedSongs) => ( +// <> +// {resolvedSongs?.length === 0 &&

    검색 결과가 없습니다.

    } +// {resolvedSongs?.map((song, i) => ( +//
  • onSelect(song, i)} +// > +// }> +// +// {(albumCover) => ( +// {song.album} { +// e.currentTarget.src = PLACEHOLDER; +// }} +// /> +// )} +// +// +//
    +//
    {song.title}
    +//
    {song.artist}
    +//
    {song.album}
    +//
    +//
  • +// ))} +// +// )} +//
    +//
    +//
+// ); +// }; diff --git a/app/features/search/components/SearchSongInputloader.tsx b/app/features/search/components/SearchSongInputloader.tsx new file mode 100644 index 0000000..77dcdfe --- /dev/null +++ b/app/features/search/components/SearchSongInputloader.tsx @@ -0,0 +1,5 @@ +import { searchMusic } from 'app/external/music/SearchMusic'; + +export const searchSongInputLoader = ({ searchQuery }: { searchQuery: string }) => { + return searchMusic.searchSongWithQuery(searchQuery); +}; diff --git a/app/features/profile/components/SongItem.module.scss b/app/features/search/components/SongItem.module.scss similarity index 100% rename from app/features/profile/components/SongItem.module.scss rename to app/features/search/components/SongItem.module.scss diff --git a/app/features/profile/components/SongItem.tsx b/app/features/search/components/SongItem.tsx similarity index 100% rename from app/features/profile/components/SongItem.tsx rename to app/features/search/components/SongItem.tsx diff --git a/app/features/search/components/searchSong.module.scss b/app/features/search/components/searchSong.module.scss new file mode 100644 index 0000000..608bbf1 --- /dev/null +++ b/app/features/search/components/searchSong.module.scss @@ -0,0 +1,90 @@ +// .searchWrapper { +// background-color: #f2f4f5; +// padding: 24px; +// border-radius: 8px; + +// margin: 16px 24px; +// } + +// .formRow { +// display: flex; +// gap: 8px; +// } + +// .input { +// flex: 1; +// padding: 8px 12px; +// border: 1px solid #d1d5db; +// border-radius: 100px; +// font-size: 14px; +// } + +// .button { +// padding: 8px 16px; +// border: none; +// background-color: #2563eb; +// color: #fff; +// border-radius: 4px; +// cursor: pointer; + +// &:disabled { +// opacity: 0.6; +// cursor: default; +// } +// } + +// .songList { +// margin-top: 16px; +// display: flex; +// flex-direction: column; +// gap: 12px; +// padding: 0; +// } + +// .songItem { +// display: flex; +// gap: 16px; +// align-items: center; +// padding: 12px; +// background-color: #fff; +// border: 1px solid #e5e7eb; +// border-radius: 6px; +// cursor: pointer; + +// &:hover { +// background-color: #f9fafb; +// } +// } + +// .cover { +// width: 64px; +// height: 64px; +// object-fit: cover; +// border-radius: 4px; +// background-color: #f3f4f6; +// } + +// .texts { +// overflow: hidden; +// } + +// .title { +// font-weight: 600; +// white-space: nowrap; +// overflow: hidden; +// text-overflow: ellipsis; +// margin-bottom: 2px; +// } + +// .artist, +// .album { +// font-size: 12px; +// color: #6b7280; +// white-space: nowrap; +// overflow: hidden; +// text-overflow: ellipsis; +// } + +// .empty { +// color: #9ca3af; +// } diff --git a/app/features/search/loader.ts b/app/features/search/loader.ts index 86115cb..7717a2e 100644 --- a/app/features/search/loader.ts +++ b/app/features/search/loader.ts @@ -2,17 +2,16 @@ import { data } from '@remix-run/node'; import createLoader from 'app/utils/createLoader'; -import { findUserByHandleSim, getRecommendedUsers } from './services'; +import { searchSongInputLoader } from './components/SearchSongInputloader'; -export const searchLoader = createLoader(async ({ db, request }) => { +export const searchLoader = createLoader(async ({ request }) => { const url = new URL(request.url); - const handle = url.searchParams.get('handle'); - const recommendedUsersPromise = getRecommendedUsers(db)(); - const searchResultsPromise = handle ? findUserByHandleSim(db)({ handle }) : Promise.resolve(null); - const [recommendedUsers, searchResults] = await Promise.all([recommendedUsersPromise, searchResultsPromise]); + const query = url.searchParams.get('search') ?? ''; + + const songs = searchSongInputLoader({ searchQuery: query }); + return data({ - recommendedUsers, - searchResults, - query: handle, + query, + songs, }); }); diff --git a/app/features/search/pages/search.module.scss b/app/features/search/pages/search.module.scss index 7f3ffcb..5fecb89 100644 --- a/app/features/search/pages/search.module.scss +++ b/app/features/search/pages/search.module.scss @@ -1,73 +1,52 @@ @use 'styles/layout' as *; -.wrapper { - padding: 2rem 1rem; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +.root { + padding: 0 0 10px; } -.searchContainer { +.pageRow { + width: 100%; display: flex; - gap: 0.5rem; - margin-bottom: 2.5rem; - margin-top: 2rem; - align-items: center; + justify-content: center; } -.searchInputWrapper { - position: relative; - flex-grow: 1; - - svg { - position: absolute; - left: 0.8rem; - top: 50%; - transform: translateY(-50%); - color: #adb5bd; - width: 18px; - height: 18px; - } -} +.side { + display: flex; + justify-content: center; + align-items: flex-start; + padding: 14px 10px; -.searchInput { - width: 93%; - height: 39px; - border-radius: 8px; - border: 1px solid #dee2e6; - padding: 0.5rem 1rem 0.5rem 2.5rem; - font-size: 0.95rem; - transition: - border-color 0.2s, - box-shadow 0.2s; - - &:focus { - outline: none; - border-color: #4dabf7; - box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2); + position: sticky; + align-self: flex-start; + + @include tablet { + display: none; + } + @include mobile { + display: none; } } -.searchButton { - background-color: #4dabf7; - color: #fff; - border: none; - width: 65px; - height: 55px; - flex-shrink: 0; - border-radius: 8px; - cursor: pointer; - transition: background-color 0.2s; +.content { display: flex; - align-items: center; - justify-content: center; - - &:hover { - background-color: #339af0; - } + flex-direction: column; + width: 100%; + max-width: 800px; +} - svg { - width: 20px; - height: 20px; - } +.backButton { + position: static; + display: inline-flex; + display: inline-flex; + align-items: center; + height: 36px; + border: 1px solid white; + background: white; + color: #333; + font-weight: 600; + cursor: pointer; + color: $primary-500; + font-size: 18px; } .title { @@ -94,3 +73,63 @@ grid-template-columns: 1fr; } } + +.formRow { + display: flex; + gap: 8px; +} + +.songList { + margin: 10px 16px; + display: flex; + flex-direction: column; + gap: 12px; + padding: 0; +} + +.empty { + color: #9ca3af; +} + +.songItem { + display: flex; + gap: 16px; + align-items: center; + background-color: #fff; + border: 1px solid #e5e7eb; + border-radius: 6px; + cursor: pointer; + + &:hover { + background-color: #f9fafb; + } +} + +.cover { + width: 64px; + height: 64px; + object-fit: cover; + border-radius: 4px; + background-color: #f3f4f6; +} + +.texts { + overflow: hidden; +} + +.title { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 2px; +} + +.artist, +.album { + font-size: 12px; + color: #6b7280; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/app/features/search/pages/search.tsx b/app/features/search/pages/search.tsx index 91db424..725e86f 100644 --- a/app/features/search/pages/search.tsx +++ b/app/features/search/pages/search.tsx @@ -1,60 +1,64 @@ -import { useLoaderData, Form } from '@remix-run/react'; +import { Await, useNavigate, useLoaderData } from '@remix-run/react'; +import { Suspense } from 'react'; + +import { SearchBar } from 'app/components/SearchBar'; +import { MusicInfo } from 'app/external/music/IMusicSearchAPI'; +import SongItem, { type SimpleSong } from 'app/features/search/components/SongItem'; import styles from './search.module.scss'; -import { SearchIcon } from '../components/Icons'; -import { ProfileCard } from '../components/ProfileCard'; import { searchLoader } from '../loader'; -export default function SearchPage() { - const { recommendedUsers, searchResults, query } = useLoaderData(); +const PLACEHOLDER = '/images/features/profile/album_default2.png'; - const isSearchMode = !!query; +export default function SearchPage() { + const { query, songs } = useLoaderData(); + const navigate = useNavigate(); return ( -
-

Search

- -
-
- - -
- -
- -
+
+
+ + +
+ + ); +} - {isSearchMode ? ( -
-

{`🔍 검색 결과"${query}"`}

- {searchResults && searchResults.length > 0 ? ( -
- {searchResults.map((user) => ( - +const SearchedSongList = ({ songs }: { songs: Promise | undefined }) => { + return ( +
    + + + {(resolvedSongs) => ( + <> + {resolvedSongs?.length === 0 &&

    검색 결과가 없습니다.

    } + {resolvedSongs?.map((song, idx) => ( +
  • + }> + + {(albumCover) => { + const simple: SimpleSong = { + id: idx, + title: song.title, + artist: song.artist, + thumbnailUrl: albumCover ?? null, + album: song.album ?? null, + }; + return ; + }} + + +
  • ))} -
- ) : ( -

{`"${query}"에 대한 검색 결과가 없습니다.`}

+ )} -
- ) : ( -
-

✨ 오늘의 추천 프로필

-
- {recommendedUsers.map((user) => ( - - ))} -
-
- )} - + + + ); -} +}; diff --git a/app/routes/profile.$userId.addTodaySong.tsx b/app/routes/profile.$userId.addTodaySong.tsx index 71c1508..3307fa9 100644 --- a/app/routes/profile.$userId.addTodaySong.tsx +++ b/app/routes/profile.$userId.addTodaySong.tsx @@ -1,12 +1,12 @@ -import type { MetaFunction } from '@remix-run/node'; +// import type { MetaFunction } from '@remix-run/node'; -import { addTodaySongAction } from 'app/features/profile/action'; -import { addTodaySongLoader } from 'app/features/profile/loader'; -import AddTodaySongPage from 'app/features/profile/pages/addTodaySong'; +// import { addTodaySongAction } from 'app/features/profile/action'; +// import { addTodaySongLoader } from 'app/features/profile/loader'; +// import AddTodaySongPage from 'app/features/profile/pages/addTodaySong'; -export const meta: MetaFunction = () => [{ title: '오늘의 추천곡 수정' }]; +// export const meta: MetaFunction = () => [{ title: '오늘의 추천곡 수정' }]; -export const loader = addTodaySongLoader; -export const action = addTodaySongAction; +// export const loader = addTodaySongLoader; +// export const action = addTodaySongAction; -export default AddTodaySongPage; +// export default AddTodaySongPage;