Skip to content

Commit

Permalink
Merge pull request #1 from Hendrixer/step-7
Browse files Browse the repository at this point in the history
Step 7
  • Loading branch information
Hendrixer authored Dec 15, 2021
2 parents e1d58a7 + a8fe894 commit 24065de
Show file tree
Hide file tree
Showing 37 changed files with 1,315 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ module.exports = {
],
'prettier/prettier': 'warn',
},
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local
.env

# vercel
.vercel
6 changes: 6 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
trailingComma: "es5",
tabWidth: 2,
semi: false,
singleQuote: true,
}
65 changes: 65 additions & 0 deletions components/authForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Box, Flex, Input, Button } from '@chakra-ui/react'
import { useRouter } from 'next/router'
import { FC, useState } from 'react'
import { useSWRConfig } from 'swr'
import NextImage from 'next/image'
import { auth } from '../lib/mutations'

const AuthForm: FC<{ mode: 'signin' | 'signup' }> = ({ mode }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()

const handleSubmit = async (e) => {
e.preventDefault()
setIsLoading(true)

await auth(mode, { email, password })
setIsLoading(false)
router.push('/')
}

return (
<Box height="100vh" width="100vw" bg="black" color="white">
<Flex
justify="center"
align="center"
height="100px"
borderBottom="white 1px solid"
>
<NextImage src="/logo.svg" height={60} width={120} />
</Flex>
<Flex justify="center" align="center" height="calc(100vh - 100px)">
<Box padding="50px" bg="gray.900" borderRadius="6px">
<form onSubmit={handleSubmit}>
<Input
placeholder="email"
type="email"
onChange={(e) => setEmail(e.target.value)}
/>
<Input
placeholder="password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
<Button
type="submit"
bg="green.500"
isLoading={isLoading}
sx={{
'&:hover': {
bg: 'green.300',
},
}}
>
{mode}
</Button>
</form>
</Box>
</Flex>
</Box>
)
}

export default AuthForm
41 changes: 41 additions & 0 deletions components/gradientLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Box, Flex, Text } from '@chakra-ui/layout'
import { Image } from '@chakra-ui/react'

const GradientLayout = ({
color,
children,
image,
subtitle,
title,
description,
roundImage,
}) => {
return (
<Box
height="100%"
overflowY="auto"
bgGradient={`linear(${color}.500 0%, ${color}.600 15%, ${color}.700 40%, rgba(0,0,0,0.95) 75%)`}
>
<Flex bg={`${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 fontSize="x-small" fontWeight="bold" casing="uppercase">
{subtitle}
</Text>
<Text fontSize="6xl">{title}</Text>
<Text fontSize="x-small">{description}</Text>
</Box>
</Flex>
<Box paddingY="50px">{children}</Box>
</Box>
)
}

export default GradientLayout
220 changes: 220 additions & 0 deletions components/player.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import {
ButtonGroup,
Box,
IconButton,
RangeSlider,
RangeSliderFilledTrack,
RangeSliderTrack,
RangeSliderThumb,
Center,
Flex,
Text,
} from '@chakra-ui/react'
import ReactHowler from 'react-howler'
import { useEffect, useRef, useState } from 'react'
import {
MdShuffle,
MdSkipPrevious,
MdSkipNext,
MdOutlinePlayCircleFilled,
MdOutlinePauseCircleFilled,
MdOutlineRepeat,
} from 'react-icons/md'
import { useStoreActions } from 'easy-peasy'
import { formatTime } from '../lib/formatters'

const Player = ({ songs, activeSong }) => {
const [playing, setPlaying] = useState(true)
const [index, setIndex] = useState(
songs.findIndex((s) => s.id === activeSong.id)
)
const [seek, setSeek] = useState(0.0)
const [isSeeking, setIsSeeking] = useState(false)
const [repeat, setRepeat] = useState(false)
const [shuffle, setShuffle] = useState(false)
const [duration, setDuration] = useState(0.0)
const soundRef = useRef(null)
const repeatRef = useRef(repeat)
const setActiveSong = useStoreActions((state: any) => state.changeActiveSong)

useEffect(() => {
let timerId

if (playing && !isSeeking) {
const f = () => {
setSeek(soundRef.current.seek())
timerId = requestAnimationFrame(f)
}

timerId = requestAnimationFrame(f)
return () => cancelAnimationFrame(timerId)
}

cancelAnimationFrame(timerId)
}, [playing, isSeeking])

useEffect(() => {
setActiveSong(songs[index])
}, [index, setActiveSong, songs])

useEffect(() => {
repeatRef.current = repeat
}, [repeat])

const setPlayState = (value) => {
setPlaying(value)
}

const onShuffle = () => {
setShuffle((state) => !state)
}

const onRepeat = () => {
setRepeat((state) => !state)
}

const prevSong = () => {
setIndex((state) => {
return state ? state - 1 : songs.length - 1
})
}

const nextSong = () => {
setIndex((state) => {
if (shuffle) {
const next = Math.floor(Math.random() * songs.length)

if (next === state) {
return nextSong()
}
return next
}

return state === songs.length - 1 ? 0 : state + 1
})
}

const onEnd = () => {
if (repeatRef.current) {
setSeek(0)
soundRef.current.seek(0)
} else {
nextSong()
}
}

const onLoad = () => {
const songDuration = soundRef.current.duration()
setDuration(songDuration)
}

const onSeek = (e) => {
setSeek(parseFloat(e[0]))
soundRef.current.seek(e[0])
}

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

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

<Box color="gray.600">
<Flex justify="center" align="center">
<Box width="10%">
<Text fontSize="xs">{formatTime(seek)}</Text>
</Box>
<Box width="80%">
<RangeSlider
aria-label={['min', 'max']}
step={0.1}
min={0}
id="player-range"
max={duration ? duration.toFixed(2) : 0}
onChange={onSeek}
value={[seek]}
onChangeStart={() => setIsSeeking(true)}
onChangeEnd={() => setIsSeeking(false)}
>
<RangeSliderTrack bg="gray.800">
<RangeSliderFilledTrack bg="gray.600" />
</RangeSliderTrack>
<RangeSliderThumb index={0} />
</RangeSlider>
</Box>
<Box width="10%" textAlign="right">
<Text fontSize="xs">{formatTime(duration)}</Text>
</Box>
</Flex>
</Box>
</Box>
)
}

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

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

return (
<Box height="100px" width="100vw" bg="gray.900" padding="10px">
<Flex align="center">
{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
Loading

0 comments on commit 24065de

Please sign in to comment.