Skip to content

Commit

Permalink
Feat: 스트리밍
Browse files Browse the repository at this point in the history
[BBB-157]
  • Loading branch information
msjang4 committed Nov 22, 2024
1 parent 38ab5a6 commit 2c4353c
Show file tree
Hide file tree
Showing 20 changed files with 4,423 additions and 73 deletions.
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export default function Home() {
const [myData, setMyData] = useRecoilState(userState);
return (
<>
<div className="text-center">
{/* <div className="text-center">
{myData?.username + '로 로그인된 상태입니다.'}
</div>
</div> */}
<div className="flex items-center justify-center min-h-screen bg-gradient-to-b from-white to-gray-300">
<div className="space-y-12">
<div className="ml-12 text-5xl grid grid-rows-3 grid-cols-2 gap-x-12 gap-y-3">
Expand Down
27 changes: 15 additions & 12 deletions app/users/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
'use client';

import { useEffect, useState } from 'react';
import {
Avatar,
AvatarFallback,
AvatarImage
} from '@/components/ui/avatar/avatar';
import { Badge } from '@/components/ui/badge/badge';
import { Button } from '@/components/ui/button/button';
import { Card, CardContent } from '@/components/ui/card/card';
import { Badge } from '@/components/ui/badge/badge';
import { Progress } from '@/components/ui/progress/progress';
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger
} from '@/components/ui/tabs/tabs';
import { Progress } from '@/components/ui/progress/progress';
import { Activity, Star, X } from 'lucide-react';
import { cn } from '@/lib/utils';
import getUsableMyCoupons from '@/lib/api/coupon/get-usable-my-coupons';
import { MyCoupon } from '@/types/coupon/my-coupon';
import getMyPointsHistories from '@/lib/api/points/get-my-points-histories';
import getMyProfile from '@/lib/api/users/my-profile';
import { cn } from '@/lib/utils';
import { MyCoupon } from '@/types/coupon/my-coupon';
import applyCoupon from '@/types/coupon/use-coupon';
import { User } from '@/types/user/user';
import { PointHistory } from '@/types/user/point-history';
import getMyProfile from '@/lib/api/users/my-profile';
import { User } from '@/types/user/user';
import { Activity, Star, X } from 'lucide-react';
import { useEffect, useState } from 'react';

export default function ProfilePage() {
const [myCoupons, setMyCoupons] = useState<MyCoupon[]>([]);
Expand All @@ -35,13 +35,14 @@ export default function ProfilePage() {
const [myProfile, setMyProfile] = useState<User>();
const [myPointsHistories, setMyPointsHistories] = useState<PointHistory[]>();

const fetchMyProfile = async () => {
const response = await getMyProfile();
return setMyProfile(response.data);
};
useEffect(() => {
const fetchMyProfile = async () => {
const response = await getMyProfile();
return setMyProfile(response.data);
};
fetchMyProfile();
fetchPointsHistory();
fetchCoupons();
}, []);

// const handleEditProfile = (e: React.FormEvent<HTMLFormElement>) => {
Expand Down Expand Up @@ -74,6 +75,8 @@ export default function ProfilePage() {
coupon.id === couponId ? { ...coupon, isUsed: true } : coupon
)
);
fetchMyProfile();
fetchPointsHistory();
}
};

Expand Down
63 changes: 63 additions & 0 deletions app/video/[assignmentId]/[taskId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';
import VideoPlayer from '@/components/ui/player/video-player';
import getSignedCookies from '@/lib/api/video/get-cookie';
import getVideo from '@/lib/api/video/get-video';
import { User } from '@/types/user/user';
import { AssignmentInfo } from '@/types/video/cloudfront';
import { useParams, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
export default function VideoPlayerPage() {
const params = useParams();
console.log(params);
const objectId = 'task/' + params['assignmentId'] + '/' + params['taskId'];
const videoId = useSearchParams().get('id');

const [assignment, setAssignment] = useState<AssignmentInfo | null>(null);

const [uploader, setUploader] = useState<User | null>(null);
const [src, setSource] = useState('');
const thumnail =
'https://devs-output-bucket.s3.ap-northeast-2.amazonaws.com/outputs/' +
objectId +
'/Default/Thumbnails/' +
params['taskId'] +
'.0000000.jpg';

useEffect(() => {
const domain = 'd2k9wcxfcmg2dn.cloudfront.net';
getVideo(videoId!).then((res) => {
console.log(res);
setAssignment(res.assignmentResult);
setUploader(res.profile);
});
getSignedCookies(objectId)
.then((res) => {
return res.signedUrl;
})
.then((signedUrl) =>
setSource(
'https://devs-output-bucket.s3.ap-northeast-2.amazonaws.com/outputs/' +
objectId +
'/Default/HLS/' +
params['taskId'] +
'.m3u8'
)
);
}, [objectId]);
return (
<div className="container mx-auto px-4 py-8 min-h-screen">
<div className="max-w-4xl mx-auto bg-white p-6 rounded-lg shadow-lg">
<VideoPlayer
src={src}
poster={thumnail}
title={assignment?.title ?? ''}
description={assignment?.description ?? ''}
uploaderName={uploader?.username ?? ''}
uploaderAvatar="/placeholder.svg?height=50&width=50"
views={88848}
uploadDate="2024. 05. 07."
/>
</div>
</div>
);
}
24 changes: 11 additions & 13 deletions components/header/user-profile-drop-down.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,18 @@ export default function UserProfileDropDown(user: User) {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[200px]">
<DropdownMenuItem>
<div className="w-full">
<Link href="/users/mypage" prefetch={false}>
마이페이지
</Link>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<div className="w-full" onClick={handleLogout}>
<Link href="/" prefetch={false}>
<Link href="/users/mypage" prefetch={false}>
<DropdownMenuItem>
<div className="w-full">마이페이지</div>
</DropdownMenuItem>
</Link>
<Link href="/" prefetch={false}>
<DropdownMenuItem>
<div className="w-full" onClick={handleLogout}>
로그아웃
</Link>
</div>
</DropdownMenuItem>
</div>
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
34 changes: 22 additions & 12 deletions components/study/button/video-upload-button.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
'use client';

import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button/button';
import { Upload } from 'lucide-react';
import { RefreshIcon } from '@/components/ui/icon/icon';
import checkVideoUploadStatus from '@/lib/api/study/check-video-upload-status';
import {
completeMultipartUpload,
generatePresignedUrl,
generateUploadId,
uploadVideo
} from '@/lib/api/video/video-upload';
import checkVideoUploadStatus from '@/lib/api/study/check-video-upload-status';
import { toast } from 'react-toastify';
import { useRecoilState } from 'recoil';
import { userState } from '@/recoil/userAtom';
import { BookRound } from '@/types/study/study-detail';
import { VideoUploadButtonProps } from '@/types/study/video-upload-button';
import { Upload } from 'lucide-react';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useRecoilState } from 'recoil';

export default function VideoUploadButton({
studyType,
Expand All @@ -24,10 +25,14 @@ export default function VideoUploadButton({
const [myData, setMyData] = useRecoilState(userState);
const [myAssignmentId, setMyAssignmentId] = useState<number>();
const [file, setFile] = useState<File | null>(null);
const [isLoading, setLoading] = useState(false);

useEffect(() => {
if (studyType === 'BOOK' && myData) {
const BookRound = round as BookRound;
if (BookRound.users[myData.id]?.assignmentId == null) {
return;
}
setMyAssignmentId(BookRound.users[myData.id].assignmentId);
}
}, [studyType, myData, round]);
Expand All @@ -41,8 +46,8 @@ export default function VideoUploadButton({
};

const handleUpload = async () => {
if (file == null) return;

if (file == null || isLoading == true) return;
setLoading(true);
const completedParts = [];
const respose = await generateUploadId(studyId, myAssignmentId);
const uploadId = respose.data.uploadId;
Expand Down Expand Up @@ -105,11 +110,16 @@ export default function VideoUploadButton({
</span>
</Button>
</label>
{file && (
<Button size="sm" onClick={handleUpload}>
업로드
</Button>
)}
{file &&
(isLoading ? (
<Button className="mx-3 rounded-full w-12">
<RefreshIcon className="animate-spin" />
</Button>
) : (
<Button size="sm" onClick={handleUpload}>
업로드
</Button>
))}
</div>
);
}
16 changes: 13 additions & 3 deletions components/study/dashboard/book-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ export function BookRow({
}) {
const [isLoading, setIsLoading] = useState(false);
const [my, _] = useRecoilState(userState);

const handler = (videoId: number) => {
return () => {
location.href =
'/video/' + studyId + '/' + assignment.id + '?id=' + videoId;
};
};
return (
<TableRow>
<TableCell className="text-center">
Expand All @@ -44,8 +51,11 @@ export function BookRow({
</TableCell>
<TableCell className="text-center">
<div className="flex items-center justify-center">
{user.video ? (
<Button className="px-3 py-1 hover:bg-gray-600 bg-gray-900 flex items-center justify-center">
{user.videoIds.length !== 0 ? (
<Button
className="px-3 py-1 hover:bg-gray-600 bg-gray-900 flex items-center justify-center"
onClick={handler(user.videoIds[0])}
>
<PlayIcon className="w-5 h-5 stroke-white" />
</Button>
) : (
Expand All @@ -56,7 +66,7 @@ export function BookRow({

<TableCell className="text-center">
<div className="flex items-center justify-center">
{user.quiz ? (
{user.problemIds.length !== 0 ? (
<Button className="px-3 py-1 hover:bg-gray-600 bg-gray-900 flex items-center justify-center">
<PuzzleIcon className="w-5 h-5 stroke-white" />
</Button>
Expand Down
23 changes: 17 additions & 6 deletions components/study/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import VideoUploadButton from '@/components/study/button/video-upload-button';
import { Row } from '@/components/study/dashboard/row';
import {
Select,
Expand Down Expand Up @@ -25,9 +26,9 @@ import {
} from '@/types/study/study-detail';
import { useParams } from 'next/navigation';
import { useRecoilState } from 'recoil';
import { Button } from '../../ui/button/button';
import FeedbackDialog from '../feedback-dialog';
import { BookRow } from './book-row';
import VideoUploadButton from '@/components/study/button/video-upload-button';

export default function StudyDashBoard({
details,
Expand Down Expand Up @@ -89,13 +90,23 @@ function AlgorithmDashBoardBody({
{Object.entries(round.problems).map(
([problemId, problem]: [string, AlgorithmProblemInfo], index) => (
<TableHead key={index} className="text-center">
<p>{problem.title}</p>
<Button
className={myTasks?.[Number(problemId)] ? 'mt-2' : ''}
onClick={() => {
window.open(problem.link, '_blank')!.focus();
}}
>
<p>{problem.title}</p>
</Button>

{myTasks?.[Number(problemId)] && (
<FeedbackDialog
problem={{ ...problem, problemId: Number(problemId) }}
studyId={studyId}
></FeedbackDialog>
<>
<br></br>
<FeedbackDialog
problem={{ ...problem, problemId: Number(problemId) }}
studyId={studyId}
></FeedbackDialog>
</>
)}
</TableHead>
)
Expand Down
1 change: 1 addition & 0 deletions components/study/dashboard/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function Row({
// },
// []
// );
setIsLoading(true);
const problemIds = Object.keys(problems).map(Number);
updateTaskStatus({ studyId, roundIdx, problemIds, userId });
};
Expand Down
2 changes: 1 addition & 1 deletion components/study/feedback-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function FeedbackDialog({
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" className="px-1 py-0 h-min">
<Button variant="outline" className="m-2 px-1 py-0 h-min">
난이도 피드백
</Button>
</DialogTrigger>
Expand Down
Loading

0 comments on commit 2c4353c

Please sign in to comment.