Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
208 changes: 45 additions & 163 deletions app/join/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,166 +1,48 @@
'use client';

import { useRouter, useParams } from 'next/navigation';
import { useState, useEffect } from 'react';
import { useEnterParticipant } from '@/hooks/api/mutation/useEnterParticipant';
import { useToast } from '@/hooks/useToast';
import Toast from '@/components/ui/toast';
import { useIsLoggedIn } from '@/hooks/useIsLoggedIn';
import { setMeetingUserId } from '@/lib/storage';

export default function Page() {
const params = useParams();
const meetingId = params?.id as string;
const router = useRouter();

const { isLogin, isChecking } = useIsLoggedIn(meetingId); // ⭐ meetingId 전달

const [name, setName] = useState('');
const [password, setPassword] = useState('');
const [isRemembered, setIsRemembered] = useState(true);
const [errorMessage, setErrorMessage] = useState('');

const participantEnter = useEnterParticipant();
const { isVisible, show } = useToast();

useEffect(() => {
if (isChecking) return;

if (isLogin && meetingId) {
router.replace(`/meeting/${meetingId}`);
}
}, [isChecking, isLogin, meetingId, router]);

if (isChecking || isLogin) {
return (
<div className="flex h-screen flex-col items-center justify-center gap-4 bg-white">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" />
<p className="text-sm font-medium text-gray-500">로그인 정보를 확인 중...</p>
</div>
);
}

const isFormValid = name.length > 0 && password.length === 4;

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isFormValid || !meetingId) return;

try {
const result = await participantEnter.mutateAsync({
meetingId,
data: {
userId: name,
password,
},
});

if (result.success) {
// ⭐ 모임별로 분리된 스토리지에 저장
setMeetingUserId(meetingId, name, isRemembered);

router.push(`/meeting/${meetingId}`);
} else {
setErrorMessage('모임 참여에 실패했습니다. 다시 시도해주세요.');
show();
}
} catch {
setErrorMessage('모임 참여에 실패했습니다. 이름과 비밀번호를 확인해주세요.');
show();
}
import type { Metadata, ResolvingMetadata } from 'next';
import JoinForm from '@/components/join/joinForm';

type Props = {
params: Promise<{ id: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};

export async function generateMetadata(
{ params, searchParams }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const { id } = await params;
const { view } = await searchParams;

// 부모(layout.tsx)의 이미지 정보 가져오기
const previousImages = (await parent).openGraph?.images || [];

// 상황별 텍스트 및 이미지 설정
const isNudge = view === 'nudge';

const title = isNudge ? '모임원들이 결과를 기다리고 있어요!' : '모임에 참여하세요!';

// 쿼리스트링에 따라 이미지 URL만 변경
const imageUrl =
view === 'nudge'
? '/images/og-image/nudge_meeting_card.jpg' // 재촉하기 이미지
: '/images/og-image/share_meeting_card.jpg'; // 기본 초대 이미지

return {
title,
openGraph: {
title,
url: `https://www.mingling.kr/join/${id}`, // 실제 도메인으로 변경 권장
images: [imageUrl, ...previousImages],
},
twitter: {
card: 'summary_large_image',
title,
images: [imageUrl],
},
};
}

return (
<div className="flex flex-col items-center justify-center gap-11 bg-white px-5 py-10 md:min-h-[calc(100vh-200px)] md:justify-center">
<h1 className="w-full text-[22px] font-semibold text-black md:max-w-sm">
모임에 참여해 주세요.
</h1>

<form onSubmit={handleSubmit} className="flex w-full flex-col gap-5 md:max-w-sm">
<div className="flex flex-col gap-2">
<label htmlFor="name" className="text-gray-9 text-sm font-semibold">
이름을 입력해주세요.
</label>
<input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="최대 20자 이내로 입력해주세요"
maxLength={20}
className="border-gray-2 placeholder:text-gray-3 text-gray-10 focus:border-blue-5 w-full rounded-sm border py-2 pl-3 text-[15px] focus:bg-white focus:outline-none"
/>
</div>

<div className="flex flex-col gap-2">
<label htmlFor="password" className="text-gray-9 text-sm font-semibold">
비밀번호를 입력해주세요
</label>
<input
id="password"
type="password"
inputMode="numeric"
value={password}
onChange={(e) => {
const val = e.target.value.replace(/[^0-9]/g, '');
if (val.length <= 4) setPassword(val);
}}
placeholder="숫자 4자리를 입력해주세요"
className={`border-gray-2 placeholder:text-gray-3 text-gray-10 focus:border-blue-5 w-full rounded-sm border py-2 pl-3 text-center text-[15px] focus:bg-white focus:outline-none ${password ? 'pl-0 text-center' : 'pl-3 text-left'}`}
/>

<div
onClick={() => setIsRemembered(!isRemembered)}
className="flex cursor-pointer items-center gap-2"
>
<div
className={`flex h-5 w-5 items-center justify-center rounded border transition-colors ${
!isRemembered
? 'border-gray-300 bg-white'
: isFormValid
? 'border-blue-500 bg-blue-500'
: 'border-gray-300 bg-gray-300'
}`}
>
{isRemembered && (
<svg
width="8"
height="6"
viewBox="0 0 14 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 5L4.5 8.5L13 1"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
<span className={`text-xs font-medium ${isFormValid ? 'text-blue-5' : 'text-gray-5'}`}>
내 정보 기억하기
</span>
</div>
</div>

<div className="relative w-full md:max-w-sm">
<button
type="submit"
disabled={!isFormValid || participantEnter.isPending}
className={`text-gray-2 mt-6 h-12 w-full rounded-sm py-4 pt-3 pb-2.5 text-lg font-semibold transition-colors ${
isFormValid && !participantEnter.isPending
? 'hover:bg-blue-8 bg-blue-5'
: 'bg-gray-4 cursor-not-allowed'
}`}
>
모임 참여하기
</button>
<Toast message={errorMessage} isVisible={isVisible} />
</div>
</form>
</div>
);
export default async function Page({ params }: Props) {
const { id } = await params;
return <JoinForm meetingId={id} />;
}
1 change: 1 addition & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const pretendard = localFont({
});

export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL || 'https://www.mingling.kr'),
title: '밍글링 - 어디서 만날지, 고민 시간을 줄여드려요',
description:
'퇴근 후 모임, 주말 약속까지. 서울 어디서든 모두가 비슷하게 도착하는 마법의 장소를 찾아드려요.',
Expand Down
6 changes: 3 additions & 3 deletions app/meeting/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export default function Page() {
)}

<KakaoMap
className="bg-gray-1 relative block aspect-video h-93.5 md:hidden"
className="bg-gray-1 relative block h-93.5 md:hidden"
participants={allParticipants}
/>

Expand Down Expand Up @@ -282,7 +282,7 @@ export default function Page() {
/>
</button>

<div className="mb-10 flex-1">
<div className="flex-1">
<div className="[&::-webkit-scrollbar-thumb]:bg-gray-6 flex h-80 flex-col gap-3.5 overflow-y-scroll pr-2 pb-5 md:pb-18 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full">
{allParticipants.length > 0 ? (
allParticipants.map((user) => (
Expand Down Expand Up @@ -314,7 +314,7 @@ export default function Page() {

<button
onClick={handleSubmit}
className="bg-gray-8 absolute right-5 bottom-0 left-5 h-12 cursor-pointer rounded text-lg text-white md:right-0 md:left-0"
className="bg-gray-8 right-5 left-5 h-12 cursor-pointer rounded text-lg text-white md:absolute md:right-0 md:bottom-0 md:left-0 md:mb-0"
>
결과보기
</button>
Expand Down
Binary file added app/opengraph-image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 13 additions & 10 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const PainPointTooltip = ({ text }: { text: string }) => (

// 2. 반복되는 특징 카드 컴포넌트
const FeatureCard = ({ title, desc, desc2 }: { title: string; desc: string; desc2: string }) => (
<div className="border-gray-2 flex h-[236px] flex-col items-center justify-center gap-7.75 rounded-[20px] border-2 bg-white w-[247px] lg:w-[280px]">
<div className="border-gray-2 flex h-[236px] w-[247px] flex-col items-center justify-center gap-7.75 rounded-[20px] border-2 bg-white lg:w-[280px]">
<h3 className="text-gray-10 text-center text-[22px] leading-[1.364] font-semibold tracking-[-0.4268px]">
{title}
</h3>
Expand All @@ -77,16 +77,19 @@ const FeatureCard = ({ title, desc, desc2 }: { title: string; desc: string; desc
const HeroSection = () => (
<section className="mx-[71.73px] mt-30 flex flex-col items-center justify-center md:px-[101px]">
<div className="flex flex-col items-center">
<h1 className="text-gray-8 max-w-[209px] text-center text-[28px] leading-[1.3] font-bold tracking-[-1.128px] break-keep md:max-w-full md:text-[40px]">
<h1 className="text-gray-8 max-w-[209px] text-center text-[28px] leading-[1.3] font-bold tracking-[-1.128px] break-keep md:max-w-full md:text-[36px] xl:text-[40px]">
모임 장소 선정, 출발역만 넣으면 끝!
</h1>
<div className="mt-4 flex flex-col items-center">
<p className="text-gray-5 max-w-[240px] text-center text-[16px] leading-[1.364] font-semibold tracking-[-0.4268px] break-keep md:max-w-[530px] md:text-[22px]">
참석자들이 지하철 출발역을 입력하면,
</p>
<p className="text-gray-5 max-w-[240px] text-center text-[16px] leading-[1.364] font-semibold tracking-[-0.4268px] break-keep md:max-w-[530px] md:text-[22px]">
이동시간과 편차를 분석해 서울 내 최적의 번화가를
추천합니다

<div className="mt-4 flex flex-col flex-nowrap items-center">
<p className="text-gray-5 text-center text-[16px] leading-[1.364] font-semibold tracking-[-0.4268px] md:text-[18px] md:leading-[1.444]">
참석자들이 지하철 출발역을 입력하면,
<br className="block md:hidden lg:block" />
<span className="hidden md:inline lg:hidden">&nbsp;</span>
이동시간과 편차를 고려해
<br className="block lg:hidden" />
<span className="hidden lg:inline">&nbsp;</span>
서울 내 최적의 번화가를 추천합니다
</p>
</div>

Expand All @@ -97,7 +100,7 @@ const HeroSection = () => (
모임 만들기
</Link>

{/* Responsive Images */}
{/* Responsive Images (기존 코드 유지) */}
<div className="mt-[74px] md:mt-[74px] lg:mt-10">
<Image
src="/images/iphone.jpg"
Expand Down
88 changes: 30 additions & 58 deletions app/share/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,32 @@
'use client';

import { notFound, useParams } from 'next/navigation';
import Link from 'next/link';
import Toast from '@/components/ui/toast';
import { useShareMeeting } from '@/hooks/api/query/useShareMeeting';
import Image from 'next/image';

export default function SharePage() {
const params = useParams();
const id = params?.id as string;

const { shareUrl, isError, isLoading, handleCopyLink, isVisible } = useShareMeeting(id);

if (isError) notFound();
if (isLoading) return null;

return (
<div className="flex flex-col items-center justify-center bg-white px-5 py-10 md:py-25">
<h2 className="text-gray-10 mb-9 text-center text-2xl leading-[1.334] font-bold md:text-4xl">
모임이 만들어졌어요!
<br />
링크를 공유해주세요
</h2>

<section className="mb-9 flex h-70 w-80 max-w-sm items-center justify-center rounded-2xl md:w-90">
<Image
src="/images/create_meeting.jpg"
width={360}
height={257}
alt="모임이 만들어졌어요"
/>
</section>

<div className="relative z-10 mb-9 flex w-full rounded-sm md:w-90">
<Toast message="주소가 복사되었습니다" isVisible={isVisible} />
<input
type="text"
value={shareUrl}
readOnly
className="border-gray-1 grow rounded-l-sm border border-r-0 bg-white p-2.5 text-[15px] font-normal text-black focus:outline-none"
/>
<button
type="button"
onClick={handleCopyLink}
className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors"
>
복사
</button>
</div>
import type { Metadata } from 'next';
import ShareContent from '@/components/share/shareContent';

type Props = {
params: Promise<{ id: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;

return {
title: '모임이 만들어졌어요! 🎉',
description: '친구들에게 링크를 공유하고 출발지를 받아보세요.',
openGraph: {
title: '모임이 만들어졌어요! 🎉',
description: '친구들에게 링크를 공유하고 출발지를 받아보세요.',
images: ['/images/og-image/create_meeting_card.jpg'],
url: `https://www.mingling.kr/share/${id}`,
},
twitter: {
card: 'summary_large_image',
title: '모임이 만들어졌어요! 🎉',
description: '친구들에게 링크를 공유하고 출발지를 받아보세요.',
images: ['/images/og-image/create_meeting_card.jpg'],
},
};
}

<Link
href={`/join/${id}`}
className="bg-blue-5 hover:bg-blue-8 h-12 w-full rounded-sm py-2.5 pt-3 text-center text-lg font-normal text-white transition-colors md:w-90"
>
내 출발지 등록하기
</Link>
</div>
);
export default async function Page({ params }: Props) {
const { id } = await params;
return <ShareContent id={id} />;
}
Loading