Skip to content

Commit

Permalink
add play music state
Browse files Browse the repository at this point in the history
  • Loading branch information
fachryadhitya committed May 4, 2022
1 parent 6dd3367 commit e46aea0
Show file tree
Hide file tree
Showing 21 changed files with 588 additions and 28 deletions.
58 changes: 58 additions & 0 deletions components/gradientLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Box, Text, Flex } from "@chakra-ui/layout";
import { Image, Skeleton } from "@chakra-ui/react";
import React from "react";

type Props = {
children?: React.ReactNode;
color?: string;
image?: string;
title?: string;
description?: string;
roundImage?: boolean;
subtitle?: string;
isLoading?: boolean;
};

const GradientLayout: React.FC<Props> = ({
color,
image,
roundImage,
title,
subtitle,
description,
children,
isLoading
}) => {
return (
<Skeleton height="100%" isLoaded={!isLoading}>
<Box
height="100%"
overflowY="auto"
bgGradient={`linear(${color}.500 0%, ${color}.600 15%, ${color}.700 40%, rgba(0,0,0,0.95) 75%)`}
>
<Flex bgColor={`{${color}.600}`} padding="40px" align="end">
<Box padding="20px">
<Image
boxSize="160px"
boxShadow="2xl"
src={image}
borderRadius={roundImage ? "100%" : "3px"}
/>
</Box>

<Box padding="20px" lineHeight="40px" color="white">
<Text fontWeight="bold" casing="uppercase" fontSize="x-small">
{subtitle}
</Text>

<Text fontSize={"6xl"}>{title}</Text>
<Text fontSize={"x-small"}>{description}</Text>
</Box>
</Flex>
<Box paddingY={"50px"}>{children}</Box>
</Box>
</Skeleton>
);
};

export default GradientLayout;
131 changes: 131 additions & 0 deletions components/player.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
ButtonGroup,
Box,
IconButton,
RangeSlider,
RangeSliderFilledTrack,
RangeSliderTrack,
RangeSliderThumb,
Center,
Flex,
Text,
} from "@chakra-ui/react";
import ReactHowler from "react-howler";
import React from "react";

import {
MdShuffle,
MdSkipPrevious,
MdSkipNext,
MdOutlinePlayCircleFilled,
MdOutlinePauseCircleFilled,
MdOutlineRepeat,
} from "react-icons/md";

import { useStoreActions } from "easy-peasy";

const Player = ({ songs, activeSong }) => {
const [playing, setPlaying] = React.useState(true);
const [index, setIndex] = React.useState(0);
const [seek, setSeek] = React.useState(0);
const [repeat, setRepeat] = React.useState(false);
const [shuffle, setShuffle] = React.useState(false);
const [duration, setDuration] = React.useState(0);

const setPlayState = (val: boolean) => {
setPlaying(val);
};

return (
<Box>
<Box>
<ReactHowler src={activeSong?.url} playing={playing} />
</Box>
<Center color="gray.600">
<ButtonGroup>
<IconButton
icon={<MdShuffle />}
outline="none"
variant="link"
aria-label="shuffle"
fontSize="24px"
color={shuffle ? "white" : "gray.600"}
onClick={() => setShuffle(!shuffle)}
/>
<IconButton
icon={<MdSkipPrevious />}
outline="none"
variant="link"
aria-label="skip"
fontSize="24px"
/>
{!playing ? (
<IconButton
icon={<MdOutlinePlayCircleFilled />}
outline="none"
variant="link"
aria-label="play"
fontSize="40px"
color="white"
onClick={() => setPlayState(true)}
/>
) : (
<IconButton
icon={<MdOutlinePauseCircleFilled />}
outline="none"
variant="link"
aria-label="pause"
fontSize="40px"
color="white"
onClick={() => setPlayState(false)}
/>
)}

<IconButton
icon={<MdSkipNext />}
outline="none"
variant="link"
aria-label="next"
fontSize="24px"
/>
<IconButton
icon={<MdOutlineRepeat />}
outline="none"
variant="link"
aria-label="repeat"
fontSize="24px"
color={repeat ? "white" : "gray.600"}
onClick={() => setRepeat(!repeat)}
/>
</ButtonGroup>
</Center>

<Box color="gray.600">
<Flex justify="center" align="center">
<Box width="10%">
<Text fontSize="xs">1:21</Text>
</Box>
<Box width="80%">
<RangeSlider
aria-label={["min", "max"]}
step={0.1}
min={0}
max={300}
id="player-ranges"
>
<RangeSliderTrack bg="gray.800">
<RangeSliderFilledTrack bg="gray.600" />
</RangeSliderTrack>
<RangeSliderThumb index={0} />
</RangeSlider>
</Box>
<Box width="10%" textAlign="right">
<Text fontSize="xs">321</Text>
</Box>
</Flex>
</Box>
</Box>
);
};

export default Player;
28 changes: 28 additions & 0 deletions components/playerBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Box, Flex, Text } from "@chakra-ui/layout";
import { useStoreState } from "easy-peasy";
import Player from "./player";

const PlayerBar = () => {
const songs = useStoreState(state => state.activeSongs);
const activeSong = useStoreState(state => state.activeSong);

console.log(activeSong);

return (
<Box height="100px" width="100vw" bg="gray.900">
<Flex align="center" padding="20px">
{activeSong ? (
<Box padding="20px" color="white" width="30%">
<Text fontSize="large">{activeSong.name}</Text>
<Text fontSize="sm">{activeSong.artist.name}</Text>
</Box>
) : null}
<Box width="40%">
{activeSong ? <Player songs={songs} activeSong={activeSong} /> : null}
</Box>
</Flex>
</Box>
);
};

export default PlayerBar;
5 changes: 3 additions & 2 deletions components/playerLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Box } from "@chakra-ui/layout";
import React from "react";
import PlayerBar from "./playerBar";
import Sidebar from "./sidebar";

type Props = {
Expand All @@ -13,10 +14,10 @@ const PlayerLayout: React.FC<Props> = ({ children }) => {
<Sidebar />
</Box>
<Box marginLeft={"250px"} marginBottom="100px">
{children}
<Box height="calc(100vh - 100px)">{children}</Box>
</Box>
<Box position={"absolute"} left="0" bottom="0">
player
<PlayerBar />
</Box>
</Box>
);
Expand Down
13 changes: 9 additions & 4 deletions components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import NextImage from "next/image";
import NextLink from "next/link";

import { IconType } from "react-icons/lib";
import { usePlaylists } from "../lib/hooks";

type navProps = {
name: string;
Expand Down Expand Up @@ -60,6 +61,7 @@ const musicMenu = [
const playlists = new Array(30).fill(1).map((_, i) => `Playlist ${i + 1}`);

const Sidebar = () => {
const { playlists } = usePlaylists();
return (
<Box
width={"100%"}
Expand Down Expand Up @@ -117,11 +119,14 @@ const Sidebar = () => {

<Box height="66%" overflowY="auto" paddingY="20px">
<List spacing={2}>
{playlists.map((playlist) => (
<ListItem paddingX="20px" key={playlist}>
{playlists?.map((playlist) => (
<ListItem paddingX="20px" key={playlist?.id}>
<LinkBox>
<NextLink href={"/"} passHref>
<LinkOverlay>{playlist}</LinkOverlay>
<NextLink href={{
pathname: "/playlist/[id]",
query: { id: playlist.id },
}} passHref>
<LinkOverlay>{playlist.name}</LinkOverlay>
</NextLink>
</LinkBox>
</ListItem>
Expand Down
68 changes: 68 additions & 0 deletions components/songsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Box } from "@chakra-ui/layout";
import { useStoreActions } from "easy-peasy";
import { Table, Thead, Tbody, Td, Tr, Th, IconButton } from "@chakra-ui/react";
import { BsFillPlayFill } from "react-icons/bs";
import { AiOutlineClockCircle } from "react-icons/ai";
import { formatDate, formatTime } from "../lib/formatter";

const SongsTable = ({ songs }) => {
const playSongs = useStoreActions((store: any) => store.changeActiveSongs);
const setActiveSong = useStoreActions((store: any) => store.changeActiveSong);

const handlePlay = (activeSong?) => {
setActiveSong(activeSong || songs[0]);
playSongs(songs);
};

return (
<Box bg="transparent" paddingX="40px" color="white">
<Box padding="10px" marginBottom="20px">
<Box marginBottom="30px">
<IconButton
icon={<BsFillPlayFill fontSize="30px" />}
colorScheme="green"
size="lg"
isRound
aria-label="Play"
onClick={() => handlePlay()}
/>
</Box>

<Table variant="unstyled">
<Thead borderBottom="1px solid" borderColor="rgba(255,255,255,0.2)">
<Tr>
<Th>#</Th>
<Th>Title</Th>
<Th>Date Added</Th>
<Th>
<AiOutlineClockCircle />
</Th>
</Tr>
</Thead>
<Tbody>
{songs.map((song, index) => (
<Tr
sx={{
transition: "all .3s ease-in-out",
":hover": {
bg: "rgba(255,255,255,0.1)",
},
}}
key={song?.id}
cursor="pointer"
onClick={() => handlePlay(song)}
>
<Td>{index + 1}</Td>
<Td>{song?.name}</Td>
<Td>{formatDate(song?.createdAt)}</Td>
<Td>{formatTime(song?.duration)}</Td>
</Tr>
))}
</Tbody>
</Table>
</Box>
</Box>
);
};

export default SongsTable;
19 changes: 14 additions & 5 deletions lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "./prisma";

export const validateRoutes = async (handler: any) => {
export const validateRoutes = (handler) => {
return async (req: NextApiRequest, res: NextApiResponse) => {
const { token } = req.cookies;

Expand All @@ -13,21 +13,30 @@ export const validateRoutes = async (handler: any) => {
const { id } = jwt.verify(token, String(process.env.JWT_SECRET)) as {
id: number;
};

user = await prisma.user.findUnique({
where: { id },
});

if (!user) {
throw new Error("User not found");
throw new Error("Not real user");
}
} catch (error) {
return res.status(401).json({ error: "Not Authorized" });
res.status(401);
res.json({ error: "Not Authorized" });
return;
}

return handler(req, res, user);
}

return res.status(401).json({ error: "Not Authorized" });
res.status(401);
res.json({ error: "Not Authorizied" });
};
};

export const validateToken = (token: string) => {
const user = jwt.verify(token, String(process.env.JWT_SECRET)) as {
id: number
};
return user;
};
7 changes: 6 additions & 1 deletion lib/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ export default function fetcher(url: string, data?: any) {
"Content-Type": "application/json",
},
body: data ? JSON.stringify(data) : null,
});
}).then((res) => {
if (!res.ok) {
throw new Error('fetch failed')
}
return res.json()
})
}
Loading

0 comments on commit e46aea0

Please sign in to comment.