-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 스토리 등록 구현 (#84) #100
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 all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f829247
feat(#84): cancel icon 추가
wkdtnqls0506 7d266f7
feat(#84): 이미지 업로드 Context 추가
wkdtnqls0506 55c15a4
feat(#84): 스토리 등록 페이지 생성
wkdtnqls0506 2f39e74
feat(#84): 스토리 등록 폼 검증 스키마 추가
wkdtnqls0506 195e7ad
feat(#84): 스토리 등록 API 및 요청/응답 타입 생성
wkdtnqls0506 086430e
feat(#84): StoryImagePreview 컴포넌트 생성
wkdtnqls0506 42073fa
feat(#84): StoryDescription 컴포넌트 생성
wkdtnqls0506 18d5c0f
feat(#84): StorySearchStore 컴포넌트 생성
wkdtnqls0506 c587ee6
feat(#84): StorySubmitButton 컴포넌트 생성
wkdtnqls0506 88d9b58
feat(#84): 홈화면에 Story 컴포넌트 추가
wkdtnqls0506 b274aed
chore(#84): 임시 스토리 상세페이지 생성
wkdtnqls0506 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,52 @@ | ||
| "use client"; | ||
|
|
||
| import { useRouter } from "next/navigation"; | ||
| import { useRef } from "react"; | ||
|
|
||
| import { useImageUploadContext } from "@/app/story/register/_contexts"; | ||
| import { imageFileSchema } from "@/app/story/register/_schemas"; | ||
|
|
||
| export const Story = () => { | ||
| const router = useRouter(); | ||
| const { setUpload } = useImageUploadContext(); | ||
| const fileInputRef = useRef<HTMLInputElement | null>(null); | ||
|
|
||
| const handleOpenPhotoGallery = () => { | ||
| fileInputRef.current?.click(); | ||
| }; | ||
|
|
||
| const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const file = e.target.files?.[0]; | ||
| if (!file) return; | ||
|
|
||
| const validationResult = imageFileSchema.safeParse(file); | ||
|
|
||
| if (!validationResult.success) { | ||
| const errorMessage = validationResult.error.errors[0]?.message; | ||
| // TODO: Toast 변경 | ||
| alert(errorMessage || "올바르지 않은 파일입니다."); | ||
|
|
||
| if (fileInputRef.current) { | ||
| fileInputRef.current.value = ""; | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| const previewUrl = URL.createObjectURL(file); | ||
| setUpload(file, previewUrl); | ||
|
|
||
| router.push("/story/register"); | ||
| }; | ||
| return ( | ||
| <> | ||
| <button onClick={handleOpenPhotoGallery}>스토리 사진 선택</button> | ||
| <input | ||
| ref={fileInputRef} | ||
| type='file' | ||
| accept='image/jpeg,image/jpg,image/png' | ||
| onChange={handleFileChange} | ||
| hidden | ||
| /> | ||
| </> | ||
| ); | ||
| }; | ||
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 @@ | ||
| export { Story } from "./Story"; |
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 |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| export { RecentCheers } from "./RecentCheers"; | ||
| export { RecentlySupportedStores } from "./RecentlySupportStories"; | ||
| export { StoreStory } from "./StoreStory"; | ||
| export { Story } from "./Story"; |
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 |
|---|---|---|
| @@ -1,9 +1,32 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
|
|
||
| import LogoWordmarkIcon from "@/assets/logo-wordmark.svg"; | ||
| import { Button } from "@/components/ui/Button"; | ||
| import { GNB } from "@/components/ui/GNB"; | ||
|
|
||
| import * as styles from "./layout.css"; | ||
|
|
||
| export default function MainLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| return <main className={styles.mainContainer}>{children}</main>; | ||
| return ( | ||
| <> | ||
| <GNB | ||
| leftAddon={<LogoWordmarkIcon width={46} height={24} />} | ||
| align='left' | ||
| rightAddon={ | ||
| <Link href='/login'> | ||
| <Button variant='primary' size='small' style={{ width: "6.3rem" }}> | ||
| 로그인 | ||
| </Button> | ||
| </Link> | ||
| } | ||
| /> | ||
| <main className={styles.mainContainer}>{children}</main> | ||
| </> | ||
| ); | ||
| } |
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
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
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,3 @@ | ||
| export default function StoryIdPage() { | ||
| return <div>StoryIdPage</div>; | ||
| } |
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,3 @@ | ||
| export * from "./register.api"; | ||
| export * from "./register.queries"; | ||
| export * from "./register.types"; |
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,33 @@ | ||
| import { authHttp } from "@/lib/api"; | ||
|
|
||
| import { | ||
| type StoryRegisterRequest, | ||
| type StoryRegisterResponse, | ||
| } from "./register.types"; | ||
|
|
||
| /** | ||
| * 스토리 등록 API | ||
| * @param {StoryRegisterRequest} storyRequest - 스토리 등록 요청 데이터 | ||
| * @param {File} imageFile - 업로드할 이미지 파일 | ||
| * | ||
| * @returns {Promise<StoryRegisterResponse>} 등록된 스토리 ID 반환 | ||
| */ | ||
| export const postStory = async ( | ||
| storyRequest: StoryRegisterRequest, | ||
| imageFile: File | ||
| ): Promise<StoryRegisterResponse> => { | ||
| const formData = new FormData(); | ||
|
|
||
| formData.append( | ||
| "request", | ||
| new Blob([JSON.stringify(storyRequest)], { type: "application/json" }) | ||
| ); | ||
|
|
||
| formData.append("image", imageFile); | ||
|
|
||
| return await authHttp | ||
| .post("api/stories", { | ||
| body: formData, | ||
| }) | ||
| .json<StoryRegisterResponse>(); | ||
| }; |
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,30 @@ | ||
| import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||
|
|
||
| import { postStory } from "./register.api"; | ||
| import type { StoryRegisterRequest } from "./register.types"; | ||
|
|
||
| export const storyQueryKeys = { | ||
| all: ["story"] as const, | ||
| lists: () => [...storyQueryKeys.all, "list"] as const, | ||
| } as const; | ||
|
|
||
| export const usePostStoryMutation = () => { | ||
| const queryClient = useQueryClient(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: ({ | ||
| storyRequest, | ||
| imageFile, | ||
| }: { | ||
| storyRequest: StoryRegisterRequest; | ||
| imageFile: File; | ||
| }) => { | ||
| return postStory(storyRequest, imageFile); | ||
| }, | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: storyQueryKeys.lists(), | ||
| }); | ||
| }, | ||
| }); | ||
| }; |
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,9 @@ | ||
| export type StoryRegisterRequest = { | ||
| storeKakaoId: string; | ||
| storeName: string; | ||
| description?: string; | ||
| }; | ||
|
|
||
| export type StoryRegisterResponse = { | ||
| storyId: number; | ||
| }; |
9 changes: 9 additions & 0 deletions
9
src/app/story/register/_components/StoryDescription/StoryDescription.css.ts
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,9 @@ | ||
| import { style } from "@vanilla-extract/css"; | ||
|
|
||
| export const wrapper = style({ | ||
| margin: "2rem 0", | ||
| }); | ||
|
|
||
| export const textField = style({ | ||
| height: "9.6rem", | ||
| }); |
28 changes: 28 additions & 0 deletions
28
src/app/story/register/_components/StoryDescription/StoryDescription.tsx
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,28 @@ | ||
| "use client"; | ||
|
|
||
| import { useFormContext } from "react-hook-form"; | ||
|
|
||
| import { TextField } from "@/components/ui/TextField"; | ||
|
|
||
| import { type StoryRegisterFormData } from "../../_schemas"; | ||
| import * as styles from "./StoryDescription.css"; | ||
|
|
||
| export const StoryDescription = () => { | ||
| const { | ||
| register, | ||
| formState: { errors }, | ||
| } = useFormContext<StoryRegisterFormData>(); | ||
|
|
||
| return ( | ||
| <div className={styles.wrapper}> | ||
| <TextField | ||
| {...register("description")} | ||
| as='textarea' | ||
| placeholder='가게에 대해 설명해주세요' | ||
| status={errors.description ? "negative" : "inactive"} | ||
| helperText={errors.description?.message} | ||
| className={styles.textField} | ||
| /> | ||
| </div> | ||
| ); | ||
| }; |
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 @@ | ||
| export { StoryDescription } from "./StoryDescription"; |
35 changes: 35 additions & 0 deletions
35
src/app/story/register/_components/StoryImagePreview/StoryImagePreview.css.ts
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,35 @@ | ||
| import { style } from "@vanilla-extract/css"; | ||
|
|
||
| import { colors, radius, semantic, typography } from "@/styles"; | ||
|
|
||
| export const imageWrapper = style({ | ||
| position: "relative", | ||
| width: "12.1rem", | ||
| height: "21.3rem", | ||
| borderRadius: radius[160], | ||
| overflow: "hidden", | ||
| margin: "0 auto", | ||
| backgroundColor: colors.neutral[10], | ||
| }); | ||
|
|
||
| export const image = style({ | ||
| objectFit: "contain", | ||
| }); | ||
|
|
||
| export const overlayButtonWrapper = style({ | ||
| position: "absolute", | ||
| bottom: "0", | ||
| width: "100%", | ||
| display: "flex", | ||
| justifyContent: "center", | ||
| padding: "0 1.2rem 1.2rem", | ||
| }); | ||
|
|
||
| export const overlayButton = style({ | ||
| padding: "0.5rem 1.2rem", | ||
| ...typography.label1Sb, | ||
| color: semantic.text.white, | ||
| background: "rgba(23, 23, 23, 0.60)", | ||
| borderRadius: radius.circle, | ||
| cursor: "pointer", | ||
| }); |
60 changes: 60 additions & 0 deletions
60
src/app/story/register/_components/StoryImagePreview/StoryImagePreview.tsx
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,60 @@ | ||
| "use client"; | ||
|
|
||
| import Image from "next/image"; | ||
| import { useFormContext } from "react-hook-form"; | ||
|
|
||
| import { imageFileSchema, type StoryRegisterFormData } from "../../_schemas"; | ||
| import * as styles from "./StoryImagePreview.css"; | ||
|
|
||
| export const StoryImagePreview = () => { | ||
| const { watch, setValue } = useFormContext<StoryRegisterFormData>(); | ||
| const imageFile = watch("image"); | ||
|
|
||
| const previewUrl = URL.createObjectURL(imageFile); | ||
|
|
||
| const validateImage = (file: File) => { | ||
| const result = imageFileSchema.safeParse(file); | ||
|
|
||
| if (!result.success) { | ||
| const errorMessage = result.error.errors[0]?.message; | ||
| // TODO: Toast 변경 | ||
| alert(errorMessage); | ||
| return; | ||
| } | ||
|
|
||
| setValue("image", file, { shouldValidate: true }); | ||
| }; | ||
|
|
||
| const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| const newFile = e.target.files?.[0]; | ||
|
|
||
| if (newFile) { | ||
| validateImage(newFile); | ||
| } | ||
|
|
||
| e.target.value = ""; | ||
| }; | ||
|
|
||
| return ( | ||
| <div className={styles.imageWrapper}> | ||
| <Image | ||
| src={previewUrl} | ||
| alt='스토리 등록 사진 프리뷰' | ||
| width={121} | ||
| height={213} | ||
| className={styles.image} | ||
| /> | ||
| <div className={styles.overlayButtonWrapper}> | ||
| <label className={styles.overlayButton}> | ||
| 사진 변경 | ||
| <input | ||
| type='file' | ||
| onChange={handleImageChange} | ||
| accept='image/jpeg,image/jpg,image/png' | ||
| hidden | ||
| /> | ||
| </label> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; |
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 @@ | ||
| export { StoryImagePreview } from "./StoryImagePreview"; |
12 changes: 12 additions & 0 deletions
12
src/app/story/register/_components/StorySearchStore/StorySearchStore.css.ts
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,12 @@ | ||
| import { style } from "@vanilla-extract/css"; | ||
|
|
||
| import { colors } from "@/styles"; | ||
|
|
||
| export const star = style({ | ||
| marginLeft: "0.4rem", | ||
| color: colors.redOrange[50], | ||
| }); | ||
|
|
||
| export const field = style({ | ||
| cursor: "pointer", | ||
| }); |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P999 image upload 로직은 어느정도 hook으로 추출해서 사용해볼 수 있을 것 같아요!
prop 조금 받아서욥