Skip to content

Commit

Permalink
feat: adding tabs for game details and torrent chat
Browse files Browse the repository at this point in the history
  • Loading branch information
felipebrsk committed Sep 3, 2024
1 parent 6d47213 commit 4d87090
Show file tree
Hide file tree
Showing 29 changed files with 1,106 additions and 340 deletions.
17 changes: 16 additions & 1 deletion DONE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
# 📝 TODO List
# 📝 DONE List

## 🚀 Project Roadmap

### Basic Components

- [x] Input
- [x] Backdrop
- [x] Logo
- [x] Icon
- [x] Header
- [x] Footer
- [x] Tabs
- [x] Button

### MVP (Minimum Viable Product)

- [x] Set up project repository
Expand Down Expand Up @@ -61,6 +72,10 @@
- [x] Display the DLC availability on platforms to be purchased
- [x] Display the game requirements for each OS and type (minimum, recommended or maximum)
- [x] Display the game languages availability
- [x] Features for games
- [x] Implement some tabs for the game detauls
- [x] Implement the views of a game
- [x] Display the game comments and the possibility to comment in a game

### Post MVP

Expand Down
11 changes: 10 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@

## 🚀 Project Roadmap

### Basic Components

- [ ] ...

### MVP (Minimum Viable Product)

- [ ] Display the game comments and the possibility to comment in a game
- [ ] Implement the search on system
- [ ] Search should display at least 100 games with no pagination on backend
- [ ] Search should have a debounce time on change
- [ ] Search should allow the user to filter category, genre, tag or platform
- [ ] Search should allow the user to sort by likes quantity, views quantity, release date or title
- [ ] Search should allow the user to change the quantities of items per page (pagination should work only on frontend)
- [ ] Game news and reviews section
- [ ] Fetch and display the latest gaming news
- [ ] Add reviews section with filtering options
Expand Down
52 changes: 52 additions & 0 deletions src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Box, CircularProgress } from '@mui/material'
import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react'

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode
isLoading?: boolean
icon?: ReactNode
fullWidth?: boolean
}

const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
children,
isLoading = false,
icon,
fullWidth = false,
className = '',
...rest
},
ref,
) => {
return (
<button
ref={ref}
className={`
flex items-center justify-center gap-2 px-6 py-3 rounded-md font-semibold text-white
transition-colors duration-300 disabled:bg-opacity-50 disabled:cursor-not-allowed
${fullWidth ? 'w-full' : 'w-auto'}
${isLoading ? 'bg-theme-red-900' : 'bg-theme-red-900 hover:bg-red-700'}
${className}
`}
disabled={isLoading}
{...rest}>
{isLoading ? (
<Box className="flex items-center justify-center">
<CircularProgress size={20} sx={{ color: 'white' }} />
</Box>
) : (
<>
{icon && <span className="flex items-center">{icon}</span>}{' '}
{children}
</>
)}
</button>
)
},
)

Button.displayName = 'Button'

export default Button
1 change: 1 addition & 0 deletions src/components/Button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Button'
144 changes: 144 additions & 0 deletions src/components/Forms/Review/ReviewForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import {
Box,
Grid2,
Rating as MuiRating,
Stack,
Switch,
Typography,
} from '@mui/material'
import { useState } from 'react'
import { RegisterOptions, SubmitHandler, useForm } from 'react-hook-form'
import toast from 'react-hot-toast'

import { Button, Input } from '@/components'
import { GameDetails, ReviewStore } from '@/types'

import { validations } from './validations'

interface ReviewFormProps {
game: GameDetails
}

function ReviewForm(props: ReviewFormProps) {
const { game } = props
const [played, setPlayed] = useState<boolean>(false)
const [rating, setRating] = useState<number | null>(null)
const {
register,
handleSubmit,
formState: { errors },
} = useForm<ReviewStore>({
defaultValues: { comment: '' },
})

const getProps = (
key: keyof typeof validations,
options?: RegisterOptions,
) => ({
...register(key, {
...validations[key],
...options,
} as RegisterOptions<ReviewStore>),
error: !!errors[key],
helperText: errors[key] && errors[key]?.message,
})

const onSubmit: SubmitHandler<ReviewStore> = async (data) => {
if (!rating) {
toast.error('You need to rate this game to submit a review.')

return
}

const payload: ReviewStore = {
...data,
played,
userId: 1,
rate: rating,
gameId: game.id,
}

console.log(payload)
}

return (
<Stack component="form" onSubmit={handleSubmit(onSubmit)}>
<Grid2 container spacing={1.5} mb={2}>
<Box className="w-full flex flex-col gap-2">
<Typography
variant="h6"
className="dark:text-white text-gray-600">
Rate the Game
</Typography>
<MuiRating
value={rating}
precision={0.5}
onChange={(_, v) => {
if (v) {
setRating(v)

return
}

setRating(null)
}}
max={5}
size="large"
sx={{
color: '#ff4d4d',
'& .MuiRating-iconFilled': {
color: '#ff4d4d',
},
'& .MuiRating-iconHover': {
color: '#ff6666',
},
}}
/>
{rating && (
<Typography className="block text-gray-400 font-semibold mt-1">
Rating: {rating}
</Typography>
)}
</Box>
<Box className="w-full flex flex-col gap-2">
<Typography
variant="h6"
className="dark:text-white text-gray-600">
Did you played this game?
</Typography>
<Switch
value={played}
checked={played}
onChange={(_, v) => setPlayed(v)}
sx={{
'& .MuiSwitch-switchBase': {
color: '#ff4d4d',
},
'& .MuiSwitch-switchBase.Mui-checked': {
color: '#ff4d4d',
},
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
backgroundColor: '#ff4d4d',
},
'& .MuiSwitch-track': {
backgroundColor: '#ccc',
},
}}
/>
</Box>

<Input
area
isFull
label="Comment"
placeholder="Write your comment here..."
{...getProps('comment')}
/>
</Grid2>

<Button>Submit</Button>
</Stack>
)
}

export default ReviewForm
1 change: 1 addition & 0 deletions src/components/Forms/Review/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ReviewForm'
20 changes: 20 additions & 0 deletions src/components/Forms/Review/validations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const validations = {
rate: {
required: 'Please, write a number between 0 and 10.',
min: { value: 0, message: 'The minimum value for a review is 0.' },
max: { value: 10, message: 'The maximum value for a review is 10.' },
},
played: {
required: 'Please, tell me if you already played this game.',
},
comment: {
minLength: {
value: 15,
message: 'Your review must have at least 15 characters.',
},
maxLength: {
value: 2000,
message: 'Your review can not have more than 2000 characters!',
},
},
}
1 change: 1 addition & 0 deletions src/components/Forms/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ReviewForm } from './Review'
101 changes: 101 additions & 0 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
ForwardedRef,
forwardRef,
InputHTMLAttributes,
ReactNode,
TextareaHTMLAttributes,
useState,
} from 'react'
import { IoEyeOffOutline, IoEyeOutline } from 'react-icons/io5'

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
error?: boolean
type?: string
placeholder?: string
icon?: ReactNode
isFull?: boolean
helperText?: string
area?: boolean
customClass?: string
label?: string
rows?: number
}

function Input(
{
icon,
isFull = false,
area = false,
type,
error,
label,
rows = 4,
helperText,
customClass,
...rest
}: InputProps,
ref: ForwardedRef<HTMLInputElement>,
) {
const [passVisible, setPassVisible] = useState<boolean>(false)

const toggleVisibility = () => {
setPassVisible((prev) => !prev)
}

const baseClass = `px-4 py-2 rounded-md bg-transparent dark:text-white text-black placeholder-gray-400 focus:outline-none transition-colors duration-300 ${
isFull ? 'w-full' : 'w-auto'
} ${error ? 'border-red-500' : 'border-zinc-600'} ${customClass}`

return (
<div className={`relative ${isFull ? 'w-full' : 'inline-block'}`}>
{label && (
<label className="block dark:text-white text-gray-600 font-semibold mb-1">
{label}
</label>
)}

<div className="flex items-center border border-zinc-600 rounded-md">
{area ? (
<textarea
rows={rows}
ref={ref as React.Ref<HTMLTextAreaElement>}
className={`${baseClass} resize-y flex-grow`}
{...(rest as TextareaHTMLAttributes<HTMLTextAreaElement>)}
/>
) : (
<input
ref={ref as React.Ref<HTMLInputElement>}
type={type === 'password' && passVisible ? 'text' : type}
className={`${baseClass} pr-12 flex-grow`}
{...rest}
/>
)}

{type === 'password' && (
<button
type="button"
onClick={toggleVisibility}
className="flex items-center text-gray-500 hover:text-white focus:outline-none p-2">
{passVisible ? (
<IoEyeOutline size={20} />
) : (
<IoEyeOffOutline size={20} />
)}
</button>
)}

{icon && (
<div className="flex items-center text-gray-500 p-2">{icon}</div>
)}
</div>

{error && helperText && (
<span className="text-red-500 flex justify-start mt-1 text-sm">
{helperText}
</span>
)}
</div>
)
}

export default forwardRef(Input)
1 change: 1 addition & 0 deletions src/components/Input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Input'
4 changes: 4 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { default as Icon } from './Icon'
export { default as Logo } from './Logo'
export { default as Input } from './Input'
export { default as Button } from './Button'
export { default as Header } from './Header'
export { default as Footer } from './Footer'
export { default as Backdrop } from './Backdrop'
Expand All @@ -8,3 +10,5 @@ export { default as Loadable } from './Loadable'
export { default as SwitchTheme } from './SwitchTheme'
export { default as LoadingScreen } from './LoadingScreen'
export { default as SwitchSidebar } from './SwitchSidebar'

export * from './Forms'
Loading

0 comments on commit 4d87090

Please sign in to comment.