Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[조현지] sprint10 #625

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1e8e548
Refactor: 재사용하지 않는 컴포넌트 파일 삭제 및 합침
cindycho0423 Jun 4, 2024
70d5c7b
Remove: 사용하지 않는 리셋 css 삭제
cindycho0423 Jun 4, 2024
2bcf067
Refactor: 이미지 파일들 위치 변경 및 네이밍 변경
cindycho0423 Jun 4, 2024
2f49f38
Refactor: 날짜 포맷 변경 함수 utils로 분리
cindycho0423 Jun 4, 2024
6a79bba
Refactor: type 파일 생성 및 페이지별로 변경
cindycho0423 Jun 4, 2024
80f4b4f
Refactor: 조언 관련 수정
cindycho0423 Jun 4, 2024
b04a38c
Refactor: 함수 형태 변경
cindycho0423 Jun 4, 2024
20621c9
Chore: 이미지 파일들 추가
cindycho0423 Jun 6, 2024
b0857bf
Chore: 불필요한 파일 정리 및 설정 파일 정리
cindycho0423 Jun 7, 2024
535f1ed
Feat: api파일 생성
cindycho0423 Jun 7, 2024
ada1db1
Chore: 폴더 위치 변경으로 인한 임포트문 수정
cindycho0423 Jun 7, 2024
7e71b57
Feat: 게시글 상세 페이지 구현 및 컴포넌트 분리
cindycho0423 Jun 7, 2024
365c1d0
Feat: 랜딩페이지 및 푸터 가져옴
cindycho0423 Jun 7, 2024
a7f2f18
Feat: addboard 페이지 구현
cindycho0423 Jun 7, 2024
be0311d
Feat: 로그인 회원가입 페이지 생성
cindycho0423 Jun 7, 2024
9d9a3cd
Feat: 로그인 되었을 때 안되었을 때 네비게이션 변경
cindycho0423 Jun 7, 2024
c67e075
Feat: boards 페이지 폴더 생성 및 변경
cindycho0423 Jun 7, 2024
3b1f2a6
Feat: 페이지네이션 구현
cindycho0423 Jun 8, 2024
f31f1b1
Feat: 댓글창 무한 스크롤 구현
cindycho0423 Jun 8, 2024
67cd0b8
Remove: 불필요한 코드 삭제
cindycho0423 Jun 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions components/fileInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { MouseEvent, ChangeEvent, useEffect, useRef, useState } from 'react';
import deleteBtn from '@/public/images/icons/ic_X.svg';
import icPlus from '@/public/images/icons/ic_plus.svg';
import Image from 'next/image';

type Props = {
name: string;
value: File | string | null | Blob;
onChange: (name: string, value: File | null) => void;
};

export default function FileInput({ name, value, onChange }: Props) {
const [preview, setPreview] = useState<string>();
const inputRef = useRef<HTMLInputElement>(null);

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const nextValue = e.target.files?.[0];
if (nextValue) {
onChange(name, nextValue);
}
};

const handleClearClick = () => {
const inputNode = inputRef.current;
if (!inputNode) return;

inputNode.value = '';
onChange(name, null);
};

useEffect(() => {
if (!value) return;
const blob = typeof value === 'string' ? new Blob([value], { type: 'text/plain' }) : value;

const nextPreview = URL.createObjectURL(blob);
setPreview(nextPreview);

return () => {
URL.revokeObjectURL(nextPreview);
};
}, [value]);

const onClickImageUpload = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
inputRef.current?.click();
};
return (
<div className='flex flex-col gap-3'>
<p className='font-bold text-[14px] leading-4'>이미지</p>
<div className='relative flex gap-2'>
<input
type='file' //
accept='image/png, image/jpeg'
className='hidden'
ref={inputRef}
onChange={handleChange}
/>
<button
onClick={onClickImageUpload}
className='w-[168px] h-[168px] bg-cool-gray100 rounded-xl flex flex-col items-center justify-center'>
<Image src={icPlus} alt='더하기 아이콘' />
<span className='text-cool-gray400'>이미지 등록</span>
</button>
{value && <img src={preview} alt='이미지 미리보기' className='w-[168px] h-[168px] rounded-xl' />}
{value && (
<button className='absolute right-2 top-2 md:left-[313px] lg:left-[313px]' onClick={handleClearClick}>
<Image src={deleteBtn} width={24} alt='삭제버튼' />
</button>
)}
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions components/submit-btn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ReactNode } from 'react';

type ButtonProps = {
children: ReactNode;
checkValue: boolean;
cindycho0423 marked this conversation as resolved.
Show resolved Hide resolved
};

export default function SubmitButton({ children, checkValue }: ButtonProps) {
return (
<button className={`px-6 py-3 text-white rounded-lg ${checkValue ? 'bg-brand-blue' : 'bg-cool-gray400'}`}>
{children}
</button>
);
}
119 changes: 119 additions & 0 deletions pages/addboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { useState, useEffect, ChangeEvent, FormEvent, KeyboardEvent } from 'react';
import SubmitButton from '@/components/submit-btn';
import FileInput from '@/components/fileInput';
import { postArticle } from '@/lib/api/postArticle';
import { useRouter } from 'next/router';
import { postImageUrl } from '@/lib/api/postImageUrl';

type Values = {
imgFile: File | null;
title: string;
content: string;
};

export default function AddArticleForm() {
const router = useRouter();
const [token, setToken] = useState('');
useEffect(() => {
const localToken = typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null;
if (localToken) {
setToken(localToken);
}
}, []);

const [values, setValues] = useState<Values>({
imgFile: null,
title: '',
content: '',
});

const handleFileChange = (name: string, value: File | null) => {
setValues(prevValues => ({
...prevValues,
[name]: value,
}));
};

const handleChange = (name: string, value: string | null) => {
setValues(prevValues => ({
...prevValues,
[name]: value,
}));
};

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

try {
const { title, content, imgFile } = values;
let imageUrl;

if (imgFile) {
const formData = new FormData();
formData.append('image', imgFile);

const imageResponse = await postImageUrl(formData, token);
imageUrl = imageResponse.url;
}

const response = await postArticle(title, content, token, imageUrl);

if (response) {
router.push('/boards');
} else {
console.error('Error posting article');
}
} catch (error) {
console.error('Error posting article:', error);
}
};

const isFormComplete = values.title.trim() !== '' && values.content.trim() !== '';

const preventDefault = (e: KeyboardEvent<HTMLFormElement>) => {
e.key === 'Enter' && e.preventDefault();
};

return (
<form
className='flex-col pt-6 m-auto w-mobile md:w-tablet lg:w-desktop h-[78vh]'
action='submit'
onSubmit={handleSubmit}
onKeyDown={preventDefault}>
<div className='flex items-center justify-between'>
<h1>상품 등록하기</h1>
<SubmitButton checkValue={isFormComplete}>{'등록'}</SubmitButton>
</div>
<label htmlFor='title' className='font-bold text-[14px] leading-4'>
*제목
</label>
<input
id='title'
name='title'
value={values.title}
type='text'
placeholder='상품명을 입력해주세요'
onChange={e => handleChange('title', e.target.value)}
className='mt-3 mb-6 w-mobile md:w-tablet lg:w-desktop h-14'
/>

<label htmlFor='content' className='font-bold text-[14px] leading-4'>
*내용
</label>
<textarea
id='content'
name='content'
value={values.content}
placeholder='내용을 입력해주세요'
onChange={e => handleChange('content', e.target.value)}
className='mt-3 mb-6 w-mobile md:w-tablet lg:w-desktop h-[200px]'
/>

<FileInput
name='imgFile' //
value={values.imgFile}
onChange={handleFileChange}
/>
</form>
);
}