diff --git a/apps/video-trading/components/CategoryCard.tsx b/apps/video-trading/components/CategoryCard.tsx index c1ff810..8d1416c 100644 --- a/apps/video-trading/components/CategoryCard.tsx +++ b/apps/video-trading/components/CategoryCard.tsx @@ -7,13 +7,15 @@ interface Props { * use card component to wrap the category card */ useCard: boolean; + keywords: string[]; } -export default function CategoryCard({ useCard }: Props) { +export default function CategoryCard({ useCard, keywords }: Props) { const chips = ( - - + {keywords.map((keyword, index) => ( + + ))} ); diff --git a/apps/video-trading/components/VideoCardSmall.tsx b/apps/video-trading/components/VideoCardSmall.tsx index b2821d7..e4a42d9 100644 --- a/apps/video-trading/components/VideoCardSmall.tsx +++ b/apps/video-trading/components/VideoCardSmall.tsx @@ -14,10 +14,10 @@ export default function VideoCardSmall({ video }: Props) { {video.title} - {video.uid} + {video.title} - {video.views.toLocaleString("en-US")} views •{" "} + {"0"} views •{" "} {dayjs(video.created_at).format("MMM DD, YYYY")} diff --git a/apps/video-trading/components/upload/VideoUploadDialog.tsx b/apps/video-trading/components/upload/VideoUploadDialog.tsx index 1818386..d26d678 100644 --- a/apps/video-trading/components/upload/VideoUploadDialog.tsx +++ b/apps/video-trading/components/upload/VideoUploadDialog.tsx @@ -73,7 +73,7 @@ export default function VideoUploadDialog(props: Props) { } if (video) { - return video.size; + return 0; } return 0; }, [props.video, props.file]); diff --git a/apps/video-trading/components/video/VideoHistoryPage.tsx b/apps/video-trading/components/video/VideoHistoryPage.tsx new file mode 100644 index 0000000..03fc440 --- /dev/null +++ b/apps/video-trading/components/video/VideoHistoryPage.tsx @@ -0,0 +1,56 @@ +import { + Collapse, + LinearProgress, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + Stack, + Typography, +} from "@mui/material"; +import dayjs from "dayjs"; +import useUser from "../../hooks/useUser"; +import useVideoHistory from "../../hooks/useVideoHistory"; + +interface Props { + video: any; +} + +export default function VideoHistoryPage({ video }: Props) { + const histories = useVideoHistory(video); + + console.log("histories", histories); + + return ( + + + + + {histories.data?.map((history, index) => ( + + + + Purchased from + + + } + secondary={`HKD $${history.amount}`} + /> + + + {dayjs(history.created_at.seconds * 1000).format("YYYY-MM-DD")} + + + + ))} + + ); +} + +function TransactionText({ userRef }: { userRef: any }) { + const user = useUser(userRef); + + return {user.data?.display_name}; +} diff --git a/apps/video-trading/hooks/useComents.tsx b/apps/video-trading/hooks/useComents.tsx new file mode 100644 index 0000000..2eca084 --- /dev/null +++ b/apps/video-trading/hooks/useComents.tsx @@ -0,0 +1,30 @@ +import { useQuery } from "@tanstack/react-query"; +import { Comment } from "client"; +import { + collection, + getFirestore, + query, + where, + getDocs, +} from "firebase/firestore"; +import React from "react"; + +const firestore = getFirestore(); + +export default function useComents(video: any) { + const comments = useQuery(["comments", video], async () => { + const commentRef = query( + collection(firestore, "Comment"), + where("video", "==", video) + ); + + const querySnapshot = await getDocs(commentRef); + const comments = querySnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); + return comments as Comment[]; + }); + + return comments; +} diff --git a/apps/video-trading/hooks/useUser.tsx b/apps/video-trading/hooks/useUser.tsx new file mode 100644 index 0000000..85c3c8b --- /dev/null +++ b/apps/video-trading/hooks/useUser.tsx @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query"; +import React from "react"; +import { getDoc, doc, getFirestore } from "firebase/firestore"; + +const firestore = getFirestore(); + +export default function useUser(userId: any) { + const user = useQuery(["user", userId], async () => { + const document = await getDoc(userId); + if (document.exists()) { + return document.data() as any; + } else { + return undefined; + } + }); + + return user; +} diff --git a/apps/video-trading/hooks/useVideo.tsx b/apps/video-trading/hooks/useVideo.tsx new file mode 100644 index 0000000..1009fdf --- /dev/null +++ b/apps/video-trading/hooks/useVideo.tsx @@ -0,0 +1,29 @@ +import { useQuery } from "@tanstack/react-query"; +import { Video } from "client"; +import { + collection, + doc, + getDoc, + getFirestore, + limit, + query, +} from "firebase/firestore"; + +const firestore = getFirestore(); + +export default function useVideo(id: string) { + /** + * Get video by id + */ + const video = useQuery([id], async () => { + const docRef = doc(firestore, "Video", id); + const document = await getDoc(docRef); + if (document.exists()) { + return { id: id, ...document.data() } as Video; + } else { + return undefined; + } + }); + + return video; +} diff --git a/apps/video-trading/hooks/useVideoHistory.tsx b/apps/video-trading/hooks/useVideoHistory.tsx new file mode 100644 index 0000000..1703207 --- /dev/null +++ b/apps/video-trading/hooks/useVideoHistory.tsx @@ -0,0 +1,26 @@ +import { useQuery } from "@tanstack/react-query"; +import { collection, getDocs, query, where } from "firebase/firestore"; +import React from "react"; + +import { getFirestore } from "firebase/firestore"; +import { Transaction } from "client"; + +const firestore = getFirestore(); + +export default function useVideoHistory(video: any) { + const histories = useQuery(["histories", video], async () => { + const historyRef = query( + collection(firestore, "Transaction"), + where("video", "==", video) + ); + + const querySnapshot = await getDocs(historyRef); + const histories = querySnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); + return histories as Transaction[]; + }); + + return histories; +} diff --git a/apps/video-trading/hooks/useVideos.tsx b/apps/video-trading/hooks/useVideos.tsx new file mode 100644 index 0000000..a71fc23 --- /dev/null +++ b/apps/video-trading/hooks/useVideos.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +import { Video } from "client"; +import { + collection, + getDocs, + getFirestore, + limit, + query, +} from "firebase/firestore"; + +const firestore = getFirestore(); + +export default function useVideos() { + const [videos, setVideos] = React.useState([]); + + React.useEffect(() => { + const getVideos = async () => { + const q = query(collection(firestore, "Video"), limit(10)); + const querySnapshot = await getDocs(q); + const videos = querySnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); + setVideos(videos as Video[]); + }; + getVideos(); + }, []); + + return videos; +} diff --git a/apps/video-trading/package.json b/apps/video-trading/package.json index b8b165b..d937b4c 100644 --- a/apps/video-trading/package.json +++ b/apps/video-trading/package.json @@ -15,18 +15,20 @@ "@monaco-editor/react": "^4.4.5", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.10.1", - "@supabase/supabase-js": "^1.35.6", "@tanstack/react-query": "^4.2.1", "@tanstack/react-query-devtools": "^4.2.1", "@tanstack/react-virtual": "3.0.0-beta.18", + "@types/video-react": "^0.15.1", "client": "workspace:client", + "codevis": "workspace:codevis", "dayjs": "^1.11.5", + "firebase": "9.12.1", "monaco-editor": "^0.34.0", "next": "12.2.5", "react": "18.2.0", "react-dom": "18.2.0", "utils": "workspace:utils", - "codevis": "workspace:codevis" + "video-react": "^0.15.0" }, "devDependencies": { "@types/node": "18.7.9", diff --git a/apps/video-trading/pages/_app.tsx b/apps/video-trading/pages/_app.tsx index 3e3d809..e239be0 100644 --- a/apps/video-trading/pages/_app.tsx +++ b/apps/video-trading/pages/_app.tsx @@ -5,8 +5,19 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import Head from "next/head"; import { CodeVisulizationProvider } from "codevis"; +import { initializeApp } from "firebase/app"; +import { getAnalytics } from "firebase/analytics"; const queryClient = new QueryClient(); +const app = initializeApp({ + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, +}); function MyApp({ Component, pageProps }: AppProps) { return ( diff --git a/apps/video-trading/pages/index.tsx b/apps/video-trading/pages/index.tsx index fdffbb6..2f26dba 100644 --- a/apps/video-trading/pages/index.tsx +++ b/apps/video-trading/pages/index.tsx @@ -1,43 +1,25 @@ import { CardActionArea, CardMedia, - Chip, Grid, - Paper, Stack, Typography, } from "@mui/material"; -import type { NextPage } from "next"; import { Video } from "client"; -import { useVirtualizer } from "@tanstack/react-virtual"; -import React from "react"; -import CategoryCard from "../components/CategoryCard"; +import type { NextPage } from "next"; import { useRouter } from "next/router"; - -const videos: Video[] = Array(100) - .fill(1) - .map(() => ({ - id: "1", - title: "Video 1", - description: "This is a video", - cover: - "https://assets.xboxservices.com/assets/8c/bf/8cbfa53c-96c4-42e1-9aa0-290cc166033c.jpg?n=XGP-2022_Small-tout-0_12-17-21_1067x600.jpg", - source: "https://www.youtube.com/watch?v=QH2-TGUlwu4", - created_at: new Date(), - updated_at: new Date(), - uid: "1", - cid: "1", - views: 3000, - likes: 3000, - size: 200 * 1024 * 1024, - })); +import { useState } from "react"; +import CategoryCard from "../components/CategoryCard"; +import useVideos from "../hooks/useVideos"; +import dayjs from "dayjs"; const Home: NextPage = () => { const router = useRouter(); + const videos = useVideos(); return (
- + {videos.map((video, index) => ( @@ -53,7 +35,7 @@ const Home: NextPage = () => { /> {video.title} - {video.uid} + {dayjs(video.created_at.seconds * 1000).format("YYYY-MM-DD")} diff --git a/apps/video-trading/pages/watch/index.tsx b/apps/video-trading/pages/watch/index.tsx index 188ee2b..4a56e13 100644 --- a/apps/video-trading/pages/watch/index.tsx +++ b/apps/video-trading/pages/watch/index.tsx @@ -3,49 +3,44 @@ import { Box, Button, Card, + CardActionArea, CardContent, CardHeader, - CardMedia, + Dialog, + DialogContent, + DialogTitle, Divider, Grid, Stack, TextField, Typography, } from "@mui/material"; -import { Video } from "client"; -import { abbreviateNumber } from "utils"; -import { useRouter } from "next/router"; import dayjs from "dayjs"; -import React from "react"; +import { useRouter } from "next/router"; +import { abbreviateNumber } from "utils"; +import { Player } from "video-react"; -import ThumbUpIcon from "@mui/icons-material/ThumbUp"; -import ThumbDownIcon from "@mui/icons-material/ThumbDown"; -import VideoButton from "../../components/VideoButton"; import ShareIcon from "@mui/icons-material/Share"; +import ThumbDownIcon from "@mui/icons-material/ThumbDown"; +import ThumbUpIcon from "@mui/icons-material/ThumbUp"; +import "video-react/dist/video-react.css"; import CategoryCard from "../../components/CategoryCard"; -import VideoCardSmall from "../../components/VideoCardSmall"; -import { appBarHeight } from "../../config"; - -const video: Video = { - id: "1", - title: "Video 1", - description: "This is a video", - cover: - "https://img.freepik.com/free-photo/cloud-sky-twilight-times_74190-4017.jpg?w=2000", - source: "https://www.youtube.com/watch?v=QH2-TGUlwu4", - created_at: new Date(), - updated_at: new Date(), - size: 200 * 1024 * 1024, - uid: "1", - cid: "1", - views: 3000, - likes: 3000, -}; +import VideoButton from "../../components/VideoButton"; +import useUser from "../../hooks/useUser"; +import useVideo from "../../hooks/useVideo"; +import useComents from "../../hooks/useComents"; +import VideoHistoryPage from "../../components/video/VideoHistoryPage"; +import { useState } from "react"; +import { doc, getFirestore } from "firebase/firestore"; export default function Index() { const router = useRouter(); // get video id from url const videoId = router.query.v; + const video = useVideo(videoId as string); + const user = useUser(video.data?.owner as string); + const comments = useComents(video.data as any); + const [openVideoHistory, setOpenVideoHistory] = useState(false); return ( @@ -54,14 +49,12 @@ export default function Index() { {/* video */} - + {video.data && ( + + )} - {video.title} + {video.data?.title} {/* video info */} - {video.views.toLocaleString("en-US")} views •{" "} - {dayjs(video.created_at).format("MMM DD, YYYY")} + 0 views •{" "} + {dayjs(video.data?.created_at.seconds * 1000).format( + "MMM DD, YYYY" + )} } /> } /> @@ -89,14 +84,11 @@ export default function Index() { A {/* description */} - User name + {user.data?.display_name} 12K subscribers - - Recorded live at Reactathon 2022. Learn more at - https://reactathon.com Goodbye, useEffect - + {video.data?.description} {/* subscribe */} @@ -111,6 +103,19 @@ export default function Index() { 100 Comments + + { + setOpenVideoHistory(true); + }} + > + + + Show video trading history + + + + {/* add comment */} @@ -125,28 +130,24 @@ export default function Index() { {/* comments */} - - A - - - User name - - 12 months ago - - - - This is a comment. This is a comment. This is a comment. This is - a comment. This is a comment. This is a comment. This is a - comment. This is a comment. This is a comment. This is a - comment. This is a comment. This is a comment. This is a - comment. This is a comment. - - - } title="252" /> - } title="252" /> + {comments.data?.map((comment: any) => ( + + A + + + User name + + {dayjs(comment.created_at.seconds * 1000).format("MMM DD")} + + + {comment.content} + + } title="252" /> + } title="252" /> + - + ))} {/* right sidebar */} @@ -156,20 +157,35 @@ export default function Index() { - - OR - + - - {Array(100) + + {/* {Array(100) .fill(1) .map((_, i) => ( - ))} + ))} */} + + {video.data && ( + setOpenVideoHistory(false)} + > + Video trading history + + + + + )} ); } diff --git a/packages/client/src/clients/video.ts b/packages/client/src/clients/video.ts index 83976c5..81b2d67 100644 --- a/packages/client/src/clients/video.ts +++ b/packages/client/src/clients/video.ts @@ -1,75 +1,31 @@ -import { PostgrestError } from "@supabase/supabase-js"; -import { Client } from "../supabase"; - export interface Video { id: string; title: string; description: string; cover: string; - source: string; - /** - * Video Size in bytes - */ - size: number; - created_at: Date; - updated_at: Date; - uid: string; - cid: string; - views: number; + video: string; + created_at: any; + updated_at: any; likes: number; + owner: any; + tags: string[]; + price: number; } -type VideoResponse = { - data?: Video; - error?: PostgrestError; -}; - -/** - * A client which can be used to interact with the video service. - */ -export class VideoClient extends Client { - async createVideo(video: Video) { - return await this.client.from("video").insert(video); - } - - /** - * Get a video by id. - * @param id The id of the video. - * @returns - */ - async getVideo(id: string): Promise { - // use supabase client to get video - const { data, error } = await this.client - .from("video") - .select("*") - .eq("id", id); - - if (error !== null) { - return { data: undefined, error }; - } - - return { - data: data[0], - error: undefined, - }; - } - - /** - * List all video by pagination. - */ - async listVideos(page: number) { - const { from, to } = this.pagination(page); - const { data, error, count } = await this.client - .from