Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 티클 상태에 따른 조건부 처리, 티클 시작/종료 API 연결 #329

Merged
merged 12 commits into from
Dec 2, 2024
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
7 changes: 7 additions & 0 deletions apps/api/src/ticle/dto/ticleDetailDto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { TicleStatus } from '@repo/types';

export class TickleDetailResponseDto {
@ApiProperty({
Expand Down Expand Up @@ -74,4 +75,10 @@ export class TickleDetailResponseDto {
description: '이미 참가신청을 했는지 여부',
})
alreadyApplied: boolean;

@ApiProperty({
example: 'inProgress',
description: '티클의 상태',
})
ticleStatus: TicleStatus;
}
4 changes: 2 additions & 2 deletions apps/api/src/ticle/ticle.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ export class TicleService {
const countQuery = this.ticleRepository
.createQueryBuilder('ticle')
.select('COUNT(*) as count')
.where('ticle.ticleStatus = :status', {
status: isOpen ? TicleStatus.OPEN : TicleStatus.CLOSED,
.where('ticle.ticleStatus IN (:...statuses)', {
status: isOpen ? [TicleStatus.OPEN, TicleStatus.IN_PROGRESS] : [TicleStatus.CLOSED],
});
const totalTicleCount = await countQuery.getRawOne();

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/api/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const getApplicantsTicle = async (ticleId: string) => {
};

const startTicle = async (ticleId: string) => {
const { data } = await axiosInstance.post('/dashboard/start', { ticleId });
const { data } = await axiosInstance.post(`/dashboard/${ticleId}/start`);

return data;
};
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/api/live.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import axiosInstance from './axios';

export const endTicle = async (ticleId: string) => {
const { data } = await axiosInstance.post(`/dashboard/${ticleId}/end`);

return data;
};
7 changes: 3 additions & 4 deletions apps/web/src/components/dashboard/apply/TicleInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface TicleInfoCardProps {
ticleTitle: string;
startTime: string;
endTime: string;
status: 'closed' | 'open';
status: 'closed' | 'open' | 'inProgress';
}

function TicleInfoCard({
Expand Down Expand Up @@ -48,9 +48,8 @@ function TicleInfoCard({
<span className="text-body1 text-main">{`${dateStr} ${timeRangeStr}`}</span>
</div>
</div>

<Button disabled={status === 'closed'} onClick={handleTicleParticipate}>
티클 참여하기
<Button disabled={status === 'closed'} onClick={handleTicleParticipate} className="w-36">
{status === 'closed' ? '종료된 티클' : '티클 참여하기'}
</Button>
</div>
</Link>
Expand Down
20 changes: 15 additions & 5 deletions apps/web/src/components/dashboard/open/TicleInfoCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MouseEvent } from 'react';

import PersonFilledIc from '@/assets/icons/person-filled.svg?react';
import Button from '@/components/common/Button';
import { useApplicantsTicle } from '@/hooks/api/dashboard';
import { useApplicantsTicle, useStartTicle } from '@/hooks/api/dashboard';
import useModal from '@/hooks/useModal';
import { formatDateTimeRange } from '@/utils/date';

Expand All @@ -14,21 +14,23 @@ interface TicleInfoCardProps {
ticleTitle: string;
startTime: string;
endTime: string;
status: 'closed' | 'open';
status: 'closed' | 'open' | 'inProgress';
}

function TicleInfoCard({ ticleId, ticleTitle, startTime, endTime, status }: TicleInfoCardProps) {
const { isOpen, onOpen, onClose } = useModal();

const { data: applicantsData } = useApplicantsTicle(ticleId.toString());
const { mutate: ticleStartMutate } = useStartTicle();
const { dateStr, timeRangeStr } = formatDateTimeRange(startTime, endTime);

const navigate = useNavigate();

if (!applicantsData) return;

const handleTicleParticipate = (e: MouseEvent<HTMLButtonElement>) => {
const handleTicleStart = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
ticleStartMutate(ticleId.toString());
navigate({ to: `/live/${ticleId}` });
};

Expand Down Expand Up @@ -63,8 +65,16 @@ function TicleInfoCard({ ticleId, ticleTitle, startTime, endTime, status }: Ticl
<PersonFilledIc className="fill-primary" />
</div>
</button>
<Button disabled={status === 'closed'} onClick={handleTicleParticipate}>
티클 시작하기
<Button
disabled={status === 'closed' || status === 'inProgress'}
onClick={handleTicleStart}
className="w-36"
>
{status === 'closed'
? '종료된 티클'
: status === 'inProgress'
? '시작된 티클'
: '티클 시작하기'}
</Button>
</div>
{isOpen && (
Expand Down
12 changes: 9 additions & 3 deletions apps/web/src/components/live/ControlBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
import { useMediasoupAction, useMediasoupState } from '@/contexts/mediasoup/context';
import useModal from '@/hooks/useModal';

const ControlBar = () => {
interface ControlBarProps {
isOwner: boolean;
onTicleEnd: () => void;
}

const ControlBar = ({ isOwner, onTicleEnd }: ControlBarProps) => {
const { isOpen, onClose, onOpen } = useModal();

const { socketRef } = useMediasoupState();
Expand All @@ -32,7 +37,7 @@
} else {
startScreenStream();
}
} catch (_) {

Check warning on line 40 in apps/web/src/components/live/ControlBar/index.tsx

View workflow job for this annotation

GitHub Actions / check

'_' is defined but never used
closeStream('screen');
}
};
Expand Down Expand Up @@ -61,9 +66,10 @@
}
};

const handleExit = (isOwner: boolean) => {
const handleExit = () => {
if (isOwner) {
socketRef.current?.emit(SOCKET_EVENTS.closeRoom);
onTicleEnd();
}

disconnect();
Expand Down Expand Up @@ -93,7 +99,7 @@
<ToggleButton type="exit" ActiveIcon={ExitIc} InactiveIcon={ExitIc} onToggle={onOpen} />
</div>
{isOpen && (
<ExitDialog isOpen={isOpen} isOwner={false} handleExit={handleExit} onClose={onClose} />
<ExitDialog isOpen={isOpen} isOwner={isOwner} handleExit={handleExit} onClose={onClose} />
)}
</>
);
Expand Down
28 changes: 26 additions & 2 deletions apps/web/src/components/live/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { useNavigate, useParams } from '@tanstack/react-router';
import { useEffect } from 'react';

import ControlBar from '@/components/live/ControlBar';
import StreamView from '@/components/live/StreamView';
import { useEndTicle } from '@/hooks/api/live';
import { useTicle } from '@/hooks/api/ticle';
import useMediasoup from '@/hooks/mediasoup/useMediasoup';

function MediaContainer() {
useMediasoup();

const { ticleId } = useParams({ from: '/_authenticated/live/$ticleId' });
const navigate = useNavigate({ from: '/live/$ticleId' });

const { data: ticleData } = useTicle(ticleId);
const { mutate: endTicleMutate } = useEndTicle();

const isOwner = ticleData?.isOwner || false;
const hanleTicleEnd = () => {
endTicleMutate(ticleId);
};

useEffect(() => {
if (ticleData?.ticleStatus === 'closed') {
alert('종료된 티클입니다.'); // TODO: toast로 교체
navigate({ to: '/' });
}
}, [ticleData?.ticleStatus, navigate]);

return (
<div className="flex h-dvh flex-col justify-between gap-y-4 bg-black">
<StreamView />
<footer className="flex w-full justify-end gap-4 px-8 pb-4 text-white">
<ControlBar />
<footer className="flex w-full items-center justify-between gap-4 px-8 pb-4 text-white">
<span className="text-body1 text-white">{ticleData?.title}</span>
<ControlBar isOwner={isOwner} onTicleEnd={hanleTicleEnd} />
</footer>
</div>
);
Expand Down
77 changes: 77 additions & 0 deletions apps/web/src/components/ticle/detail/CtaButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ReactNode } from 'react';
import { TicleStatus } from '@repo/types';

import TrashIc from '@/assets/icons/trash.svg?react';
import Button from '@/components/common/Button';

type ButtonState = 'owner' | 'applied' | 'closed' | 'default';

interface ButtonConfig {
onClick: (() => void) | undefined;
disabled: boolean;
content: ReactNode;
}

type ButtonConfigMap = Record<ButtonState, ButtonConfig>;

interface CtaButtonProps {
isOwner: boolean;
alreadyApplied: boolean;
ticleStatus: TicleStatus;
onDelete: () => void;
onApply: () => void;
}

function CtaButton({ isOwner, alreadyApplied, ticleStatus, onDelete, onApply }: CtaButtonProps) {
const getButtonState = (): ButtonState => {
if (isOwner) {
return 'owner';
}
if (alreadyApplied) {
return 'applied';
}
if (ticleStatus === 'closed') {
return 'closed';
}
return 'default';
};

const buttonConfig: ButtonConfigMap = {
owner: {
onClick: onDelete,
disabled: false,
content: (
<span className="flex items-center gap-1">
티클 삭제하기
<TrashIc className="fill-white" />
</span>
),
},
applied: {
onClick: undefined,
disabled: true,
content: '신청 완료',
},
closed: {
onClick: undefined,
disabled: true,
content: '종료된 티클',
},
default: {
onClick: onApply,
disabled: false,
content: '티클 신청하기',
},
};

const buttonState = getButtonState();
const config = buttonConfig[buttonState];

return (
<Button onClick={config.onClick} disabled={config.disabled}>
{config.content}
</Button>
);
}

export default CtaButton;
25 changes: 10 additions & 15 deletions apps/web/src/components/ticle/detail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { useParams } from '@tanstack/react-router';

import CalendarIc from '@/assets/icons/calendar.svg?react';
import ClockIc from '@/assets/icons/clock.svg?react';
import TrashIc from '@/assets/icons/trash.svg?react';
import Avatar from '@/components/common/Avatar';
import Badge from '@/components/common/Badge';
import Button from '@/components/common/Button';
import UserProfileDialog from '@/components/user/UserProfileDialog';
import { useApplyTicle, useDeleteTicle, useTicle } from '@/hooks/api/ticle';
import useModal from '@/hooks/useModal';
import { formatDateTimeRange } from '@/utils/date';

import CtaButton from './CtaButton';

function Detail() {
const { ticleId } = useParams({ from: '/_authenticated/ticle/$ticleId' });
const { data } = useTicle(ticleId);
Expand All @@ -34,7 +34,7 @@ function Detail() {
const { dateStr, timeRangeStr } = formatDateTimeRange(data.startTime, data.endTime);
return (
<div className="flex flex-col items-end gap-9">
<div className="surface-white flex w-[49.5rem] flex-col gap-9 rounded-lg border border-main p-10 shadow-normal">
<div className="flex w-[49.5rem] flex-col gap-9 rounded-lg border border-main bg-white p-10 shadow-normal">
<div className="flex flex-col gap-2">
<h1 className="w-full text-head1 text-main">{data.title}</h1>
<div className="flex gap-2.5">
Expand Down Expand Up @@ -84,18 +84,13 @@ function Detail() {
</div>
</div>
</div>
{data.isOwner ? (
<Button onClick={handleDeleteButtonClick}>
<span className="flex items-center gap-1">
티클 삭제하기
<TrashIc className="fill-white" />
</span>
</Button>
) : (
<Button onClick={handleApplyButtonClick} disabled={data.alreadyApplied}>
{data.alreadyApplied ? '신청 완료' : '티클 신청하기'}
</Button>
)}
<CtaButton
isOwner={data.isOwner}
alreadyApplied={data.alreadyApplied}
ticleStatus={data.ticleStatus}
onApply={handleApplyButtonClick}
onDelete={handleDeleteButtonClick}
/>
</div>
);
}
Expand Down
3 changes: 1 addition & 2 deletions apps/web/src/hooks/api/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ export const useStartTicle = () => {

return useMutation({
mutationFn: startTicle,
onSuccess: (_, ticleId) => {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['appliedTicleList'] });
queryClient.invalidateQueries({ queryKey: ['applicantsTicle', ticleId] });
},
});
};
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/hooks/api/live.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { endTicle } from '@/api/live';

export const useEndTicle = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: endTicle,
onSuccess: (_, ticleId) => {
queryClient.invalidateQueries({ queryKey: ['appliedTicleList'] });
queryClient.invalidateQueries({ queryKey: ['ticleList'] });
queryClient.invalidateQueries({ queryKey: ['ticle', ticleId] });
},
});
};
Loading