From ae8b992fcc7bccb271be79c9b3788c93b0c97bb4 Mon Sep 17 00:00:00 2001 From: KajiyamaVK Date: Tue, 11 Jul 2023 06:24:49 -0300 Subject: [PATCH] feat: Adds functionality for the button --- package-lock.json | 16 +++ package.json | 1 + src/components/CountDown/index.tsx | 52 ++++++++ src/components/CountDown/styles.ts | 24 ++++ .../NewCyclesForm}/formValidations.ts | 2 +- src/components/NewCyclesForm/index.tsx | 54 ++++++++ src/components/NewCyclesForm/styles.ts | 44 +++++++ src/pages/Home/index.tsx | 121 ++++++++---------- src/pages/Home/interfaces.tsx | 4 +- src/pages/Home/styles.ts | 81 ++---------- 10 files changed, 262 insertions(+), 137 deletions(-) create mode 100644 src/components/CountDown/index.tsx create mode 100644 src/components/CountDown/styles.ts rename src/{pages/Home => components/NewCyclesForm}/formValidations.ts (86%) create mode 100644 src/components/NewCyclesForm/index.tsx create mode 100644 src/components/NewCyclesForm/styles.ts diff --git a/package-lock.json b/package-lock.json index 3020f7a..fcf697a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@hookform/resolvers": "^3.1.1", "@types/styled-components": "^5.1.26", + "date-fns": "^2.30.0", "phosphor-react": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3562,6 +3563,21 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index a738bfa..4607872 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@hookform/resolvers": "^3.1.1", "@types/styled-components": "^5.1.26", + "date-fns": "^2.30.0", "phosphor-react": "^1.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/CountDown/index.tsx b/src/components/CountDown/index.tsx new file mode 100644 index 0000000..38d9319 --- /dev/null +++ b/src/components/CountDown/index.tsx @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react' +import { CountdownContainer, Separator } from './styles' +import { differenceInSeconds } from 'date-fns' + +export default function CountDown() { + const [amountSecondsPassed, setAmountSecondsPassed] = useState(0) + const totalSeconds = activeCycle ? activeCycle.minutesAmount * 60 : 0 + + useEffect(() => { + let interval: number + + if (activeCycle) { + interval = setInterval(() => { + const secondsDifference = differenceInSeconds( + new Date(), + activeCycle.startDate, + ) + + if (secondsDifference >= totalSeconds) { + setCycles((state) => + state.map((cycle) => { + if (cycle.id === activeCycle.id) { + const now = new Date() + return { ...cycle, finishedDate: now } + } else { + return cycle + } + }), + ) + setAmountSecondsPassed(totalSeconds) + clearInterval(interval) + } else { + setAmountSecondsPassed(secondsDifference) + } + }, 1000) + } + + return () => { + clearInterval(interval) + } + }, [activeCycle, totalSeconds, activeCycleId]) + + return ( + + {minutes[0]} + {minutes[1]} + : + {seconds[0]} + {seconds[1]} + + ) +} diff --git a/src/components/CountDown/styles.ts b/src/components/CountDown/styles.ts new file mode 100644 index 0000000..2168ea4 --- /dev/null +++ b/src/components/CountDown/styles.ts @@ -0,0 +1,24 @@ +import { styled } from 'styled-components' + +export const CountdownContainer = styled.div` + font-family: 'Roboto-Mono', monospace; + font-size: 10rem; + line-height: 8rem; + color: ${(props) => props.theme['gray-100']}; + display: flex; + gap: 1rem; + span { + background: ${(props) => props.theme['gray-700']}; + padding: 2rem 1rem; + border-radius: 8px; + } +` + +export const Separator = styled.div` + padding: 2rem 0; + color: ${(props) => props.theme['green-500']}; + width: 4rem; + overflow: hidden; + display: flex; + justify-content: center; +` diff --git a/src/pages/Home/formValidations.ts b/src/components/NewCyclesForm/formValidations.ts similarity index 86% rename from src/pages/Home/formValidations.ts rename to src/components/NewCyclesForm/formValidations.ts index d2bc590..418db88 100644 --- a/src/pages/Home/formValidations.ts +++ b/src/components/NewCyclesForm/formValidations.ts @@ -12,6 +12,6 @@ export const newCycleFormValidationSchema = zod.object({ task: zod.string().min(1, validationsMessages.task), minutesAmount: zod .number() - .min(5, validationsMessages.minutesAmount.min) + .min(1, validationsMessages.minutesAmount.min) .max(60, validationsMessages.minutesAmount.max), }) diff --git a/src/components/NewCyclesForm/index.tsx b/src/components/NewCyclesForm/index.tsx new file mode 100644 index 0000000..352bdb5 --- /dev/null +++ b/src/components/NewCyclesForm/index.tsx @@ -0,0 +1,54 @@ +import { FormContainer, TaskInput, MinutesAmountInput } from './styles' +import { useForm } from 'react-hook-form' +import { ICycle } from '../../pages/Home/interfaces' +import * as zod from 'zod' +import { zodResolver } from '@hookform/resolvers/zod' +import { newCycleFormValidationSchema } from './formValidations' + +interface INewCyclesForm { + activeCycle: ICycle | undefined +} + +type formType = zod.infer + +export default function NewCyclesForm({ activeCycle }: INewCyclesForm) { + const { register, handleSubmit, formState, reset, watch } = useForm( + { + resolver: zodResolver(newCycleFormValidationSchema), + defaultValues: { + minutesAmount: 0, + task: '', + }, + }, + ) + return ( + + + + + + + + minutes + + ) +} diff --git a/src/components/NewCyclesForm/styles.ts b/src/components/NewCyclesForm/styles.ts new file mode 100644 index 0000000..f164322 --- /dev/null +++ b/src/components/NewCyclesForm/styles.ts @@ -0,0 +1,44 @@ +import { styled } from 'styled-components' + +export const FormContainer = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + color: ${(props) => props.theme['gray-100']}; + font-size: 1.125rem; + font-weight: bold; + flex-wrap: wrap; +` + +const BaseInput = styled.input` + background: transparent; + height: 2.5rem; + border: 0; + border-bottom: 2px solid ${(props) => props.theme['gray-500']}; + font-weight: bold; + font-size: 1.125rem; + padding: 0 0.5%; + color: ${(props) => props.theme['gray-100']}; + + &:focus { + box-shadow: none; + border-color: ${(props) => props.theme['green-500']}; + } + + &::placeholder { + color: ${(props) => props.theme['gray-500']}; + } +` + +export const TaskInput = styled(BaseInput)` + flex: 1; + &::-webkit-calendar-picker-indicator { + display: none !important; + } +` + +export const MinutesAmountInput = styled(BaseInput)` + width: 4rem; +` diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 618daaa..2448a3f 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -1,100 +1,87 @@ -import { zodResolver } from '@hookform/resolvers/zod' -import { Play } from 'phosphor-react' +import { differenceInSeconds } from 'date-fns' +import { HandPalm, Play } from 'phosphor-react' import { - CountdownContainer, - FormContainer, HomeContainer, - MinutesAmountInput, - Separator, StartCountDownButton, - TaskInput, + StopCountDownButton, } from './styles' -import { useForm } from 'react-hook-form' -import * as zod from 'zod' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { ICycle } from './interfaces' -import { newCycleFormValidationSchema } from './formValidations' import { showError } from './functions' - -type formType = zod.infer +import NewCyclesForm from '../../components/NewCyclesForm' +import CountDown from '../../components/CountDown' export default function Home() { - const teste: formType = { - minutesAmount: 1, - task: 'asd', - } - console.log(teste) const [cycles, setCycles] = useState([]) const [activeCycleId, setActiveCycleId] = useState(0) - const [amountSecondsPassed, setAmountSecondsPassed] = useState(0) + const activeCycle = cycles.find((cycle) => cycle.id === activeCycleId) - const { register, handleSubmit, formState, reset } = useForm({ - resolver: zodResolver(newCycleFormValidationSchema), - defaultValues: { - minutesAmount: 0, - task: '', - }, - }) - const totalSeconds = activeCycle ? activeCycle.minutesAmount * 60 : 0 + const currentSeconds = activeCycle ? totalSeconds - amountSecondsPassed : 0 + const taskInput = watch('task') + const minutesInput = watch('minutesAmount') + const isSubmitDisabled = !(taskInput && minutesInput) const minutesAmount: number = Math.floor(currentSeconds / 60) const secondsAmount: number = currentSeconds % 60 const minutes = String(minutesAmount).padStart(2, '0') const seconds = String(secondsAmount).padStart(2, '0') + function saveFormData(data: formType) { const newCycle: ICycle = { id: new Date().getTime(), task: data.task, minutesAmount: data.minutesAmount, + startDate: new Date(), } setCycles([...cycles, newCycle]) setActiveCycleId(newCycle.id) + setAmountSecondsPassed(0) + } + + useEffect(() => { + if (activeCycle) { + document.title = `${minutes}:${seconds}` + } + }, [minutes, seconds]) + + function handleInterruptCycle() { + if (activeCycle) { + setCycles((state) => + state.map((cycle) => { + if (cycle.id === activeCycle.id) { + const now = new Date() + + return { ...cycle, interruptedDate: now } + } else { + return cycle + } + }), + ) + } + setActiveCycleId(0) reset() } return (
- - - - - - - - minutes - - - {minutes[0]} - {minutes[1]} - : - {seconds[0]} - {seconds[1]} - - showError(formState.errors)} - > - - Begin - + + + {activeCycle ? ( + + + Stop + + ) : ( + showError(formState.errors)} + disabled={isSubmitDisabled} + > + + Begin + + )}
) diff --git a/src/pages/Home/interfaces.tsx b/src/pages/Home/interfaces.tsx index 0fd22de..fef7507 100644 --- a/src/pages/Home/interfaces.tsx +++ b/src/pages/Home/interfaces.tsx @@ -7,5 +7,7 @@ export interface ICycle { id: number task: string minutesAmount: number + startDate: Date + finishedDate?: Date + interruptedCycle?: Date } - diff --git a/src/pages/Home/styles.ts b/src/pages/Home/styles.ts index eb8a3c1..88af6da 100644 --- a/src/pages/Home/styles.ts +++ b/src/pages/Home/styles.ts @@ -14,63 +14,8 @@ export const HomeContainer = styled.main` gap: 3.5rem; } ` -export const FormContainer = styled.div` - width: 100%; - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - color: ${(props) => props.theme['gray-100']}; - font-size: 1.125rem; - font-weight: bold; - flex-wrap: wrap; -` -export const CountdownContainer = styled.div` - font-family: 'Roboto-Mono', monospace; - font-size: 10rem; - line-height: 8rem; - color: ${(props) => props.theme['gray-100']}; - display: flex; - gap: 1rem; - span { - background: ${(props) => props.theme['gray-700']}; - padding: 2rem 1rem; - border-radius: 8px; - } -` -const BaseInput = styled.input` - background: transparent; - height: 2.5rem; - border: 0; - border-bottom: 2px solid ${(props) => props.theme['gray-500']}; - font-weight: bold; - font-size: 1.125rem; - padding: 0 0.5%; - color: ${(props) => props.theme['gray-100']}; - - &:focus { - box-shadow: none; - border-color: ${(props) => props.theme['green-500']}; - } - - &::placeholder { - color: ${(props) => props.theme['gray-500']}; - } -` - -export const TaskInput = styled(BaseInput)` - flex: 1; - &::-webkit-calendar-picker-indicator { - display: none !important; - } -` - -export const MinutesAmountInput = styled(BaseInput)` - width: 4rem; -` - -export const StartCountDownButton = styled.button` +export const BaseCountDownButton = styled.button` width: 100%; border: 0; padding: 1rem; @@ -81,24 +26,24 @@ export const StartCountDownButton = styled.button` gap: 8px; font-weight: bold; cursor: pointer; - background: ${(props) => props.theme['green-500']}; color: ${(props) => props.theme['gray-100']}; - &:not(:disabled):hover { - background: ${(props) => props.theme['green-700']}; - } - &:disabled { opacity: 0.7; cursor: not-allowed; } ` -export const Separator = styled.div` - padding: 2rem 0; - color: ${(props) => props.theme['green-500']}; - width: 4rem; - overflow: hidden; - display: flex; - justify-content: center; +export const StartCountDownButton = styled(BaseCountDownButton)` + background: ${(props) => props.theme['green-500']}; + &:not(:disabled):hover { + background: ${(props) => props.theme['green-700']}; + } +` + +export const StopCountDownButton = styled(BaseCountDownButton)` + background: ${(props) => props.theme['red-500']}; + &:not(:disabled):hover { + background: ${(props) => props.theme['red-700']}; + } `