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
16 changes: 16 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,23 @@
media="print"
onload="this.media = 'all'"
/>
<script type="text/javascript">
window.addEventListener('load', function () {
setTimeout(function () {
var gaScript = document.createElement('script');
gaScript.async = true;
gaScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-MWK0L3E9EK';
document.head.appendChild(gaScript);

window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-MWK0L3E9EK');
}, 2000);
});
</script>
<script type="text/javascript">
window.addEventListener('load', function () {
setTimeout(function () {
Expand Down
19 changes: 18 additions & 1 deletion src/components/common/layout/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { useNavigate } from 'react-router-dom';

import { ChevronLeft, Settings } from 'lucide-react';
import { ChevronLeft, LogOut, Settings } from 'lucide-react';

import { cn } from '@/lib/utils';

import { LeaveButton } from '@/features/meeting/setting/LeaveButton';

type HeaderProps = {
title: string;
showBackButton?: boolean;
showSettingButton?: boolean;
showLeaveButton?: boolean;
onBack?: () => void;
onLeave?: () => void;
className?: string;
};

export function Header({
title,
showBackButton = true,
showSettingButton = false,
showLeaveButton = false,
onBack,
onLeave = () => {},
className,
}: HeaderProps) {
const navigate = useNavigate();
Expand Down Expand Up @@ -53,6 +59,17 @@ export function Header({
<Settings className="h-6 w-6" />
</button>
)}
{showLeaveButton && (
<LeaveButton onLeave={onLeave}>
<button
type="button"
className="inline-flex h-10 w-10 cursor-pointer items-center justify-center"
aria-label="모임 나기기"
>
<LogOut className="h-6 w-6" />
</button>
</LeaveButton>
)}
</header>
);
}
8 changes: 6 additions & 2 deletions src/features/Time/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export default function Calendar({
<div className="relative z-20 w-full">
<div className="w-full" style={{ height: `${ROW_HEIGHT + 24}px` }}>
<div
className="overflow-hidden bg-white px-10 transition-all duration-300 ease-in-out"
className={cn(
'overflow-hidden bg-white px-10 transition-all duration-300 ease-in-out',
'border-r border-l border-gray-100',
)}
style={{ height: isOpen ? `${contentHeight}px` : `${ROW_HEIGHT}px` }} //열리면 달력 크기로
>
<div
Expand Down Expand Up @@ -128,7 +131,8 @@ export default function Calendar({
type="button"
onClick={() => setIsOpen(!isOpen)}
className={cn(
'-mt-px h-6 w-full rounded-b-xl bg-white text-gray-400 transition-colors hover:bg-gray-50',
'-mt-px h-6 w-full rounded-b-3xl bg-white text-gray-400 transition-colors hover:bg-gray-50',
'border-r border-b border-l border-r-gray-100 border-b-gray-100 border-l-gray-100',
'flex cursor-pointer items-center justify-center',
)}
aria-label={isOpen ? '달력 닫기' : '달력 펼치기'}
Expand Down
10 changes: 9 additions & 1 deletion src/features/Time/DateNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,26 @@ import { cn } from '@/lib/utils';
interface DateNavigatorProps {
selectedDate: Date;
isPrevDisabled: boolean;
dateType: string;
handlePrevClick: () => void;
handleNextClick: () => void;
}

export default function DateNavigator({
selectedDate,
isPrevDisabled,
dateType,
handlePrevClick,
handleNextClick,
}: DateNavigatorProps) {
return (
<div className="flex-eow flex items-center justify-center gap-10 bg-white pt-4 pb-2 text-lg font-bold text-gray-800">
<div
className={cn(
'flex-eow flex items-center justify-center gap-10 bg-white pt-4 pb-2 text-lg font-bold text-gray-800',

dateType !== 'WEEKLY' && 'border-r border-l border-gray-100',
)}
>
<button // 이전 주/달 버튼
onClick={handlePrevClick}
disabled={isPrevDisabled}
Expand Down
1 change: 1 addition & 0 deletions src/features/Time/TimeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export default function TimeHeader({
{/* 달력 헤더 - 특정 날짜 일때 */}
{dateType === 'SPECIFIC_DATE' && (
<DateNavigator
dateType={dateType}
handleNextClick={handleNextClick}
handlePrevClick={handlePrevClick}
isPrevDisabled={isPrevDisabled}
Expand Down
1 change: 1 addition & 0 deletions src/features/Time/TimeHeatMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ export default function TimeHeatMap({
}}
className={cn(
'relative transition-colors duration-100',
'bg-gray-100/40',
mode === 'INPUT' ? 'cursor-pointer' : '',
isTopHour
? 'border-t border-gray-200'
Expand Down
2 changes: 2 additions & 0 deletions src/features/Time/WeekdayBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export default function WeekdayBar({
<div
className={cn(
'flex flex-row justify-between bg-white px-10 pt-2 text-center',

dateType !== 'WEEKLY' && 'border-r border-l border-gray-100',
dateType === 'WEEKLY' ? 'border-b border-gray-200 pb-2' : '', // 주간 이면 하단 보더 생성
)}
>
Expand Down
14 changes: 14 additions & 0 deletions src/features/api/participantApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
type JoinMeetingRequest,
type JoinMeetingResponse,
type LeaveMeetingResponse,
type TransferHostRequest,
type TransferHostResponse,
} from '../../types/apiTypes';
import { axiosInstance } from './axiosInstance';

Expand Down Expand Up @@ -41,6 +43,18 @@ export const getMyStatus = async (code: string): Promise<GetMyStatusResponse> =>
return data;
};

//모임장 양도
export const transferHost = async (
code: string,
body: TransferHostRequest,
): Promise<TransferHostResponse> => {
const { data } = await axiosInstance.post<TransferHostResponse>(
`/meetings/${code}/participants/host`,
body,
);
return data;
};

//모임 나가기
//요청 X
//반환 LeaveMeetingResponse
Expand Down
34 changes: 25 additions & 9 deletions src/features/meeting/general/ParticipantStatusItem.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Clock, MapPin } from 'lucide-react';
import { Clock, Crown, MapPin } from 'lucide-react';

import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
Expand All @@ -7,28 +7,33 @@ import { type ParticipantStatus } from '@/types/meetingTypes';

interface ParticipantStatusItemProps extends ParticipantStatus {
isMe: boolean;
isLast: boolean;
isHost: boolean;
isTimeRecommendEnabled: boolean;
isPlaceRecommendEnabled: boolean;
isLoading?: boolean;
isClickable: boolean;
onClick: () => void;
}

export function ParticipantStatusItem({
nickName,
isMe,
hasTimeInput,
hasPlaceInput,
isLast,
isHost,
isTimeRecommendEnabled,
isPlaceRecommendEnabled,
isLoading = false,
}: ParticipantStatusItemProps & { isLast: boolean }) {
isClickable,
onClick,
}: ParticipantStatusItemProps) {
return (
<div
<button
className={cn(
'mx-3 flex items-center justify-between py-3',
!isLast && 'border-b-2 border-gray-100',
'flex w-full items-center justify-between rounded-3xl p-2',
isClickable && !isMe && 'cursor-pointer hover:bg-gray-100',
)}
onClick={onClick}
>
{/* 프로필 및 닉네임 */}
<div className="flex items-center justify-center gap-3 text-base font-bold text-black">
Expand All @@ -46,11 +51,22 @@ export function ParticipantStatusItem({
{isMe && !isLoading && (
<Badge
variant="secondary"
className="bg-greedy/10 text-greedy hover:bg-greedy/10 h-5 w-5 rounded-full border-none p-3 text-xs font-bold"
className="bg-greedy/10 text-greedy h-5 w-5 rounded-full border-none p-3 text-xs font-bold"
>
</Badge>
)}
{isHost && !isLoading && (
<div
className={cn(
'flex h-6 w-6 items-center justify-center rounded-full transition-colors',
'bg-amber-100 text-amber-600',
isLoading ? 'bg-gray-100 text-gray-100' : '',
)}
>
<Crown className="h-3 w-3" />
</div>
)}
</div>

<div className="flex items-center gap-2">
Expand Down Expand Up @@ -80,6 +96,6 @@ export function ParticipantStatusItem({
</div>
)}
</div>
</div>
</button>
);
}
95 changes: 81 additions & 14 deletions src/features/meeting/general/ParticipantStatusList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import React, { useState } from 'react';

import { ChevronDown, ChevronUp } from 'lucide-react';

import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { cn } from '@/lib/utils';

import { ParticipantStatusItem } from './ParticipantStatusItem';
Expand All @@ -14,6 +24,8 @@ interface ParticipantStatusListProps {
isTimeRecommendEnabled: boolean;
isPlaceRecommendEnabled: boolean;
isLoading?: boolean;
isHost: boolean;
onTransferHost: (nickName: string) => void;
}

export function ParticipantStatusList({
Expand All @@ -22,8 +34,11 @@ export function ParticipantStatusList({
isTimeRecommendEnabled,
isPlaceRecommendEnabled,
isLoading = false,
isHost,
onTransferHost,
}: ParticipantStatusListProps) {
const [isExpanded, setIsExpanded] = useState(false);
const [targetNickName, setTargetNickName] = useState<string | null>(null);

//처음에는 최대 3명만 보기
const visibleList = isExpanded ? list : list.slice(0, 3);
Expand All @@ -32,21 +47,40 @@ export function ParticipantStatusList({
return (
<div className={cn('flex flex-col gap-1', className)}>
<div className="overflow-hidden rounded-3xl border-2 border-gray-200 bg-gray-50">
<div className="flex flex-col">
{visibleList.map((participant, index) => (
<ParticipantStatusItem
key={`${participant.nickName}-${index}`}
{...participant}
isMe={index === 0}
hasTimeInput={participant.hasTimeInput}
hasPlaceInput={participant.hasPlaceInput}
isLast={index === visibleList.length - 1 && !showExpandButton}
isTimeRecommendEnabled={isTimeRecommendEnabled}
isPlaceRecommendEnabled={isPlaceRecommendEnabled}
isLoading={isLoading}
/>
))}
<div className="flex flex-col p-1">
{visibleList.map((participant, index) => {
const isMe = index === 0;
const canTransfer = isHost && !isMe; // 방장이고 본인이 아닐 때만 클릭 가능

return (
<div key={`${participant.nickName}-${index}`}>
<ParticipantStatusItem
{...participant}
isMe={isMe}
hasTimeInput={participant.hasTimeInput}
hasPlaceInput={participant.hasPlaceInput}
isHost={participant.isHost}
isTimeRecommendEnabled={isTimeRecommendEnabled}
isPlaceRecommendEnabled={isPlaceRecommendEnabled}
isLoading={isLoading}
isClickable={canTransfer}
onClick={() => {
if (canTransfer) {
setTargetNickName(participant.nickName);
}
}}
/>

{index !== visibleList.length - 1 && (
<div className="px-3 py-1">
<div className="h-0.5 w-full bg-gray-100" />
</div>
)}
</div>
);
})}
</div>

{/* 모든 참여자 리스트 보기 */}
{showExpandButton && (
<button
Expand All @@ -68,6 +102,39 @@ export function ParticipantStatusList({
</button>
)}
</div>

{/* 리스트 바깥에서 단일 모달로 렌더링 (상태에 따라 열림/닫힘) */}
<AlertDialog
open={targetNickName !== null}
onOpenChange={(isOpen) => {
if (!isOpen) setTargetNickName(null);
}}
>
<AlertDialogContent className="w-[90%] rounded-2xl">
<AlertDialogHeader>
<AlertDialogTitle>모임장을 양도할까요?</AlertDialogTitle>
<AlertDialogDescription>
{targetNickName}님에게 모임장 권한을 넘겨요
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className="flex-row gap-2">
<AlertDialogCancel className="h-10 flex-1 cursor-pointer rounded-xl border-2 bg-white shadow-none! hover:bg-gray-100">
취소
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (targetNickName) {
onTransferHost(targetNickName);
setTargetNickName(null); // 양도 후 모달 닫기
}
}}
className="bg-greedy! hover:bg-greedy/50! h-10 flex-1 cursor-pointer rounded-xl text-white shadow-none!"
>
양도하기
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
}
Loading
Loading