diff --git a/apps/what-today/package.json b/apps/what-today/package.json index c8f15e1c..3577d68a 100644 --- a/apps/what-today/package.json +++ b/apps/what-today/package.json @@ -4,10 +4,10 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "pnpm --filter @what-today/design-system... build && tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview --host" }, "dependencies": { "@hookform/resolvers": "^5.1.1", diff --git a/apps/what-today/src/apis/activities.ts b/apps/what-today/src/apis/activities.ts index e15e8d2c..fd5775be 100644 --- a/apps/what-today/src/apis/activities.ts +++ b/apps/what-today/src/apis/activities.ts @@ -4,6 +4,7 @@ import axiosInstance from './axiosInstance'; // ✅ 체험(Activity) 타입 정의 (category 추가) export interface Activity { + createdAt?: string | number | Date; id: number; title: string; price: number; @@ -29,6 +30,7 @@ export const activityListSchema = z.array( reviewCount: z.number(), bannerImageUrl: z.string().url(), category: z.string(), + createdAt: z.string(), }), ); diff --git a/apps/what-today/src/apis/activityDetail.ts b/apps/what-today/src/apis/activityDetail.ts index 18cc864a..bdb9465c 100644 --- a/apps/what-today/src/apis/activityDetail.ts +++ b/apps/what-today/src/apis/activityDetail.ts @@ -1,4 +1,9 @@ -import { type ActivityWithSubImagesAndSchedules, activityWithSubImagesAndSchedulesSchema } from '@/schemas/activities'; +import { + type ActivityWithSubImagesAndSchedules, + activityWithSubImagesAndSchedulesSchema, + type Schedules, + schedulesSchema, +} from '@/schemas/activities'; import { type ActivityReviewsResponse, activityReviewsResponseSchema } from '@/schemas/activityReview'; import { type CreateReservationBodyDto, @@ -26,6 +31,23 @@ export const fetchActivityReviews = async (activityId: number): Promise => { + const response = await axiosInstance.get(`/activities/${activityId}/reviews`, { + params: { page, size }, + }); + return activityReviewsResponseSchema.parse(response.data); +}; + /** * @description 체험 예약 생성 요청 * @param activityId 체험 ID @@ -38,3 +60,22 @@ export const createReservation = async ( const response = await axiosInstance.post(`/activities/${activityId}/reservations`, body); return reservationResponseSchema.parse(response.data); }; + +// ✅ 체험 예약 가능일 리스트 조회 시 사용되는 파라미터 타입 +export interface ReservationAvailableScheduleParams { + year: string; + month: string; +} + +/** + * @description 체험 예약 가능일 리스트를 불러옵니다. + * @param activityId 체험 ID + * @returns ActivityWithSubImagesAndSchedules 타입의 체험 상세 데이터 + */ +export const fetchReservationAvailableSchedule = async ( + activityId: number, + params: ReservationAvailableScheduleParams, +): Promise => { + const response = await axiosInstance.get(`/activities/${activityId}/available-schedule`, { params }); + return schedulesSchema.parse(response.data); +}; diff --git a/apps/what-today/src/apis/experiences.ts b/apps/what-today/src/apis/experiences.ts index 0a45f6de..6c6c3228 100644 --- a/apps/what-today/src/apis/experiences.ts +++ b/apps/what-today/src/apis/experiences.ts @@ -1,4 +1,8 @@ -import { type CreateActivityBody, type UpdateMyActivityBody } from '@/schemas/experiences'; // 스키마 정의된 파일 기준 경로로 수정하세요 +import { + type ActivityWithSchedulesResponse, + type CreateActivityBody, + type UpdateMyActivityBody, +} from '@/schemas/experiences'; import axiosInstance from './axiosInstance'; @@ -15,3 +19,49 @@ export const patchActivity = async (activityId: number, body: UpdateMyActivityBo // createActivityBodySchema, parse를 여기서 관리하지 말고 페이지 handleSubmit에서 관리 // updateMyActivityBodySchema, + +/** + * @description 체험 이미지 url 생성 + * + * @param file 업로드할 체험 이미지 파일 + * @returns 체험 이미지 url (string) + */ +export const uploadImage = async (file: File): Promise<{ file: string }> => { + const formData = new FormData(); + formData.append('image', file); + + const response = await axiosInstance.post('activities/image', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return { file: response.data.activityImageUrl }; +}; + +/** + * @description 체험 등록 + * + * @param body 등록할 체험 데이터 + * @returns 체험 등록 데이터 + */ +export const postExperiences = async (body: CreateActivityBody): Promise => { + const response = await axiosInstance.post('activities', body); + + return response.data; +}; + +/** + * @description 체험 수정 + * + * @param body 등록할 체험 수정 데이터 + * @returns 체험 수정 데이터 + */ +export const patchExperiences = async ( + body: UpdateMyActivityBody, + activityId?: string, +): Promise => { + const response = await axiosInstance.patch(`my-activities/${activityId}`, body); + + return response.data; +}; diff --git a/apps/what-today/src/apis/myActivities.ts b/apps/what-today/src/apis/myActivities.ts index ec4c6975..740d33df 100644 --- a/apps/what-today/src/apis/myActivities.ts +++ b/apps/what-today/src/apis/myActivities.ts @@ -7,8 +7,6 @@ import { type monthlyScheduleParam, type monthlyScheduleResponse, monthlyScheduleResponseSchema, - type reservation, - reservationSchema, type timeSlotReservationParam, type timeSlotReservationResponse, timeSlotReservationResponseSchema, @@ -50,9 +48,8 @@ export async function patchReservationStatus( activityId: number, reservationId: number, status: ManageableReservationStatus, -): Promise { - const response = await axiosInstance.patch(`/my-activities/${activityId}/reservations/${reservationId}`, { status }); - return reservationSchema.parse(response.data); +): Promise { + await axiosInstance.patch(`/my-activities/${activityId}/reservations/${reservationId}`, { status }); } export const deleteMyActivity = async (activityId: number) => { diff --git a/apps/what-today/src/components/.gitkeep b/apps/what-today/src/components/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/what-today/src/components/FlagIcon.tsx b/apps/what-today/src/components/FlagIcon.tsx new file mode 100644 index 00000000..44be7551 --- /dev/null +++ b/apps/what-today/src/components/FlagIcon.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +interface FlagIconProps { + flagCode: string; + alt?: string; + className?: string; + size?: 'sm' | 'md' | 'lg'; +} + +const FlagIcon: React.FC = ({ flagCode, alt = 'Flag', className = '', size = 'md' }) => { + const sizeClasses = { + sm: 'w-30 h-30', + md: 'w-40 h-40', + lg: 'w-50 h-50', + }; + + const flagUrl = `https://cdn.weglot.com/flags/square/${flagCode}.svg`; + + return ( + {alt} { + // 이미지 로드 실패 시 기본 아이콘으로 대체 + const target = e.target as HTMLImageElement; + target.src = + 'data:image/svg+xml,'; + }} + /> + ); +}; + +export default FlagIcon; diff --git a/apps/what-today/src/components/FloatingTranslateButton.tsx b/apps/what-today/src/components/FloatingTranslateButton.tsx new file mode 100644 index 00000000..59026a51 --- /dev/null +++ b/apps/what-today/src/components/FloatingTranslateButton.tsx @@ -0,0 +1,317 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; + +import { findLanguageByCode, type Language, languages, supportedLanguageCodes } from '@/constants/languages'; + +import FlagIcon from './FlagIcon'; + +// 전역에서 스크립트 로드 상태 관리 +let isGoogleTranslateLoaded = false; +let isGoogleTranslateLoading = false; + +interface FloatingTranslateButtonProps { + className?: string; +} + +const FloatingTranslateButton: React.FC = ({ className }) => { + const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); + const [isReady, setIsReady] = useState(false); + const [currentTranslatedLang, setCurrentTranslatedLang] = useState('ko'); + const [isOpen, setIsOpen] = useState(false); + const initializationAttempted = useRef(false); + const containerRef = useRef(null); + + // 현재 번역된 언어 감지 + const detectCurrentLanguage = useCallback(() => { + try { + // URL에서 현재 번역 언어 감지 + const urlParams = new URLSearchParams(window.location.search); + const langFromUrl = urlParams.get('lang'); + + // Google Translate가 URL에 추가하는 언어 코드 확인 + if (langFromUrl) { + setCurrentTranslatedLang(langFromUrl); + const foundLang = findLanguageByCode(langFromUrl); + if (foundLang) { + setSelectedLanguage(foundLang); + } + return; + } + + // body 클래스에서 번역 상태 확인 + const bodyClasses = document.body.className; + if (bodyClasses.includes('translated-')) { + const matches = bodyClasses.match(/translated-(\w+)/); + if (matches && matches[1]) { + const detectedLang = matches[1]; + setCurrentTranslatedLang(detectedLang); + const foundLang = findLanguageByCode(detectedLang); + if (foundLang) { + setSelectedLanguage(foundLang); + } + } + } + } catch (error) { + console.warn('언어 감지 중 오류:', error); + } + }, []); + + // Google Translate 위젯 초기화 + const initializeGoogleTranslate = useCallback(() => { + if (initializationAttempted.current) return; + + try { + initializationAttempted.current = true; + + if ((window as any).google?.translate?.TranslateElement) { + // 기존 번역 요소가 있다면 제거 + const existingElement = document.getElementById('google_translate_element'); + if (existingElement) { + existingElement.innerHTML = ''; + } + + new (window as any).google.translate.TranslateElement( + { + pageLanguage: 'auto', + autoDisplay: false, + includedLanguages: supportedLanguageCodes, + layout: 0, + }, + 'google_translate_element', + ); + + setIsReady(true); + + // 초기화 후 현재 언어 감지 + setTimeout(() => { + detectCurrentLanguage(); + }, 300); + } + } catch (error) { + console.error('Google Translate 초기화 실패:', error); + initializationAttempted.current = false; + } + }, [detectCurrentLanguage]); + + // Google Translate 스크립트 로드 + useEffect(() => { + if (isGoogleTranslateLoaded) { + initializeGoogleTranslate(); + return; + } + + if (isGoogleTranslateLoading) return; + + isGoogleTranslateLoading = true; + + // 콜백 함수를 window에 등록 + (window as any).googleTranslateElementInit = () => { + isGoogleTranslateLoaded = true; + isGoogleTranslateLoading = false; + + // 약간의 지연 후 초기화 (DOM이 준비될 시간 제공) + setTimeout(() => { + initializeGoogleTranslate(); + }, 100); + }; + + // 기존 스크립트가 있는지 확인 + const existingScript = document.querySelector('script[src*="translate.google.com"]'); + if (!existingScript) { + const script = document.createElement('script'); + script.src = 'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit'; + script.async = true; + script.onerror = () => { + console.error('Google Translate 스크립트 로드 실패'); + isGoogleTranslateLoading = false; + }; + document.head.appendChild(script); + } + }, [initializeGoogleTranslate]); + + // URL 변경 감지 (번역 후 URL이 변경되므로) + useEffect(() => { + const handleLocationChange = () => { + setTimeout(() => { + detectCurrentLanguage(); + }, 500); + }; + + window.addEventListener('popstate', handleLocationChange); + return () => { + window.removeEventListener('popstate', handleLocationChange); + }; + }, [detectCurrentLanguage]); + + // 외부 클릭 감지로 드롭다운 닫기 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); + + // 언어 변경 함수 + const changeLanguage = useCallback( + (language: Language) => { + if (!isReady) { + console.warn('Google Translate가 아직 준비되지 않았습니다.'); + return; + } + + try { + // 이미 선택된 언어인 경우 무시 + if (currentTranslatedLang === language.code) { + return; + } + + // 여러 방법으로 select 요소 찾기 + let selectElement = document.querySelector('.goog-te-combo') as HTMLSelectElement; + + if (!selectElement) { + // iframe 내부에서 찾기 + const iframe = document.querySelector('iframe.goog-te-banner-frame') as HTMLIFrameElement; + if (iframe && iframe.contentDocument) { + selectElement = iframe.contentDocument.querySelector('.goog-te-combo') as HTMLSelectElement; + } + } + + if (!selectElement) { + // 다른 selector로 재시도 + selectElement = document.querySelector('select.goog-te-combo') as HTMLSelectElement; + } + + if (selectElement) { + // 한국어 선택 시 번역 끄기 (원본 상태로 되돌리기) + if (language.code === 'ko') { + try { + // select 초기화 + const selectElement = document.querySelector('.goog-te-combo') as HTMLSelectElement; + if (selectElement) { + selectElement.value = ''; + selectElement.dispatchEvent(new Event('change', { bubbles: true })); + } + + // 쿠키 제거 + document.cookie = `googtrans=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;`; + document.cookie = `googtrans=;domain=.${window.location.hostname};path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;`; + + // iframe 숨기기 + const bannerIframe = document.querySelector('iframe.goog-te-banner-frame') as HTMLIFrameElement; + if (bannerIframe?.parentElement) { + bannerIframe.parentElement.style.display = 'none'; + } + + // body class 제거 + document.body.className = document.body.className + .split(' ') + .filter((cls) => !cls.startsWith('translated-')) + .join(' '); + + // ✅ 최종: URL 파라미터 제거 & 페이지 새로고침 + const newUrl = window.location.origin + window.location.pathname; + window.location.href = newUrl; + } catch (error) { + console.error('원문 복귀 실패:', error); + } + + return; + } + // 다른 언어에서 다른 언어로 변경 시 (한국어가 아닌 경우) + else if (currentTranslatedLang !== 'ko' && language.code !== 'ko') { + selectElement.value = ''; + selectElement.dispatchEvent(new Event('change', { bubbles: true })); + + // 잠시 후 목표 언어로 변경 + setTimeout(() => { + selectElement.value = language.code; + selectElement.dispatchEvent(new Event('change', { bubbles: true })); + setCurrentTranslatedLang(language.code); + setSelectedLanguage(language); + setIsOpen(false); + }, 300); + } + // 한국어에서 다른 언어로 직접 변경 또는 일반적인 변경 + else { + selectElement.value = language.code; + selectElement.dispatchEvent(new Event('change', { bubbles: true })); + setCurrentTranslatedLang(language.code); + setSelectedLanguage(language); + setIsOpen(false); + } + } else { + console.error('Google Translate select 요소를 찾을 수 없습니다.'); + // 초기화 재시도 + initializationAttempted.current = false; + setTimeout(() => initializeGoogleTranslate(), 500); + } + } catch (error) { + console.error('언어 변경 중 오류:', error); + } + }, + [isReady, currentTranslatedLang, initializeGoogleTranslate], + ); + + // 버튼 클릭 핸들러 + const handleButtonClick = () => { + setIsOpen(!isOpen); + }; + + return ( + <> + {/* 숨겨진 Google Translate 요소 */} +
+ + {/* 플로팅 번역 버튼 */} +
+ {/* 언어 선택 드롭다운 */} + {isOpen && ( +
+
언어 선택
+
+ {languages.map((language) => ( + + ))} +
+
+ )} + + {/* 플로팅 버튼 */} + +
+ + ); +}; + +export default FloatingTranslateButton; diff --git a/apps/what-today/src/components/Header.tsx b/apps/what-today/src/components/Header.tsx index 3d42883b..5634296c 100644 --- a/apps/what-today/src/components/Header.tsx +++ b/apps/what-today/src/components/Header.tsx @@ -19,7 +19,7 @@ export default function Header() { }; return ( -
+
@@ -31,7 +31,7 @@ export default function Header() {
- + {user?.profileImageUrl ? ( 프로필 이미지 void; +// /** +// * 클릭했을 때 sidebar 열림 여부 변경 +// */ +// onClick: () => void; +// /** +// * MypageSidebar의 열림 여부 +// */ +// isOpen: boolean; +// } + +/** + * MypageSidebar 컴포넌트 + * + * 사용자의 프로필 이미지와 마이페이지 관련 메뉴를 표시합니다. + * 현재 URL 경로에 따라 해당 메뉴 항목을 하이라이트 처리합니다. + * 로그아웃 버튼을 포함하며, 클릭 시 지정된 콜백을 실행합니다. + * + * @component + * @example + * setSidebarOpen((prev) => !prev)} + * onLogoutClick={() => alert('hi')} + * /> + */ +export default function MypageMainSidebar() { + const location = useLocation(); + + /** + * 사이드바에 표시할 고정 메뉴 항목 목록 + * 각 항목은 라벨, 아이콘 컴포넌트, 이동 경로로 구성됩니다. + */ + const items = [ + { icon: UserIcon, label: '내 정보', to: '/mypage/edit-profile' }, + { icon: ListIcon, label: '예약 내역', to: '/mypage/reservations-list' }, + { icon: SettingIcon, label: '내 체험 관리', to: '/mypage/manage-activities' }, + { icon: CalendarIcon, label: '예약 현황', to: '/mypage/reservations-status' }, + ]; + + return ( + + ); +} diff --git a/apps/what-today/src/components/MypageSidebar.tsx b/apps/what-today/src/components/MypageSidebar.tsx index 2b37113b..191220c9 100644 --- a/apps/what-today/src/components/MypageSidebar.tsx +++ b/apps/what-today/src/components/MypageSidebar.tsx @@ -1,12 +1,4 @@ -import { - Button, - CalendarIcon, - ExitIcon, - ListIcon, - ProfileLogo, - SettingIcon, - UserIcon, -} from '@what-today/design-system'; +import { Button, CalendarIcon, ExitIcon, ListIcon, SettingIcon, UserIcon } from '@what-today/design-system'; import { Link, useLocation } from 'react-router-dom'; import { twMerge } from 'tailwind-merge'; @@ -15,7 +7,7 @@ interface MypageSidebarProps { * 사용자 프로필 이미지 URL * 전달되지 않을 경우 기본 아이콘(ProfileLogo)이 표시됩니다. */ - profileImgUrl?: string; + // profileImgUrl?: string; /** * 로그아웃 버튼 클릭 시 실행되는 콜백 함수(아마 모달을 띄우지 않을까 싶습니다.) */ @@ -46,7 +38,7 @@ interface MypageSidebarProps { * onLogoutClick={() => alert('hi')} * /> */ -export default function MypageSidebar({ profileImgUrl, onLogoutClick, onClick, isOpen }: MypageSidebarProps) { +export default function MypageSidebar({ onLogoutClick, onClick, isOpen }: MypageSidebarProps) { const location = useLocation(); /** @@ -64,22 +56,22 @@ export default function MypageSidebar({ profileImgUrl, onLogoutClick, onClick, i diff --git a/packages/design-system/src/pages/IconDoc.tsx b/packages/design-system/src/pages/IconDoc.tsx index e71403b4..b6e16a90 100644 --- a/packages/design-system/src/pages/IconDoc.tsx +++ b/packages/design-system/src/pages/IconDoc.tsx @@ -5,6 +5,7 @@ import { BusIcon, CalendarIcon, ChevronIcon, + CloudIcon, DeleteIcon, DotIcon, EditIcon, @@ -77,6 +78,7 @@ const colorIcons = [ { name: 'SuccessIcon', component: SuccessIcon }, { name: 'InfoIcon', component: InfoIcon }, { name: 'ErrorIcon', component: ErrorIcon }, + { name: 'CloudIcon', component: CloudIcon, props: { color: 'var(--color-primary-500)' } }, ]; const ColorIconPropsCode = ``; @@ -116,6 +118,7 @@ const iconScope = { ErrorIcon, SuccessIcon, InfoIcon, + CloudIcon, }; export default function IconDoc() { diff --git a/packages/design-system/src/pages/InputDoc.tsx b/packages/design-system/src/pages/InputDoc.tsx index cebe0ea0..82ce021e 100644 --- a/packages/design-system/src/pages/InputDoc.tsx +++ b/packages/design-system/src/pages/InputDoc.tsx @@ -9,7 +9,7 @@ const EmailInput = memo(function EmailInput() { const [value, setValue] = useState(''); return ( - + 이메일 WarningLogo
+
+ + NotFoundLogo +
@@ -81,6 +86,7 @@ export default function LogoDoc() { EmptyLogo, ProfileLogo, WarningLogo, + NotFoundLogo, }} />
diff --git a/packages/design-system/src/pages/MainCardDoc.tsx b/packages/design-system/src/pages/MainCardDoc.tsx index d14e0fe9..19b3cb1d 100644 --- a/packages/design-system/src/pages/MainCardDoc.tsx +++ b/packages/design-system/src/pages/MainCardDoc.tsx @@ -5,7 +5,7 @@ import DocTemplate, { DocCode } from '../layouts/DocTemplate'; /* Playground는 편집 가능한 코드 블록입니다. */ /* Playground에서 사용할 예시 코드를 작성해주세요. */ -const code = ` -`; +`; export default function MainCardDoc() { return ( @@ -59,24 +59,26 @@ Main에서 쓰이는 인기체험 모든체험 Card입니다.

기본 MainCard 예시 입니다.

- `} + `} />

커스텀 MainCard 예시 입니다.

-
+
+ + + +
@@ -111,7 +126,7 @@ Main에서 쓰이는 인기체험 모든체험 Card입니다. ratingClassName='text-emerald-300 font-semibold' priceClassName='text-pink-400 font-bold text-lg' /> - `} + `} /> ); diff --git a/packages/design-system/src/pages/MainPageSkeletonDoc.tsx b/packages/design-system/src/pages/MainPageSkeletonDoc.tsx new file mode 100644 index 00000000..09729abb --- /dev/null +++ b/packages/design-system/src/pages/MainPageSkeletonDoc.tsx @@ -0,0 +1,54 @@ +import Playground from '@/layouts/Playground'; + +import { ActivityCardGridSkeleton, CarouselSkeleton } from '../components/MainPageSkeleton/MainPageSkeleton'; +import DocTemplate, { DocCode } from '../layouts/DocTemplate'; + +/* Playground에서 사용할 예시 코드 */ +const code = ` +<> + {/* 인기 체험 섹션 */} +
+

인기 체험

+ +
+ + {/* 모든 체험 섹션 */} +
+

모든 체험

+
+ +
+
+ +`; + +export default function MainPageSkeletonDoc() { + return ( + <> + + + {/* 예시 코드 */} + + + {/* Playground */} +
+ +
+ + ); +} diff --git a/packages/design-system/src/pages/MainSearchInputDoc.tsx b/packages/design-system/src/pages/MainSearchInputDoc.tsx index ae39fe3b..8064f8a2 100644 --- a/packages/design-system/src/pages/MainSearchInputDoc.tsx +++ b/packages/design-system/src/pages/MainSearchInputDoc.tsx @@ -4,7 +4,7 @@ import MainSearchInput from '../components/MainSearchInput/MainSearchInput'; import DocTemplate, { DocCode } from '../layouts/DocTemplate'; /* Playground에서 사용할 예시 코드 */ -const code = ` `; +const code = ` `; export default function MainSearchInputDoc() { return ( diff --git a/packages/design-system/src/pages/MypageProfileHeaderDoc.tsx b/packages/design-system/src/pages/MypageProfileHeaderDoc.tsx new file mode 100644 index 00000000..a33baaa2 --- /dev/null +++ b/packages/design-system/src/pages/MypageProfileHeaderDoc.tsx @@ -0,0 +1,31 @@ +import MypageProfileHeader from '../components/MypageProfileHeader'; +import DocTemplate, { DocCode } from '../layouts/DocTemplate'; + +/* Playground는 편집 가능한 코드 블록입니다. */ +/* Playground에서 사용할 예시 코드를 작성해주세요. */ +// const code = `예시 코드를 작성해주세요.`; + +export default function MypageProfileHeaderDoc() { + return ( + <> + + {/* 실제 컴포넌트를 아래에 작성해주세요 */} + {/* 예시 코드 */} + Click me`} /> + + {}} /> + + ); +} diff --git a/packages/design-system/src/pages/MypageSummaryCardDoc.tsx b/packages/design-system/src/pages/MypageSummaryCardDoc.tsx new file mode 100644 index 00000000..4cb6bca3 --- /dev/null +++ b/packages/design-system/src/pages/MypageSummaryCardDoc.tsx @@ -0,0 +1,57 @@ +import { MypageSummaryCard } from '../components/MypageSummaryCard'; +import DocTemplate, { DocCode } from '../layouts/DocTemplate'; + +/* Playground는 편집 가능한 코드 블록입니다. */ +/* Playground에서 사용할 예시 코드를 작성해주세요. */ +// const code = `예시 코드를 작성해주세요.`; + +export default function MypageSummaryCardDoc() { + return ( + <> + + {/* 실제 컴포넌트를 아래에 작성해주세요 */} + {/* 예시 코드 */} + + {}} /> + {}} /> + + Click me`} /> + +
+ + {}} /> + {}} /> + + + + {}} + /> + {}} + /> + +
+ + ); +} diff --git a/packages/design-system/src/pages/OngoingExperienceCardDoc.tsx b/packages/design-system/src/pages/OngoingExperienceCardDoc.tsx new file mode 100644 index 00000000..d4415456 --- /dev/null +++ b/packages/design-system/src/pages/OngoingExperienceCardDoc.tsx @@ -0,0 +1,35 @@ +import OngoingExperienceCard from '../components/OngoingExperienceCard'; +import DocTemplate, { DocCode } from '../layouts/DocTemplate'; + +/* Playground는 편집 가능한 코드 블록입니다. */ +/* Playground에서 사용할 예시 코드를 작성해주세요. */ +// const code = `예시 코드를 작성해주세요.`; + +export default function OngoingExperienceCardDoc() { + return ( + <> + + {/* 실제 컴포넌트를 아래에 작성해주세요 */} + {/* 예시 코드 */} + Click me`} /> + +
+ {}} onClickActivity={() => {}} /> + {}} onClickActivity={() => {}} /> + {}} onClickActivity={() => {}} /> +
+ + ); +} diff --git a/packages/design-system/src/pages/SelectDoc.tsx b/packages/design-system/src/pages/SelectDoc.tsx index b890bdf7..8905d07f 100644 --- a/packages/design-system/src/pages/SelectDoc.tsx +++ b/packages/design-system/src/pages/SelectDoc.tsx @@ -49,6 +49,9 @@ export default function SelectDoc() { - \`\`을 생략하면 외부에서 직접 값을 사용해 Trigger 내부를 커스텀해야 합니다. - \`\`: Content 영역 최상단에 들어갈 타이틀 영역입니다. +### new +Select.Root에 disabled를 true로 전달하면 Select가 비활성화 처리됩니다. + `} propsDescription={` diff --git a/packages/design-system/src/pages/UpcomingScheduleDoc.tsx b/packages/design-system/src/pages/UpcomingScheduleDoc.tsx new file mode 100644 index 00000000..b205cf0b --- /dev/null +++ b/packages/design-system/src/pages/UpcomingScheduleDoc.tsx @@ -0,0 +1,32 @@ +import UpcomingSchedule from '@/components/UpcomingSchedule'; + +import DocTemplate, { DocCode } from '../layouts/DocTemplate'; + +/* Playground는 편집 가능한 코드 블록입니다. */ +/* Playground에서 사용할 예시 코드를 작성해주세요. */ +// const code = `예시 코드를 작성해주세요.`; + +export default function UpcomingScheduleDoc() { + return ( + <> + + {/* 실제 컴포넌트를 아래에 작성해주세요 */} + {/* 예시 코드 */} + Click me`} /> + + {}} /> + + ); +} diff --git a/packages/design-system/src/routes/index.tsx b/packages/design-system/src/routes/index.tsx index 58cae265..62bb1de8 100644 --- a/packages/design-system/src/routes/index.tsx +++ b/packages/design-system/src/routes/index.tsx @@ -14,10 +14,14 @@ import LandingPage from '@pages/LandingPage'; import LogoDoc from '@pages/LogoDoc'; import MainBannerDoc from '@pages/MainBannerDoc'; import MainCardDoc from '@pages/MainCardDoc'; +import MainPageSkeletonDoc from '@pages/MainPageSkeletonDoc'; import MainSearchInputDoc from '@pages/MainSearchInputDoc'; import ModalDoc from '@pages/ModalDoc'; +import MypageProfileHeaderDoc from '@pages/MypageProfileHeaderDoc'; +import MypageSummaryCardDoc from '@pages/MypageSummaryCardDoc'; import NoResultDoc from '@pages/NoResultDoc'; import NotificationCardDoc from '@pages/NotificationCardDoc'; +import OngoingExperienceCardDoc from '@pages/OngoingExperienceCardDoc'; import PaginationDoc from '@pages/PaginationDoc'; import PopoverDoc from '@pages/PopoverDoc'; import ProfileImageInputDoc from '@pages/ProfileImageInputDoc'; @@ -27,11 +31,13 @@ import StarRatingDoc from '@pages/StarRatingDoc'; import TextareaDoc from '@pages/TextareaDoc'; import TimePickerDoc from '@pages/TimePickerDoc'; import ToastDoc from '@pages/ToastDoc'; +import UpcomingScheduleDoc from '@pages/UpcomingScheduleDoc'; import { createBrowserRouter, Navigate } from 'react-router-dom'; import FooterDoc from '@/pages/FooterDoc'; import OwnerBadgeDoc from '@/pages/OwnerBadgeDoc'; import ReservationInfoCardDoc from '@/pages/ReservationInfoCardDoc'; +import SelectDoc from '@/pages/SelectDoc'; import UserBadgeDoc from '@/pages/UserBadgeDoc'; const router = createBrowserRouter([ @@ -47,10 +53,30 @@ const router = createBrowserRouter([ path: '', element: , }, + { + path: 'Select', + element: , + }, { path: 'TimePicker', element: , }, + { + path: 'OngoingExperienceCard', + element: , + }, + { + path: 'UpcomingSchedule', + element: , + }, + { + path: 'MypageSummaryCard', + element: , + }, + { + path: 'MypageProfileHeader', + element: , + }, { path: 'ProfileImageInput', element: , @@ -171,6 +197,10 @@ const router = createBrowserRouter([ path: 'AddressInput', element: , }, + { + path: 'MainPageSkeleton', + element: , + }, ], }, ]);