Skip to content

Commit

Permalink
feat : 아이템 뷰/추가/수정 페이지 실 API 연결 (#35)
Browse files Browse the repository at this point in the history
* feat: 아이템 페이지 실 API 연결

* feat: 아이템 수정/추가 페이지 실 API 연결

---------

Co-authored-by: Kong Chae Won (Chaewon Kong) <chaewonkong@gmail.com>
  • Loading branch information
bsy1141 and chaewonkong authored Aug 15, 2023
1 parent 9f2ceb4 commit b54cc55
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 257 deletions.
9 changes: 9 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'grafi-test-bucket.s3.ap-northeast-2.amazonaws.com',
port: '',
},
],
},
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
Expand Down
38 changes: 12 additions & 26 deletions src/components/user/item/ItemsSlide/ItemsSlide.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Carousel } from 'react-bootstrap';
import { Icon, ImageFrame } from '@/components/shared';
import { ImageFrame } from '@/components/shared';

interface ItemType {
id: number;
photo_cut_id: number;
title: string;
image: string;
description: string;
text: string;
}

interface ItemSlideProps {
Expand All @@ -24,38 +24,24 @@ export function ItemsSlide({
setActiveIndex(selectedIndex);
};

// useEffect(() => {
// require('bootstrap/dist/js/bootstrap.js');
// }, []);

// const goToNext = () => {
// const nextIndex = activeIndex === items.length - 1 ? 0 : activeIndex + 1;
// setActiveIndex(nextIndex);
// };
// const goToPrev = () => {
// const nextIndex = activeIndex === 0 ? items.length - 1 : activeIndex - 1;
// setActiveIndex(nextIndex);
// };

return (
<>
<Carousel
activeIndex={activeIndex}
onSelect={handleSelect}
controls={true}
indicators={false}
interval={null}
>
{items.map((item) => (
<Carousel.Item key={item.title}>
<ImageFrame alt={item.title} src={item.image} />
{items.map((item, idx) => (
<Carousel.Item key={item.photo_cut_id}>
<ImageFrame
alt={item.title}
src={item.image}
priority={idx === activeIndex}
/>
</Carousel.Item>
))}
{/* <button onClick={goToPrev} className='absolute left-4 top-1/2'>
<Icon iconType='LeftChevronBG' width='2rem' height='2rem' />
</button>
<button onClick={goToNext} className='absolute right-4 top-1/2'>
<Icon iconType='RightChevronBG' width='2rem' height='2rem' />
</button> */}
</Carousel>
</>
);
Expand Down
5 changes: 4 additions & 1 deletion src/pages/api/films/[film_id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { HttpMethod } from '@/constants/httpMethods';
import { AxiosError } from 'axios';
import { instance } from '@/utils/axiosInstance';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const url = `/films`;
const film_id = req.query.film_id;

Expand Down
1 change: 1 addition & 0 deletions src/pages/api/users/[user_id]/visit-logs/[log_id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default async function handler(
const user_id = req.query.user_id;
const log_id = req.query.log_id;
const url = `/users/${user_id}/visit-logs/${log_id}`;

try {
if (req.method === HttpMethod.DELETE) {
const result = await instance.delete(url);
Expand Down
12 changes: 6 additions & 6 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ import { ItemsSlide } from '@/components/user/item/ItemsSlide';
// NOTE : 온보딩 페이지
const items = [
{
id: 1,
photo_cut_id: 1,
title: '',
image: '/images/onboarding1.png',
description: '',
text: '',
},
{
id: 2,
photo_cut_id: 2,
title: '',
image: '/images/onboarding2.png',
description: '',
text: '',
},
{
id: 3,
photo_cut_id: 3,
title: '',
image: '/images/onboarding3.png',
description: '',
text: '',
},
];
export default function Home() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import { useRouter } from 'next/router';
import { useRef, useState } from 'react';
import { convertImageToBase64 } from '@/utils';
import { Icon, ImageFrame, Input, TextButton, Textarea } from '@/components/shared';
import { isString } from '@/utils/type-util';
import { useEffect, useRef, useState } from 'react';
import { imagesApis } from '@/query-hooks/useImages';
import { useCreatePhotoCut } from '@/query-hooks/usePhotoCuts';
import {
Icon,
ImageFrame,
Input,
TextButton,
Textarea,
} from '@/components/shared';

export default function AddPage() {
const router = useRouter();
const { id, title } = router.query;

useEffect(() => {
const user_id = localStorage.getItem('userId');
if (!user_id) router.push('/');
}, []);

const { id, filmId } = router.query;

const inputRef = useRef<HTMLInputElement>(null);
const [image, setImage] = useState<string | null>(null);

const [title, setTitle] = useState('');
const [text, setText] = useState('');

const createPhotoCut = useCreatePhotoCut();

const handleClick = () => {
if (inputRef.current) {
inputRef.current.click();
Expand All @@ -20,25 +37,39 @@ export default function AddPage() {
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files !== null) {
const file = e.target.files[0];
const convertedImage = await convertImageToBase64(file);
if (typeof convertedImage === 'string') {
setImage(convertedImage);
const { image_url, presigned_url } = await imagesApis.getPresignedUrl(
file.name,
);
await imagesApis.uploadFile(presigned_url, file);

if (typeof image_url === 'string') {
setImage(image_url);
}
// Upload API 연결
}
};

const handleSubmit = () => {
createPhotoCut.mutate(
{
film_id: Number(filmId),
title: String(title),
text,
image: String(image),
},
{
onSuccess: () => router.push(`/user/${id}/${filmId}/item`),
},
);
};

return (
<div className='tw-h-[100vh] tw-w-full tw-bg-white'>
{/** Header */}
<div className='tw-flex tw-justify-between tw-p-2.5'>
<TextButton color='danger' onClick={router.back}>
취소
</TextButton>
<TextButton
color='primary'
onClick={() => router.push(`/user/${id}/item`)}
>
<TextButton color='primary' onClick={handleSubmit}>
저장
</TextButton>
</div>
Expand All @@ -65,9 +96,15 @@ export default function AddPage() {

{/** 본문 입력 영역 */}
<div className='tw-flex tw-flex-col tw-gap-3.5 tw-px-5 tw-py-6'>
<Input placeholder='제목을 입력해주세요.' value={isString(title) ? title : ''} readOnly />
<Textarea placeholder='설명을 입력해주세요.' rows={3} />
{/* <Input placeholder='링크를 입력해주세요.(선택)' /> */}
<Input
placeholder='제목을 입력해주세요.'
onValueChange={(title) => setTitle(title)}
/>
<Textarea
placeholder='설명을 입력해주세요.'
rows={3}
onValueChange={(text) => setText(text)}
/>
</div>
</div>
);
Expand Down
127 changes: 127 additions & 0 deletions src/pages/user/[id]/[filmId]/item/edit/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { GetServerSideProps } from 'next';
import { useRouter } from 'next/router';
import { useRef, useState } from 'react';
import { QueryClient, dehydrate } from '@tanstack/react-query';
import { imagesApis } from '@/query-hooks/useImages';
import { useEditPhotoCut, useGetPhotoCut } from '@/query-hooks/usePhotoCuts';
import photoCutsApis from '@/query-hooks/usePhotoCuts/apis';
import photoCutsKeys from '@/query-hooks/usePhotoCuts/keys';
import {
Button,
Icon,
ImageFrame,
Input,
TextButton,
Textarea,
} from '@/components/shared';

export default function EditPage() {
const router = useRouter();
const { id, filmId, cutId } = router.query;
const { data: item } = useGetPhotoCut(Number(cutId));

const inputRef = useRef<HTMLInputElement>(null);
const [image, setImage] = useState<string>(item.image);

const [title, setTitle] = useState(item.title);
const [text, setText] = useState(item.text);

const editPhotoCut = useEditPhotoCut();

const handleClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};

const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files !== null) {
const file = e.target.files[0];

const { image_url, presigned_url } = await imagesApis.getPresignedUrl(
file.name,
);
await imagesApis.uploadFile(presigned_url, file);

if (typeof image_url === 'string') {
setImage(image_url);
}
}
};

const handleSubmit = () => {
editPhotoCut.mutate(
{
photo_cut_id: item.photo_cut_id,
title,
text,
image,
},
{
onSuccess: () => router.push(`/user/${id}/${filmId}/item`),
},
);
};

return (
<div className='tw-h-[100vh] tw-w-full tw-bg-white'>
{/** Header */}
<div className='tw-flex tw-justify-between tw-p-2.5'>
<TextButton color='secondary' onClick={router.back}>
취소
</TextButton>
<TextButton color='primary' onClick={handleSubmit}>
저장
</TextButton>
</div>

{/** 이미지 영역 */}
<div className='tw-relative'>
<ImageFrame alt='item image' src={image} className='aspect-[3/4]' />
<input
type='file'
accept='image/*'
className='tw-hidden'
ref={inputRef}
onChange={handleFileUpload}
/>
<Button
variant='rounded'
onClick={handleClick}
className='tw-absolute tw-bottom-3.5 tw-right-5 tw-h-12 tw-w-12 tw-bg-white'
>
<Icon iconType='Camera' width={32} height={32} />
</Button>
</div>

{/** 본문 입력 영역 */}
<div className='tw-flex tw-flex-col tw-gap-3.5 tw-px-5 tw-py-6'>
<Input
placeholder='제목을 입력해주세요.'
defaultValue={title}
onValueChange={(title) => setTitle(title)}
/>
<Textarea
placeholder='설명을 입력해주세요.'
defaultValue={text}
onValueChange={(text) => setText(text)}
/>
</div>
</div>
);
}

export const getServerSideProps: GetServerSideProps = async ({ query }) => {
const cutId: string = query.cutId as string;
const queryClient = new QueryClient();

await queryClient.prefetchQuery(photoCutsKeys.item(Number(cutId)), () =>
photoCutsApis.getPhotoCut(Number(cutId)),
);

return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
};
Loading

0 comments on commit b54cc55

Please sign in to comment.