From affbdf1a44ebd1fbb84e0b6c2034ead4f96e165a Mon Sep 17 00:00:00 2001 From: gahusb Date: Wed, 11 Feb 2026 23:57:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9D=8C=EB=A0=A5=20=EB=B3=80=ED=99=98?= =?UTF-8?q?,=20=EB=8C=80=EC=9A=B4=20=EA=B3=84=EC=82=B0,=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EA=B3=B5=EC=9C=A0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 음력 변환 기능 구현 - lunar-calendar 라이브러리 추가 - 음력-양력 변환 유틸리티 생성 - 모든 입력 폼에 양력/음력 선택 및 윤달 옵션 추가 - SajuForm, CompatibilityForm에 음력 지원 - 대운(大運) 계산 기능 구현 - 10년 단위 대운 계산 알고리즘 - 현재 대운 표시 및 해석 - 사주팔자 결과 페이지에 대운 섹션 추가 - 8개 대운 (80년치) 표시 - 소셜 공유 기능 구현 - ShareButtons 컴포넌트 생성 - 카카오톡, 페이스북, 트위터 공유 - 네이티브 공유 API 지원 - 링크 복사 기능 - 모든 결과 페이지에 공유 버튼 추가 - 메타데이터 개선 - 사이트 제목 및 설명 최적화 - 한국어(ko) 설정 - 카카오 SDK 추가 Co-Authored-By: Claude Sonnet 4.5 --- app/compatibility/result/page.tsx | 8 +- app/components/CompatibilityForm.tsx | 123 +++++++++++++++++++- app/components/SajuForm.tsx | 47 +++++++- app/components/ShareButtons.tsx | 162 +++++++++++++++++++++++++++ app/layout.tsx | 23 +++- app/result/page.tsx | 98 +++++++++++++++- app/tojeong/result/page.tsx | 8 +- lib/daeun-calculator.ts | 135 ++++++++++++++++++++++ lib/lunar-utils.ts | 97 ++++++++++++++++ package-lock.json | 9 ++ package.json | 1 + 11 files changed, 695 insertions(+), 16 deletions(-) create mode 100644 app/components/ShareButtons.tsx create mode 100644 lib/daeun-calculator.ts create mode 100644 lib/lunar-utils.ts diff --git a/app/compatibility/result/page.tsx b/app/compatibility/result/page.tsx index 288d443..bd92ce0 100644 --- a/app/compatibility/result/page.tsx +++ b/app/compatibility/result/page.tsx @@ -1,6 +1,7 @@ import Link from 'next/link'; import { calculateSaju, FIVE_ELEMENTS } from '@/lib/saju-calculator'; import PDFButton from '../../components/PDFButton'; +import ShareButtons from '../../components/ShareButtons'; interface PageProps { searchParams: Promise<{ @@ -349,7 +350,7 @@ export default async function CompatibilityResultPage({ searchParams }: PageProp {/* 다른 메뉴 */} -
+
+ +
diff --git a/app/components/CompatibilityForm.tsx b/app/components/CompatibilityForm.tsx index 19a9152..dcebe70 100644 --- a/app/components/CompatibilityForm.tsx +++ b/app/components/CompatibilityForm.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { lunarToSolar } from '@/lib/lunar-utils'; export default function CompatibilityForm() { const router = useRouter(); @@ -12,6 +13,8 @@ export default function CompatibilityForm() { const [day1, setDay1] = useState(''); const [hour1, setHour1] = useState(''); const [gender1, setGender1] = useState<'male' | 'female'>('male'); + const [calendarType1, setCalendarType1] = useState<'solar' | 'lunar'>('solar'); + const [isLeapMonth1, setIsLeapMonth1] = useState(false); // Person 2 const [year2, setYear2] = useState(''); @@ -19,6 +22,8 @@ export default function CompatibilityForm() { const [day2, setDay2] = useState(''); const [hour2, setHour2] = useState(''); const [gender2, setGender2] = useState<'male' | 'female'>('female'); + const [calendarType2, setCalendarType2] = useState<'solar' | 'lunar'>('solar'); + const [isLeapMonth2, setIsLeapMonth2] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -33,15 +38,33 @@ export default function CompatibilityForm() { return; } + let finalYear1 = year1, finalMonth1 = month1, finalDay1 = day1; + let finalYear2 = year2, finalMonth2 = month2, finalDay2 = day2; + + // 음력인 경우 양력으로 변환 + if (calendarType1 === 'lunar') { + const solar = lunarToSolar(parseInt(year1), parseInt(month1), parseInt(day1), isLeapMonth1); + finalYear1 = solar.year.toString(); + finalMonth1 = solar.month.toString(); + finalDay1 = solar.day.toString(); + } + + if (calendarType2 === 'lunar') { + const solar = lunarToSolar(parseInt(year2), parseInt(month2), parseInt(day2), isLeapMonth2); + finalYear2 = solar.year.toString(); + finalMonth2 = solar.month.toString(); + finalDay2 = solar.day.toString(); + } + // URL 파라미터로 전달 const params = new URLSearchParams({ - year1, - month1, - day1, + year1: finalYear1, + month1: finalMonth1, + day1: finalDay1, gender1, - year2, - month2, - day2, + year2: finalYear2, + month2: finalMonth2, + day2: finalDay2, gender2, }); @@ -126,6 +149,50 @@ export default function CompatibilityForm() { + {/* 양력/음력 선택 */} +
+ +
+ + +
+ {calendarType1 === 'lunar' && ( +
+ +
+ )} +
+ {/* 성별 선택 */}
+ {/* 양력/음력 선택 */} +
+ +
+ + +
+ {calendarType2 === 'lunar' && ( +
+ +
+ )} +
+ {/* 성별 선택 */}
+ {calendarType === 'lunar' && ( +
+ +
+ )} {/* 성별 선택 */} diff --git a/app/components/ShareButtons.tsx b/app/components/ShareButtons.tsx new file mode 100644 index 0000000..fbdd718 --- /dev/null +++ b/app/components/ShareButtons.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { useState } from 'react'; + +interface ShareButtonsProps { + title: string; + description: string; + url?: string; +} + +export default function ShareButtons({ title, description, url }: ShareButtonsProps) { + const [showShareMenu, setShowShareMenu] = useState(false); + const shareUrl = url || (typeof window !== 'undefined' ? window.location.href : ''); + + const handleKakaoShare = () => { + if (typeof window !== 'undefined' && (window as any).Kakao) { + (window as any).Kakao.Share.sendDefault({ + objectType: 'feed', + content: { + title: title, + description: description, + imageUrl: 'https://developers.kakao.com/assets/img/about/logos/kakaolink/kakaolink_btn_medium.png', + link: { + mobileWebUrl: shareUrl, + webUrl: shareUrl, + }, + }, + buttons: [ + { + title: '자세히 보기', + link: { + mobileWebUrl: shareUrl, + webUrl: shareUrl, + }, + }, + ], + }); + } else { + alert('카카오톡 공유 기능을 사용할 수 없습니다.'); + } + }; + + const handleFacebookShare = () => { + const facebookUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`; + window.open(facebookUrl, '_blank', 'width=600,height=400'); + }; + + const handleTwitterShare = () => { + const twitterUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(shareUrl)}`; + window.open(twitterUrl, '_blank', 'width=600,height=400'); + }; + + const handleCopyLink = async () => { + try { + await navigator.clipboard.writeText(shareUrl); + alert('링크가 복사되었습니다!'); + setShowShareMenu(false); + } catch (err) { + alert('링크 복사에 실패했습니다.'); + } + }; + + const handleNativeShare = async () => { + if (navigator.share) { + try { + await navigator.share({ + title: title, + text: description, + url: shareUrl, + }); + setShowShareMenu(false); + } catch (err) { + console.log('Share cancelled or failed', err); + } + } else { + setShowShareMenu(true); + } + }; + + return ( +
+ + + {/* 공유 메뉴 (모바일에서 네이티브 공유가 안 될 때) */} + {showShareMenu && ( + <> + {/* 배경 오버레이 */} +
setShowShareMenu(false)} + >
+ + {/* 공유 메뉴 */} +
+
+

공유하기

+ +
+ +
+ {/* 카카오톡 */} + + + {/* 페이스북 */} + + + {/* 트위터 */} + + + {/* 링크 복사 */} + +
+
+ + )} +
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..0d0a747 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "사주보기 - 무료 사주팔자, 운세, 궁합, 토정비결", + description: "생년월일로 무료로 사주팔자, 오늘의 운세, 궁합, 토정비결을 확인하세요. 쟁승메이드가 제공하는 정확한 사주 서비스입니다.", }; export default function RootLayout({ @@ -23,7 +23,24 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + + + +