diff --git a/DONE.md b/DONE.md index 6fdd94c..efca004 100644 --- a/DONE.md +++ b/DONE.md @@ -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 @@ -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 diff --git a/TODO.md b/TODO.md index 698b207..796126a 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx new file mode 100644 index 0000000..ce7eef0 --- /dev/null +++ b/src/components/Button/Button.tsx @@ -0,0 +1,52 @@ +import { Box, CircularProgress } from '@mui/material' +import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react' + +interface ButtonProps extends ButtonHTMLAttributes { + children: ReactNode + isLoading?: boolean + icon?: ReactNode + fullWidth?: boolean +} + +const Button = forwardRef( + ( + { + children, + isLoading = false, + icon, + fullWidth = false, + className = '', + ...rest + }, + ref, + ) => { + return ( + + ) + }, +) + +Button.displayName = 'Button' + +export default Button diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts new file mode 100644 index 0000000..3389ecb --- /dev/null +++ b/src/components/Button/index.ts @@ -0,0 +1 @@ +export { default } from './Button' diff --git a/src/components/Forms/Review/ReviewForm.tsx b/src/components/Forms/Review/ReviewForm.tsx new file mode 100644 index 0000000..2ab94da --- /dev/null +++ b/src/components/Forms/Review/ReviewForm.tsx @@ -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(false) + const [rating, setRating] = useState(null) + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { comment: '' }, + }) + + const getProps = ( + key: keyof typeof validations, + options?: RegisterOptions, + ) => ({ + ...register(key, { + ...validations[key], + ...options, + } as RegisterOptions), + error: !!errors[key], + helperText: errors[key] && errors[key]?.message, + }) + + const onSubmit: SubmitHandler = 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 ( + + + + + Rate the Game + + { + if (v) { + setRating(v) + + return + } + + setRating(null) + }} + max={5} + size="large" + sx={{ + color: '#ff4d4d', + '& .MuiRating-iconFilled': { + color: '#ff4d4d', + }, + '& .MuiRating-iconHover': { + color: '#ff6666', + }, + }} + /> + {rating && ( + + Rating: {rating} + + )} + + + + Did you played this game? + + 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', + }, + }} + /> + + + + + + + + ) +} + +export default ReviewForm diff --git a/src/components/Forms/Review/index.ts b/src/components/Forms/Review/index.ts new file mode 100644 index 0000000..cb4120c --- /dev/null +++ b/src/components/Forms/Review/index.ts @@ -0,0 +1 @@ +export { default } from './ReviewForm' diff --git a/src/components/Forms/Review/validations.ts b/src/components/Forms/Review/validations.ts new file mode 100644 index 0000000..9c3dd7d --- /dev/null +++ b/src/components/Forms/Review/validations.ts @@ -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!', + }, + }, +} diff --git a/src/components/Forms/index.ts b/src/components/Forms/index.ts new file mode 100644 index 0000000..cf5ef87 --- /dev/null +++ b/src/components/Forms/index.ts @@ -0,0 +1 @@ +export { default as ReviewForm } from './Review' diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx new file mode 100644 index 0000000..f9c105e --- /dev/null +++ b/src/components/Input/Input.tsx @@ -0,0 +1,101 @@ +import { + ForwardedRef, + forwardRef, + InputHTMLAttributes, + ReactNode, + TextareaHTMLAttributes, + useState, +} from 'react' +import { IoEyeOffOutline, IoEyeOutline } from 'react-icons/io5' + +interface InputProps extends InputHTMLAttributes { + 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, +) { + const [passVisible, setPassVisible] = useState(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 ( +
+ {label && ( + + )} + +
+ {area ? ( +