Unsplash Photo API 연동
This commit is contained in:
parent
5481e62a38
commit
dd689b97b6
3
src/assets/icons/icon-arrowLeft.svg
Normal file
3
src/assets/icons/icon-arrowLeft.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15 19.92L8.48 13.4C7.71 12.63 7.71 11.37 8.48 10.6L15 4.07999" stroke="#98A2B3" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 284 B |
3
src/assets/icons/icon-arrowRight.svg
Normal file
3
src/assets/icons/icon-arrowRight.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.91 19.92L15.43 13.4C16.2 12.63 16.2 11.37 15.43 10.6L8.91 4.07999" stroke="#98A2B3" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 290 B |
65
src/components/common/footer/CommonFooter.module.scss
Normal file
65
src/components/common/footer/CommonFooter.module.scss
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
border: 1px solid $color-gray-100;
|
||||||
|
background-color: $color-white-000;
|
||||||
|
}
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
|
||||||
|
padding: 3px 7px;
|
||||||
|
|
||||||
|
color: $color-gray-500;
|
||||||
|
|
||||||
|
font-family: 'Public Sans', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 16px;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-gray-100;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
color: $color-black-900;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
background-color: $color-gray-100;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
color: $color-black-900;
|
||||||
|
}
|
||||||
|
&.inactive {
|
||||||
|
background-color: $color-white-000;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
color: $color-black-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/components/common/footer/CommonFooter.tsx
Normal file
85
src/components/common/footer/CommonFooter.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// import { useEffect, useState } from 'react'
|
||||||
|
// import { useRecoilState, useRecoilValue, useRecoilValueLoadable } from 'recoil'
|
||||||
|
// import { imageData } from '@/recoil/selectors/imageSelector'
|
||||||
|
// import { pageState } from '@/recoil/atoms/pageState'
|
||||||
|
import styles from './CommonFooter.module.scss'
|
||||||
|
// import { searchState } from '@/recoil/atoms/searchState'
|
||||||
|
import IconImageLeft from '@/assets/icons/icon-arrowLeft.svg'
|
||||||
|
import IconImageRight from '@/assets/icons/icon-arrowRight.svg'
|
||||||
|
|
||||||
|
function CommonFooter() {
|
||||||
|
// const imgSelector = useRecoilValueLoadable(imageData)
|
||||||
|
// const search = useRecoilValue(searchState)
|
||||||
|
// const [page, setPage] = useRecoilState(pageState)
|
||||||
|
// const [step, setStep] = useState(0)
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// setStep(0)
|
||||||
|
// }, [search])
|
||||||
|
|
||||||
|
// // 페이지 리스트 UI 생성
|
||||||
|
// const newArr: number[] = new Array()
|
||||||
|
// for (let i = 1; i <= imgSelector.contents.total_pages; i++) {
|
||||||
|
// newArr.push(i)
|
||||||
|
// }
|
||||||
|
// const length = newArr.length
|
||||||
|
// const divide = Math.floor(length / 10) + (Math.floor(length % 10) > 0 ? 1 : 0)
|
||||||
|
// const res = []
|
||||||
|
|
||||||
|
// for (let i = 0; i <= divide; i++) {
|
||||||
|
// // 배열 0부터 n개씩 잘라 새 배열에 넣기
|
||||||
|
// res.push(newArr.splice(0, 10))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // ----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// const moveToPage = (selected: number) => {
|
||||||
|
// setPage(selected)
|
||||||
|
// }
|
||||||
|
// const moveToPrev = () => {
|
||||||
|
// if (step === 0) return
|
||||||
|
// else {
|
||||||
|
// setStep(step - 1)
|
||||||
|
// setPage(res[step - 1][0])
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// const moveToNext = () => {
|
||||||
|
// if (step < res[step].length - 2) {
|
||||||
|
// setStep(step + 1)
|
||||||
|
// setPage(res[step + 1][0])
|
||||||
|
// } else return
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
<div className={styles.pagination}>
|
||||||
|
<button className={styles.pagination__button} /*onClick={moveToPrev}*/>
|
||||||
|
<img src={IconImageLeft} alt="IconImageLeft" />
|
||||||
|
</button>
|
||||||
|
{/* 변경될 UI 부분 */}
|
||||||
|
<span>1</span>
|
||||||
|
{/* {res[step] &&
|
||||||
|
res[step].map((item: number, index: number) => {
|
||||||
|
if (item < 11) {
|
||||||
|
return (
|
||||||
|
<button className={index === page - 1 ? `${styles.pagination__button} ${styles.active}` : `${styles.pagination__button} ${styles.inactive}`} key={item} onClick={() => moveToPage(item)}>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<button className={index === page - 1 - step * 10 ? `${styles.pagination__button} ${styles.active}` : `${styles.pagination__button} ${styles.inactive}`} key={item} onClick={() => moveToPage(item)}>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})} */}
|
||||||
|
<button className={styles.pagination__button} /*onClick={moveToNext}*/>
|
||||||
|
<img src={IconImageRight} alt="IconImageRight" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommonFooter
|
||||||
47
src/components/common/navigation/CommanNav.tsx
Normal file
47
src/components/common/navigation/CommanNav.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link, useLocation } from 'react-router-dom'
|
||||||
|
import styles from './CommonNav.module.scss'
|
||||||
|
import navJson from './nav.json'
|
||||||
|
// import { useRecoilState } from 'recoil'
|
||||||
|
// import { pageState } from '@/recoil/atoms/pageState'
|
||||||
|
// import { searchState } from '@/recoil/atoms/searchState'
|
||||||
|
|
||||||
|
interface Navigation {
|
||||||
|
index: number
|
||||||
|
path: string
|
||||||
|
label: string
|
||||||
|
searchValue: string
|
||||||
|
isActive: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommonNav() {
|
||||||
|
const location = useLocation()
|
||||||
|
const [navigation, setNavigation] = useState<Navigation[]>(navJson)
|
||||||
|
// const [, setPage] = useRecoilState(pageState)
|
||||||
|
// const [, setSearch] = useRecoilState(searchState)
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// navigation.forEach((nav: Navigation) => {
|
||||||
|
// nav.isActive = false
|
||||||
|
|
||||||
|
// if (nav.path === location.pathname || location.pathname.includes(nav.path)) {
|
||||||
|
// nav.isActive = true
|
||||||
|
// setSearch(nav.searchValue)
|
||||||
|
// setPage(1)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// setNavigation([...navigation])
|
||||||
|
// }, [location.pathname])
|
||||||
|
|
||||||
|
// useState로 선언한 반응성을 가진 데이터를 기반으로 UI를 반복호출해보도록 한다.
|
||||||
|
const navLinks = navigation.map((item: Navigation) => {
|
||||||
|
return (
|
||||||
|
<Link to={item.path} className={item.isActive ? `${styles.navigation__menu} ${styles.active}` : `${styles.navigation__menu} ${styles.inactive}`} key={item.path}>
|
||||||
|
<span className={styles.navigation__menu__label}>{item.label}</span>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return <nav className={styles.navigation}>{navLinks}</nav>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommonNav
|
||||||
37
src/components/common/navigation/CommonNav.module.scss
Normal file
37
src/components/common/navigation/CommonNav.module.scss
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
.navigation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
gap: 32px;
|
||||||
|
|
||||||
|
border-bottom: 1px solid $color-white-300;
|
||||||
|
|
||||||
|
&__menu {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
text-decoration: none; // 추후에 div 태그를 React Router 속성 중 Link 태그로 바꿀 것이기 때문입니다.
|
||||||
|
color: $color-white-700;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
color: $color-white-700;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
color: $color-black-900;
|
||||||
|
border-bottom: 2px solid $color-white-700;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: $color-black-900;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/components/common/navigation/nav.json
Normal file
86
src/components/common/navigation/nav.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"index": 0,
|
||||||
|
"path": "/search/edit",
|
||||||
|
"label": "보도/편집 전용",
|
||||||
|
"searchValue": "edit",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"path": "/search/following",
|
||||||
|
"label": "팔로잉",
|
||||||
|
"searchValue": "following",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"path": "/search/photoPlus",
|
||||||
|
"label": "Unsplash Photo+",
|
||||||
|
"searchValue": "photo",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 3,
|
||||||
|
"path": "/search/oneColor",
|
||||||
|
"label": "단색",
|
||||||
|
"searchValue": "one color",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 4,
|
||||||
|
"path": "/search/3dRender",
|
||||||
|
"label": "3D 렌더링",
|
||||||
|
"searchValue": "3d rendering",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 5,
|
||||||
|
"path": "/search/nature",
|
||||||
|
"label": "자연",
|
||||||
|
"searchValue": "nature",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 6,
|
||||||
|
"path": "/search/texture",
|
||||||
|
"label": "텍스쳐 및 패턴",
|
||||||
|
"searchValue": "texture",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 7,
|
||||||
|
"path": "/search/interior",
|
||||||
|
"label": "인테리어",
|
||||||
|
"searchValue": "interior",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 8,
|
||||||
|
"path": "/search/film",
|
||||||
|
"label": "필름",
|
||||||
|
"searchValue": "film",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 9,
|
||||||
|
"path": "/search/experimental",
|
||||||
|
"label": "실험적인",
|
||||||
|
"searchValue": "experimental",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 10,
|
||||||
|
"path": "/search/travel",
|
||||||
|
"label": "여행",
|
||||||
|
"searchValue": "travel",
|
||||||
|
"isActive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 11,
|
||||||
|
"path": "/search/sports",
|
||||||
|
"label": "스포츠",
|
||||||
|
"searchValue": "sports",
|
||||||
|
"isActive": false
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,9 +1,5 @@
|
|||||||
import { StrictMode } from 'react'
|
// import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(<App />)
|
||||||
<StrictMode>
|
|
||||||
<App />
|
|
||||||
</StrictMode>,
|
|
||||||
)
|
|
||||||
|
|||||||
22
src/pages/index/components/Card.module.scss
Normal file
22
src/pages/index/components/Card.module.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
.card {
|
||||||
|
width: 260px;
|
||||||
|
height: 260px;
|
||||||
|
|
||||||
|
background-color: $color-gray-200;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@media (min-width: 1024px) and (max-width: 1440px) {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
border-radius: 6px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/pages/index/components/Card.tsx
Normal file
25
src/pages/index/components/Card.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { CardDTO } from '../types/card'
|
||||||
|
import styles from './Card.module.scss'
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: CardDTO
|
||||||
|
handleDialog: (eventValue: boolean) => void
|
||||||
|
handleSetData: (eventValue: CardDTO) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function Card({ data/*, handleDialog, handleSetData */}: Props) {
|
||||||
|
const openDialog = () => {
|
||||||
|
console.log('함수호출')
|
||||||
|
// handleDialog(true)
|
||||||
|
// handleSetData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.card} onClick={openDialog}>
|
||||||
|
<img src={data.urls.small} alt={data.alt_description} className={styles.card__image} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card
|
||||||
@ -1,13 +1,51 @@
|
|||||||
import CommonHeader from "@/components/common/header/CommonHeader"
|
import CommonHeader from "@/components/common/header/CommonHeader"
|
||||||
import styles from "./styles/index.module.scss"
|
|
||||||
import CommonSearchBar from "@/components/common/searchBar/CommonSearchBar"
|
import CommonSearchBar from "@/components/common/searchBar/CommonSearchBar"
|
||||||
|
import CommonNav from "@/components/common/navigation/CommanNav"
|
||||||
|
import CommonFooter from "@/components/common/footer/CommonFooter"
|
||||||
|
import Card from "./components/Card"
|
||||||
|
import styles from "./styles/index.module.scss"
|
||||||
|
import axios from "axios"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import type { CardDTO } from "./types/card"
|
||||||
|
|
||||||
function index() {
|
function index() {
|
||||||
|
const [imgUrls, setImgUrls] = useState([])
|
||||||
|
|
||||||
|
const getData = async() => {
|
||||||
|
// Open API 호출
|
||||||
|
const API_URL = 'https://api.unsplash.com/search/photos'
|
||||||
|
const API_KEY = 'nloztIOd94-5EH6vAJUU3L66l79z9Dv2KwwbKHShAnY'
|
||||||
|
const PER_PAGE = 30
|
||||||
|
|
||||||
|
const searchValue = 'Korea'
|
||||||
|
const pageValue = 100
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${API_URL}?query=${searchValue}&client_id=${API_KEY}&page=${pageValue}&per_page=${PER_PAGE}`)
|
||||||
|
console.log(res)
|
||||||
|
if(res.status === 200) {
|
||||||
|
setImgUrls(res.data.results)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardList = imgUrls.map((card: CardDTO) => {
|
||||||
|
return <Card data={card} key={card.id} />
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
getData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
{/*공통 헤더 UI 부분*/}
|
{/*공통 헤더 UI 부분*/}
|
||||||
<CommonHeader />
|
<CommonHeader />
|
||||||
{/*공통 네비게이션 UI 부분*/}
|
{/*공통 네비게이션 UI 부분*/}
|
||||||
|
<CommonNav />
|
||||||
<div className={styles.page__contents}>
|
<div className={styles.page__contents}>
|
||||||
<div className={styles.page__contents__introBox}>
|
<div className={styles.page__contents__introBox}>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
@ -17,12 +55,15 @@ function index() {
|
|||||||
모든 지역에 있는 크리에이터들의 지원을 받습니다.
|
모든 지역에 있는 크리에이터들의 지원을 받습니다.
|
||||||
</span>
|
</span>
|
||||||
{/*검색창 UI 부분*/}
|
{/*검색창 UI 부분*/}
|
||||||
<CommonSearchBar />
|
<CommonSearchBar />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.page__contents__imageBox}></div>
|
<div className={styles.page__contents__imageBox}>
|
||||||
|
{cardList}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* 공통 푸터 UI 부분 */}
|
{/* 공통 푸터 UI 부분 */}
|
||||||
|
<CommonFooter />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/pages/index/types/card.ts
Normal file
53
src/pages/index/types/card.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
export interface CardDTO {
|
||||||
|
alt_description: string
|
||||||
|
blur_hash: string
|
||||||
|
breadcrumbs: []
|
||||||
|
color: string
|
||||||
|
created_at: string
|
||||||
|
current_user_collections: []
|
||||||
|
description: string
|
||||||
|
height: number
|
||||||
|
id: string
|
||||||
|
liked_by_user: boolean
|
||||||
|
likes: number
|
||||||
|
links: Link
|
||||||
|
promoted_at?: string
|
||||||
|
slug: string
|
||||||
|
sponsorship?: string
|
||||||
|
tags: Tag[]
|
||||||
|
topic_submissions: any
|
||||||
|
updated_at: string
|
||||||
|
urls: Url
|
||||||
|
user: any
|
||||||
|
width: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Link {
|
||||||
|
download: string
|
||||||
|
download_location: string
|
||||||
|
html: string
|
||||||
|
self: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tag {
|
||||||
|
source: {
|
||||||
|
ancestry: any
|
||||||
|
cover_photo: any
|
||||||
|
description: string
|
||||||
|
meta_description: string
|
||||||
|
meta_title: string
|
||||||
|
subtitle: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Url {
|
||||||
|
full: string
|
||||||
|
raw: string
|
||||||
|
regular: string
|
||||||
|
small: string
|
||||||
|
small_s3: string
|
||||||
|
thumb: string
|
||||||
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.app.json"
|
"extends": "./tsconfig.app.json"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user