-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/#55 axios instance #56
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
Merged
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1b674d1
✨이미지업로드모달, 퀴즈모달 미리보기기
horororok 159443b
✨ 모달 공통 레이아웃 제작
horororok cc862d2
✨ 이미지 업로드 모달 컴포넌트 추가
horororok de99f31
✨ 위키 퀴즈 모달 컴포넌트 추가
horororok 7e8f22f
✨ 모달 컴포넌트에 필요한 아이콘 추가가
horororok 78fb2ee
✨ Disconnection modal 컴포넌트 추가
horororok 83e5a3e
✨ Unsaved changes modal 컴포넌트 추가
horororok aa2e57a
✨ 모달 컴포넌트 테스트 페이지지
horororok 84b2573
✨ 토큰 관리와 요청/응답 인터셉터가 포함된 커스텀 Axios 인스턴스 추가
horororok dec42f6
Merge branch 'feature/#55_Axios-Instance' of https://github.com/codei…
horororok 043c6be
Merge branch 'develop' into feature/#55_Axios-Instance
horororok File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import Modal from './Modal'; | ||
|
|
||
| interface DisconnectionModalProps { | ||
| isOpen: boolean; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| const DisconnectionModal = ({ isOpen, onClose }: DisconnectionModalProps) => { | ||
| return ( | ||
| <Modal isOpen={isOpen} onClose={onClose}> | ||
| <div className="flex flex-col gap-2"> | ||
| <p className="text-lg font-bold"> | ||
| 5분 이상 글을 쓰지 않아 접속이 끊어졌어요. | ||
| </p> | ||
| <p className="text-sm"> | ||
| 위키 참여하기를 통해 다시 위키를 수정해 주세요. | ||
| </p> | ||
| <div className="flex justify-end"> | ||
| <button | ||
| onClick={onClose} | ||
| className="mt-2 w-20 rounded-custom bg-green-200 px-6 py-2 text-background" | ||
| > | ||
| 확인 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </Modal> | ||
| ); | ||
| }; | ||
|
|
||
| export default DisconnectionModal; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| import Image from 'next/image'; | ||
| import { useEffect, useRef, useState } from 'react'; | ||
|
|
||
| import Modal from './Modal'; | ||
|
|
||
| interface ImageUploadModalProps { | ||
| isOpen: boolean; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| /** | ||
| * 이미지 업로드 모달 컴포넌트 | ||
| * @param isOpen 모달 오픈 여부 | ||
| * @param onClose 모달 닫기 함수 | ||
| * @returns ImageUploadModal 컴포넌트 | ||
| */ | ||
|
|
||
| const ImageUploadModal = ({ isOpen, onClose }: ImageUploadModalProps) => { | ||
| // input 요소를 참조하기 위한 ref | ||
| const fileInputRef = useRef<HTMLInputElement>(null); | ||
|
|
||
| // 선택한 이미지의 미리보기 url | ||
| const [previewUrl, setPreviewUrl] = useState<string | null>(null); | ||
|
|
||
| const [isDragging, setIsDragging] = useState(false); // 드래그 중인지 여부 | ||
|
|
||
| const [isUpload, setIsUpload] = useState(false); | ||
|
|
||
| // 카메라 아이콘 클릭시 input 요소 클릭이 되도록 | ||
| const handleCameraClick = () => { | ||
| fileInputRef.current?.click(); | ||
| }; | ||
|
|
||
| // 파일 선택시 | ||
| const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const file = e.target.files?.[0]; | ||
| if (file) { | ||
| // 파일을 url로 변환하여 미리보기 생성 | ||
| const imageUrl = URL.createObjectURL(file); | ||
| setPreviewUrl(imageUrl); | ||
| } | ||
| }; | ||
|
|
||
| // 드래그 관련 이벤트 | ||
| // 드래그 시작 | ||
| const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); // 이벤트 전파 중지 | ||
| setIsDragging(true); | ||
| }; | ||
| // 드래그가 영역을 벗어날 때 | ||
| const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setIsDragging(false); | ||
| }; | ||
| // 드래그 중 | ||
| const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| }; | ||
| // 드래그로 파일을 드랍했을 때 | ||
| const handleDrop = (e: React.DragEvent<HTMLDivElement>) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| setIsDragging(false); | ||
|
|
||
| const file = e.dataTransfer.files?.[0]; | ||
| if (file) { | ||
| const imageUrl = URL.createObjectURL(file); | ||
| setPreviewUrl(imageUrl); | ||
| } | ||
| }; | ||
|
|
||
| const handleImageUpload = async () => { | ||
| try { | ||
| setIsUpload(true); | ||
| // 이미지 업로그 api 통신 로직 | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
| setIsUpload(false); | ||
| alert('이미지 업로드 성공'); | ||
| onClose(); | ||
| } catch (error) { | ||
| console.error('이미지 업로드 error:', error); | ||
| alert('이미지 업로드 중 오류가 발생했습니다.'); | ||
| } finally { | ||
| setIsUpload(false); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| return () => { | ||
| // 이전 미리보기 url 정리 | ||
| if (previewUrl) { | ||
| URL.revokeObjectURL(previewUrl); | ||
| setPreviewUrl(null); | ||
| } | ||
| }; | ||
| }, [previewUrl, isOpen]); | ||
|
|
||
| return ( | ||
| <Modal isOpen={isOpen} onClose={onClose}> | ||
| <div className="flex flex-col gap-4"> | ||
| <p className="text-lg font-bold">이미지</p> | ||
| <input | ||
| type="file" | ||
| ref={fileInputRef} | ||
| onChange={handleFileChange} | ||
| accept="image/*" | ||
| className="hidden" | ||
| /> | ||
|
|
||
| {previewUrl ? ( | ||
| <div className="relative h-40 w-full overflow-hidden rounded-lg"> | ||
| <Image | ||
| src={previewUrl} | ||
| alt="선택된 이미지" | ||
| layout="fill" // 부모 요소 크기에 맞게 | ||
| objectFit="contain" // 비율 유지 | ||
| /> | ||
| </div> | ||
| ) : ( | ||
| <div | ||
| onClick={handleCameraClick} | ||
| onDragEnter={handleDragEnter} | ||
| onDragLeave={handleDragLeave} | ||
| onDragOver={handleDragOver} | ||
| onDrop={handleDrop} | ||
| className={`flex h-40 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed ${ | ||
| isDragging | ||
| ? 'border-green-400 bg-green-50' | ||
| : 'border-gray-300 bg-gray-100 hover:border-gray-400' | ||
| }`} | ||
| > | ||
| <Image | ||
| src="/icon/icon-camera.png" | ||
| alt="카메라 아이콘" | ||
| width={40} | ||
| height={40} | ||
| /> | ||
| <p className="text-sm text-gray-500"> | ||
| 클릭 또는 이미지를 드래그하여 올려주세요 | ||
| </p> | ||
| </div> | ||
| )} | ||
| <div className="flex justify-end"> | ||
| <button | ||
| className={`w-40 rounded-custom px-6 py-2 text-background ${ | ||
| previewUrl | ||
| ? 'cursor-pointer bg-green-200' | ||
| : 'cursor-not-allowed bg-gray-300' | ||
| }`} | ||
| disabled={!previewUrl || isUpload} | ||
| onClick={handleImageUpload} | ||
| > | ||
| {isUpload ? ( | ||
| <div className="flex items-center justify-center"> | ||
| <span className="mr-2">확인 중</span> | ||
| <svg | ||
| className="h-5 w-5 animate-spin" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| fill="none" | ||
| viewBox="0 0 24 24" | ||
| > | ||
| <circle | ||
| className="opacity-25" | ||
| cx="12" | ||
| cy="12" | ||
| r="10" | ||
| stroke="currentColor" | ||
| strokeWidth="4" | ||
| /> | ||
| <path | ||
| className="opacity-75" | ||
| fill="currentColor" | ||
| d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" | ||
| /> | ||
| </svg> | ||
| </div> | ||
| ) : ( | ||
| '삽입하기' | ||
| )} | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </Modal> | ||
| ); | ||
| }; | ||
|
|
||
| export default ImageUploadModal; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import Image from 'next/image'; | ||
| import { useEffect } from 'react'; | ||
|
|
||
| interface ModalProps { | ||
| isOpen: boolean; // 모달 오픈 여부 | ||
| onClose: () => void; // 모달 닫기 함수 | ||
| children: React.ReactNode; // 모달 내용 | ||
| width?: string; // 모달 너비 | ||
| } | ||
|
|
||
| /** | ||
| * 모달 공통 레이아웃 컴포넌트 | ||
| * @param isOpen 모달 오픈 여부 | ||
| * @param onClose 모달 닫기 함수 | ||
| * @param children 모달 내용 | ||
| * @param width 모달 너비 | ||
| * @returns Modal 컴포넌트 | ||
| */ | ||
|
|
||
| const Modal = ({ | ||
| isOpen, | ||
| onClose, | ||
| children, | ||
| width = 'w-11/12 ta:w-3/4 pc:w-1/2', | ||
| }: ModalProps) => { | ||
| // 모달이 닫혀있으면 null 반환 | ||
| if (!isOpen) return null; | ||
|
|
||
| // 배경 클릭시 모달 닫기 | ||
| const handleBackGroundClick = (e: React.MouseEvent) => { | ||
| if (e.target === e.currentTarget) { | ||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| // esc 키로 모달 닫기 | ||
| useEffect(() => { | ||
| const handleEsc = (e: KeyboardEvent) => { | ||
| if (e.key === 'Escape') { | ||
| onClose(); | ||
| } | ||
| }; | ||
| window.addEventListener('keydown', handleEsc); | ||
| return () => window.removeEventListener('keydown', handleEsc); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div | ||
| className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" | ||
| onClick={handleBackGroundClick} | ||
| > | ||
| <div | ||
| className={`${width} relative max-h-[90vh] overflow-y-auto rounded-lg bg-white shadow-xl`} | ||
| > | ||
| {/* 닫기 버튼 영역 */} | ||
| <div | ||
| className="absolute right-0 top-0 m-2 cursor-pointer" | ||
| onClick={onClose} | ||
| > | ||
| <Image | ||
| src="/icon/icon-close.svg" | ||
| alt="닫기 아이콘" | ||
| width={22} | ||
| height={22} | ||
| /> | ||
| </div> | ||
| {/* 컨텐츠 영역 */} | ||
| <div className="p-6">{children}</div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Modal; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import Modal from './Modal'; | ||
|
|
||
| interface UnsavedChangesModalProps { | ||
| isOpen: boolean; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| const UnsavedChangesModal = ({ isOpen, onClose }: UnsavedChangesModalProps) => { | ||
| return ( | ||
| <Modal isOpen={isOpen} onClose={onClose}> | ||
| <div className="flex flex-col gap-2"> | ||
| <p className="text-lg font-bold">저장하지 않고 나가시겠어요?</p> | ||
| <p className="text-sm">작성하신 모든 내용이 사라집니다.</p> | ||
| <div className="flex justify-end"> | ||
| <button | ||
| onClick={onClose} | ||
| className="mt-2 w-40 rounded-custom bg-red-100 px-6 py-2 text-background" | ||
| > | ||
| 페이지 나가기 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </Modal> | ||
| ); | ||
| }; | ||
|
|
||
| export default UnsavedChangesModal; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.