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 App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
createRoot(document.getElementById('root')!).render(<App />)
|
||||
|
||||
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 styles from "./styles/index.module.scss"
|
||||
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() {
|
||||
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 (
|
||||
<div className={styles.page}>
|
||||
{/*공통 헤더 UI 부분*/}
|
||||
<CommonHeader />
|
||||
{/*공통 네비게이션 UI 부분*/}
|
||||
<CommonNav />
|
||||
<div className={styles.page__contents}>
|
||||
<div className={styles.page__contents__introBox}>
|
||||
<div className={styles.wrapper}>
|
||||
@ -17,12 +55,15 @@ function index() {
|
||||
모든 지역에 있는 크리에이터들의 지원을 받습니다.
|
||||
</span>
|
||||
{/*검색창 UI 부분*/}
|
||||
<CommonSearchBar />
|
||||
<CommonSearchBar />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.page__contents__imageBox}></div>
|
||||
<div className={styles.page__contents__imageBox}>
|
||||
{cardList}
|
||||
</div>
|
||||
</div>
|
||||
{/* 공통 푸터 UI 부분 */}
|
||||
<CommonFooter />
|
||||
</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