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

style: 선물 보따리 채우기 플로우 퍼블리싱 #25

Merged
merged 31 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d714224
feat: shadcn tooltip 설치
xxxjinn Feb 3, 2025
a6bac95
design: shadcn tooltip (기본) 추가
xxxjinn Feb 3, 2025
8c8dd53
feat: 선물 업로드 페이지로 연결 및 선물 업로드 페이지 라우팅 처리
xxxjinn Feb 3, 2025
9405c7d
design: 선물 링크 input 컴포넌트 퍼블리싱
xxxjinn Feb 3, 2025
42d22d5
design: 선물 정보 업로드 페이지 기본 구조 잡기
xxxjinn Feb 3, 2025
cdc1e1b
feat: 삭제 아이콘 추가
xxxjinn Feb 3, 2025
4c11078
feat: image 아이콘 추가
xxxjinn Feb 3, 2025
eec2e5d
design: ImageCard 컴포넌트 퍼블리싱
xxxjinn Feb 3, 2025
47f1386
feat: 이미지 업로드 목록 퍼블리싱 및 구현
xxxjinn Feb 3, 2025
e259192
feat: 선물 상세 업로드 - 이미지 업로드 컴포넌트 구현
xxxjinn Feb 3, 2025
aac82da
feat: CharacterCountInput의 value를 부모에 전달할 수 있도록 props 추가
xxxjinn Feb 4, 2025
49f3ee8
feat: 보따리 이름 입력에 따른 button disabled 구현
xxxjinn Feb 4, 2025
58f88e0
refactor: (임시) CharacterCountInput onChange props 선택으로 수정
xxxjinn Feb 4, 2025
c8c8ebb
design: symantic color 추가
xxxjinn Feb 4, 2025
2ac28c5
feat: 에러메세지 컴포넌트 구현
xxxjinn Feb 4, 2025
63ca7ed
feat: 선물 정보 업로드 - 필수 입력 항목 입력 여부에 따른 에러메세지 출력 구현
xxxjinn Feb 4, 2025
b6f0410
refactor: constants 추가
xxxjinn Feb 4, 2025
f1541c1
design: font weight 추가
xxxjinn Feb 4, 2025
4461788
design: pretendardVariable로 변경 (weight 수정 가능)
xxxjinn Feb 4, 2025
eab9dc3
feat: Chip 컴포넌트 구현
xxxjinn Feb 4, 2025
c158b54
feat: ChipList 컴포넌트 구현
xxxjinn Feb 4, 2025
1c2f152
rename: constants.ts 파일 하나로 합치기
xxxjinn Feb 4, 2025
31e413e
rename: ChipList.tsx 파일 위치 이동
xxxjinn Feb 4, 2025
761edee
feat: shadcn textarea 설치
xxxjinn Feb 4, 2025
6a202b6
refactor: 0번째 chip default로 선택하도록 수정
xxxjinn Feb 4, 2025
3daee02
design: Chip 스타일 수정
xxxjinn Feb 4, 2025
7df7baf
design: CustomTextArea 퍼블리싱
xxxjinn Feb 4, 2025
5cbfea1
feat: chip 선택에 따라 textArea 내용 매핑
xxxjinn Feb 4, 2025
7941804
rename: store 관련 폴더 구조 정리
xxxjinn Feb 4, 2025
02346e7
feat: gift_letter_square img 추가
xxxjinn Feb 4, 2025
566c272
feat: 선물 선택 이유 컴포넌트 구현
xxxjinn Feb 4, 2025
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
398 changes: 397 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.474.0",
Expand Down
18 changes: 18 additions & 0 deletions public/icons/btn_erase.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions public/icons/image_medium.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions public/img/gift_letter_square.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/app/constants/constants.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constants.ts 파일을 app 내 위치시킨 이유가 있으실까요?!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어 그러게요....ㅎ 인지를 못하고있었네요 dev 브랜치에서 바로 경로 루트로 바꿔놔도 괜찮을가요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵!

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const GIFTBAG_NAME_MAX_LENGTH = 20;
export const GIFT_NAME_MAX_LENGTH = 20;

export const GIFT_SELECT_REASON_MAX_LENGTH = 100;
Binary file removed src/app/fonts/Pretendard.woff
Binary file not shown.
Binary file added src/app/fonts/PretendardVariable.woff2
Binary file not shown.
11 changes: 11 additions & 0 deletions src/app/gift-upload/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import GiftForm from "@/components/gift-upload/GiftForm";

const page = () => {
return (
<>
<GiftForm />
</>
);
};

export default page;
12 changes: 2 additions & 10 deletions src/app/giftbag/name/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import CharacterCountInput from "@/components/common/CharacterCountInput";
import SelectedGiftBag from "@/components/giftbag/SelectedGiftBag";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import GiftBagForm from "@/components/giftbag/GiftBagForm";

const page = () => {
return (
Expand All @@ -12,13 +10,7 @@ const page = () => {
선물 보따리의 이름을 적어주세요
</p>
</div>
<CharacterCountInput
maxLength={20}
placeholder="빅토리의 생일 선물 보따리"
/>
<Link href="/giftbag/add">
<Button size="lg">선물 채우러 가기</Button>
</Link>
<GiftBagForm />
</div>
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import Header from "@/layout/Header";
import { Suspense } from "react";

const pretendard = localFont({
src: "./fonts/Pretendard.woff",
src: "./fonts/PretendardVariable.woff2",
variable: "--font-pretendard",
weight: "100 900",
weight: "100 200 300 400 500 600 700 800 900",
});
const nanumSquareRound = localFont({
src: "./fonts/NanumSquareRound.ttf",
variable: "--font-nanum-square-round",
weight: "100 900",
weight: "100 200 300 400 500 600 700 800 900",
});

export const metadata: Metadata = {
Expand Down
8 changes: 6 additions & 2 deletions src/components/common/CharacterCountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ import { Input } from "@/components/ui/input";
interface CharacterCountInputProps {
placeholder: string;
maxLength: number;
onChange?: (value: string) => void; // 입력값을 부모 컴포넌트로 전달
}

const CharacterCountInput = ({
placeholder,
maxLength,
onChange,
}: CharacterCountInputProps) => {
const [text, setText] = useState("");

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value.length <= maxLength) {
setText(e.target.value);
const value = e.target.value;
if (value.length <= maxLength && onChange) {
setText(value);
onChange(value); // 입력값 변경 시 부모 컴포넌트에 전달
}
};

Expand Down
19 changes: 19 additions & 0 deletions src/components/common/Chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
interface ChipProps {
text: string;
isActive: boolean;
onClick: () => void;
}

const Chip = ({ text, isActive, onClick }: ChipProps) => {
return (
<span
onClick={onClick}
className={`inline-flex items-center justify-center min-w-[55px] h-[28px] px-3 rounded-[6px] border-[1.4px] cursor-pointer
${isActive ? "bg-pink-500 border-pink-500 text-white" : "bg-white hover:bg-pink-500 hover:border-pink-500 hover:text-white"}`}
>
<p className="m-0 text-xs">{text}</p>
</span>
);
};

export default Chip;
9 changes: 9 additions & 0 deletions src/components/common/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
interface ErrorMessageProps {
message: string;
}

const ErrorMessage = ({ message }: ErrorMessageProps) => {
return <p className="text-symantic-negative text-xs mt-1">{message}</p>;
};

export default ErrorMessage;
28 changes: 28 additions & 0 deletions src/components/gift-upload/ChipList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Chip from "../common/Chip";

interface ChipListProps {
chipText: string[];
selectedChipIndex: number;
onChipClick: (index: number) => void;
}

const ChipList = ({
chipText,
selectedChipIndex,
onChipClick,
}: ChipListProps) => {
return (
<div className="flex gap-[7px] whitespace-nowrap">
{chipText.map((text, index) => (
<Chip
key={index}
text={text}
isActive={index === selectedChipIndex}
onClick={() => onChipClick(index)}
/>
))}
</div>
);
};

export default ChipList;
32 changes: 32 additions & 0 deletions src/components/gift-upload/CustomTextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Textarea } from "../ui/textarea";

interface CustomTextAreaProps {
placeholder: string;
maxLength: number;
text: string;
onTextChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
}

const CustomTextArea = ({
placeholder,
maxLength,
text,
onTextChange,
}: CustomTextAreaProps) => {
return (
<div className="relative">
<Textarea
placeholder={placeholder}
className="min-h-[135px] h-[135px] resize-none bg-white placeholder:text-gray-300"
value={text}
onChange={onTextChange}
maxLength={maxLength}
/>
<span className="absolute bottom-2 right-2 text-gray-400 text-[10px]">
{text.length} / {maxLength}
</span>
</div>
);
};

export default CustomTextArea;
56 changes: 56 additions & 0 deletions src/components/gift-upload/GiftForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { useState } from "react";
import CharacterCountInput from "../common/CharacterCountInput";
import { Button } from "../ui/button";
import InputLink from "./InputLink";
import InputReason from "./InputReason";
import UploadImageList from "./UploadImageList";
import ErrorMessage from "../common/ErrorMessage";
import { GIFT_NAME_MAX_LENGTH } from "@/app/constants/constants";

const GiftForm = () => {
const [imageCount, setImageCount] = useState(0);
const [giftName, setGiftName] = useState("");
const [isSubmitted, setIsSubmitted] = useState(false);

const handleSubmit = () => {
if (imageCount === 0 || giftName.length === 0) {
setIsSubmitted(true);
return;
}
};

return (
<div className="flex flex-col p-4 gap-[50px]">
<div className="flex flex-col gap-1">
<div
className="w-full overflow-x-auto h-[110px] flex items-center"
style={{ scrollbarWidth: "none" }}
>
<div className="min-w-max">
<UploadImageList onImagesChange={setImageCount} />
</div>
</div>
{isSubmitted && imageCount === 0 && (
<ErrorMessage message="필수 입력 정보입니다." />
)}
<CharacterCountInput
maxLength={GIFT_NAME_MAX_LENGTH}
placeholder="선물명을 적어주세요"
onChange={setGiftName}
/>
{isSubmitted && giftName.length === 0 && (
<ErrorMessage message="필수 입력 정보입니다." />
)}
</div>
<InputReason />
<InputLink />
<Button size="lg" onClick={handleSubmit}>
채우기 완료
</Button>
</div>
);
};

export default GiftForm;
39 changes: 39 additions & 0 deletions src/components/gift-upload/ImageCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import Image from "next/image";
import EraseIcon from "../../../public/icons/btn_erase.svg";

interface ImageCardProps {
src: string;
isPrimary?: boolean;
onDelete: () => void;
}

const ImageCard = ({ src, isPrimary, onDelete }: ImageCardProps) => {
return (
<div className="relative">
<button
className="absolute top-[-8px] right-[-8px] z-10"
onClick={onDelete}
>
<Image src={EraseIcon} alt="delete" width={24} height={24} />
</button>
<div className="relative rounded-[10px] h-[88px] w-[88px] bg-gray-50 border-[1.4px] border-gray-100 overflow-hidden">
<Image
src={src}
alt="Uploaded"
width={88}
height={88}
className="w-full h-full object-cover"
priority
/>
{isPrimary && (
<div className="absolute bottom-0 w-[88px] bg-[#0F0F10] opacity-70 text-gray-300 text-xs pt-0.5 text-center rounded-bl-[10px] rounded-br-[10px] h-5">
<p className="text-[10px]">대표 사진</p>
</div>
)}
</div>
</div>
);
};

export default ImageCard;
17 changes: 17 additions & 0 deletions src/components/gift-upload/InputLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Input } from "../ui/input";

const InputLink = () => {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-[5px] ">
<p className="text-[15px]">선물 링크를 메모해보세요.</p>
<p className="text-gray-400 text-xs">
선물 링크는 상대방에게 공개되지 않습니다.
</p>
</div>
<Input placeholder="링크 추가" />
</div>
);
};

export default InputLink;
Loading