Skip to content
Merged
66 changes: 32 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,56 @@
# 🎁 WishPool
# 🎁 WishPooL: 마음이 모여, 넘쳐 흐르는 곳
<div align="center"><img src="https://github.com/user-attachments/assets/76bd6881-b437-48cb-8867-f26279038a03" width="500"/></div>

WishPool은 여러 사람이 제안한 선물 중에서 생일자가 직접 선택할 수 있도록 돕는 **선물 선택 과정 중심의 큐레이션 서비스**입니다.
---

## 🎁 WishPooL 서비스 소개

선물 결과보다 **선물을 고르는 과정의 경험**에 집중하여, 생일자와 참여자 모두가 만족할 수 있는 선물 준비를 돕습니다.
친구들과 함께 만드는 위시리스트로, **선물을 준비하는 과정을 편리하고 즐거운 경험**으로 만들어주는 서비스입니다. <br>
**생일자의 취향에 맞는 선물**을 고르는 과정에 집중하여, 생일자와 참여자 모두가 만족할 수 있는 선물 준비 경험을 제공합니다.

---

## 🔗 배포 링크
- 서비스 URL: https://wishpool.store
- GitHub Repository: https://github.com/WishPool-dev
| | |
|---|---|
| 🌐 WishpooL 서비스 | [WishpoooL](https://wishpool.store) |
| **Frontend** Repository | [WishPool-FE](https://github.com/WishPool-dev/WishPool-FE) |
| **Backend** Repository | [WishPool-BE](https://github.com/WishPool-dev/WishPool-BE) |

---

## 💡 기획 배경

여러 명이 함께 생일 선물을 준비할수록 생일자의 취향을 정확히 반영하기 어렵고, 선택 과정에서 의견이 분산되는 문제가 발생합니다.

WishPool은 선물 제안 과정을 열어두고 최종 선택 권한을 생일자에게 맡김으로써, 선물 준비 과정의 부담을 줄이고 선물 만족도를 높이고자 기획되었습니다.

---

## ✨ 주요 기능

### 📝 위시풀 생성
### 위시풀 생성
한 사람이 대표로 위시풀을 생성합니다.
<br> 선물을 함께 준비할 사람들에게 카카오톡 초대 링크를 공유합니다.

대표자는 생일자의 생일과 선물 수령 날짜를 선택하여 위시풀을 생성할 수 있습니다.

위시풀 생성이 완료되면 참여자를 초대할 수 있는 링크가 발급되며, 카카오톡 공유 기능을 통해 간편하게 전달할 수 있습니다.
<div align="">
<img src="https://github.com/user-attachments/assets/985762e8-0f64-404f-b726-4ff4fd43ed1e" width="320"/>
<img src="https://github.com/user-attachments/assets/852aadf7-7411-48d8-9257-7f021fb426f9" width="320"/>
</div>


---

### 🎁 선물 제안

대표자와 참여자는 생성된 위시풀에 생일자에게 주고 싶은 선물을 자유롭게 제안할 수 있습니다.

각 선물은 **이미지, 링크, 이름** 정보를 포함하여 등록되며, 여러 개의 선물을 제안하는 것도 가능합니다.

이를 통해 참여자들의 다양한 아이디어가 자연스럽게 모이도록 설계했습니다.
### 선물 제안
위시풀에서는 생일자에게 주고 싶은 선물을 자유롭게 제안할 수 있습니다.<br>
상품 이미지, 링크, 이름 정보를 포함하여 여러 개의 선물을 등록할 수 있습니다.
<div align="">
<img src="https://github.com/user-attachments/assets/0ff0d1c1-448e-4fc3-98fb-f020efe93d6b" width="320"/>
<img src="https://github.com/user-attachments/assets/8b26bf75-9339-4c4b-93f2-b683913ae3e3" width="320"/>
</div>

---

### 🎯 생일자 선물 선택

생일자는 참여자들이 제안한 선물 목록을 확인한 뒤, 원하지 않는 선물을 **드래그 인터랙션으로 제거**하며 최종적으로 받고 싶은 선물만 선택할 수 있습니다.

선물 선택 과정을 단순한 결정이 아닌, 부담 없이 즐길 수 있는 하나의 경험으로 제공합니다.

---

### 📣 선택 결과 공유

생일자의 선택이 완료되면 대표자의 홈 화면에서 선택 완료 상태를 확인할 수 있습니다.

선택된 선물 목록은 카카오톡 공유 버튼을 통해 참여자들과 간편하게 공유할 수 있어, 선물 준비 과정을 자연스럽게 마무리할 수 있습니다.
생일자는 친구들이 제안한 선물 중 취향에 맞지 않는 선물을 드래그로 제거하며 부담 없이 원하는 선물을 선택할 수 있습니다.
<div align="">
<img src="https://github.com/user-attachments/assets/3834eb8d-2a04-4548-a6ec-ae9dc6e9fd0c" width="320"/>
<img src="https://github.com/user-attachments/assets/b99ba069-778e-4408-bf9b-e5f4cb3efcdb" width="320"/>
</div>

---

Expand Down
24 changes: 5 additions & 19 deletions src/app/intro/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
'use client';

import Image from 'next/image';
import { useRouter } from 'next/navigation';

import Onboarding1Image from '@/assets/images/onboarding-1.jpg';
import Button from '@/components/common/Button';
import { PATH } from '@/constants/common/path';
import StartButton from '@/components/common/Button/StartButton';
import { ONBOARDING_CONTENT } from '@/constants/intro/onBoardingContent';

const OnBoardingPage = () => {
const router = useRouter();

const handleStart = () => {
const token = localStorage.getItem('accessToken');
if (!token) {
router.push(PATH.LOGIN);
return;
}
router.push(PATH.HOME);
};
return (
<div className="w-full text-center">
<div>
Expand All @@ -39,7 +25,8 @@ const OnBoardingPage = () => {
alt="온보딩 - 위시풀 이미지"
fill
priority
sizes="100vw"
fetchPriority="high"
sizes="(max-width: 430px) 100vw, 430px"
className="object-cover"
/>
</div>
Expand All @@ -52,8 +39,7 @@ const OnBoardingPage = () => {
src={section.imageSrc}
alt={section.imageAlt}
fill
priority
sizes="100vw"
sizes="(max-width: 430px) 100vw, 430px"
className="object-cover"
/>
</div>
Expand All @@ -65,7 +51,7 @@ const OnBoardingPage = () => {
))}
<div className="fixed inset-x-0 bottom-0">
<div className="bottom-0 mx-auto w-full max-w-[430px] bg-[linear-gradient(180deg,_rgba(255,255,255,0)_0%,_#fff_100%)] p-[2rem]">
<Button onClick={handleStart}>WishpooL 시작하기</Button>
<StartButton />
</div>
</div>
</div>
Expand Down
44 changes: 28 additions & 16 deletions src/app/wishpool/join/[id]/info/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,50 @@ const InfoPage = () => {
const router = useRouter();
const wishpoolId = useGetWishpoolId();
const param = useSearchParams();
const shareidentifier = param.get('shareIdentifier') ?? '';
const { data: wishpoolData } = useGetWishpoolGuestInfo(
wishpoolId,
shareidentifier,
);
const { data: wishpoolImage, isPending } = useGetWishpoolImage(
wishpoolData?.imageKey ?? '',
);
const shareIdentifier = param.get('shareIdentifier') ?? '';

const guestQuery = useGetWishpoolGuestInfo(wishpoolId, shareIdentifier);
const wishpoolData = guestQuery.data;

const imageQuery = useGetWishpoolImage(wishpoolData?.imageKey ?? '');
const wishpoolImage = imageQuery.data;

const displayImg = wishpoolImage?.key || WishpoolCardImage;

if (!wishpoolData) return <Loading />;
if (isPending) return <Loading />;
if (!wishpoolId || !shareIdentifier) {
return <div>잘못된 접근입니다.</div>;
}

if (guestQuery.isPending) {
return <Loading />;
}

if (guestQuery.isError || !wishpoolData) {
return <div>초대 정보를 불러오지 못했어요.</div>;
}

return (
<>
<div className="text-text">
<h1 className="head1 break-words break-keep">
{wishpoolData?.owner}님이 보낸 <br />
{wishpoolData.owner}님이 보낸 <br />
위시풀 초대장이 도착했어요! <br />
<span className="text-blue-primary">{wishpoolData?.celebrant}</span>
<span className="text-blue-primary">{wishpoolData.celebrant}</span>
님의 선물을 함께 고민해 볼까요?
</h1>
</div>

<div className="relative mx-auto mt-[7.6rem] w-full rounded-[16px] bg-white py-[1.2rem]">
<Icon
name="ribbon"
width={120}
height={75}
className="absolute -top-5 left-1/2 -translate-x-1/2 -translate-y-1/2"
/>

<BirthdayInfo
celebrant={wishpoolData?.celebrant ?? ''}
birthDay={getSlashDateFmt(wishpoolData?.birthDay) ?? ''}
celebrant={wishpoolData.celebrant ?? ''}
birthDay={getSlashDateFmt(wishpoolData.birthDay) ?? ''}
/>

<div className="relative h-[18.7rem] w-full">
Expand All @@ -61,17 +72,18 @@ const InfoPage = () => {
alt="위시풀 대표 이미지"
/>
</div>

<div className="body2 flex gap-[1.2rem] p-[1.6rem]">
<span className="shrink-0 text-gray-600">소개</span>
<p className="whitespace-pre-line text-gray-800">
{wishpoolData?.description}
{wishpoolData.description}
</p>
</div>
</div>

<div className="bg-background-01 fixed inset-x-0 bottom-0 mx-auto inline-flex w-full max-w-[430px] flex-col items-center justify-center gap-[1.6rem] border-t border-gray-300 p-[2rem] pt-[1.6rem] pb-[2rem]">
<p className="text-text caption1 text-center">
참여 마감일: {getSlashDateFmt(wishpoolData?.endDate)} 까지
참여 마감일: {getSlashDateFmt(wishpoolData.endDate)} 까지
</p>

<Button
Expand Down
17 changes: 17 additions & 0 deletions src/components/common/Button/StartButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use client';

import { useRouter } from 'next/navigation';

import Button from '@/components/common/Button';
import { PATH } from '@/constants/common/path';

export default function StartButton() {
const router = useRouter();

const handleStart = () => {
const token = localStorage.getItem('accessToken');
router.push(token ? PATH.HOME : PATH.LOGIN);
};

return <Button onClick={handleStart}>WishpooL 시작하기</Button>;
}
20 changes: 0 additions & 20 deletions src/styles/font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,6 @@ import localFont from 'next/font/local';

export const suite = localFont({
src: [
{
path: '../../public/fonts/suite/SUITE-Light.woff2',
weight: '300',
style: 'normal',
},
{
path: '../../public/fonts/suite/SUITE-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: '../../public/fonts/suite/SUITE-Medium.woff2',
weight: '500',
Expand All @@ -27,16 +17,6 @@ export const suite = localFont({
weight: '700',
style: 'normal',
},
{
path: '../../public/fonts/suite/SUITE-ExtraBold.woff2',
weight: '800',
style: 'normal',
},
{
path: '../../public/fonts/suite/SUITE-Heavy.woff2',
weight: '900',
style: 'normal',
},
],
variable: '--font-suite',
display: 'swap',
Expand Down
Loading