Refactored the whole app 🔧

This commit is contained in:
2022-05-05 20:31:45 -03:00
parent 3fb0f5bc6c
commit c9115e2ef8
90 changed files with 2738 additions and 3566 deletions

View File

@@ -1,33 +1,24 @@
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import './styles.css'
import React, { useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import AboutThis from "./components/AboutThis";
import Account from "./components/Account";
import BannerLogin from "./components/BannerLogin";
import ClockifyTasksDisplay from './components/ClockifyTasksDisplay';
import Credits from './components/Credits';
import Footer from './components/Footer';
import GoDownArrow from "./components/GoDownArrow";
import Header from "./components/Header";
import Identify from "./components/Identify";
import MainPomodoro from "./components/MainPomodoro";
//Pages
import ConfigAccount from "./pages/ConfigAccount/ConfigAccount";
import Identify from "./pages/Identify/Identify";
import Main from './pages/Main/Main';
import AboutThis from "./pages/AboutThis/AboutThis";
import Credits from './pages/Credits/Credits';
import Footer from './components/Footer/Footer';
import Header from "./components/Header/Header";
function App() {
const [timerOn, setTimerOn] = useState(false)
const [signIn, setSignIn] = useState('')
const [apiKey, setApiKey] = useState('')
const [taskName, setTaskName] = useState('')
const [workspaceID, setWorspaceID] = useState(0)
const [projectID, setProjectID] = useState(0)
const [signedIn, setSignedIn] = useState('')
const [darkMode, setDarkmode] = useState(false)
const [KonamiCodeActive, setKonamiCodeActive] = useState(false)
const [konamiCodeActive, setKonamiCodeActive] = useState(false)
const [notificationPermission, setNotificationPermission] = useState(undefined)
const auth = getAuth()
@@ -35,9 +26,9 @@ function App() {
onAuthStateChanged(auth, (user) => {
if (user) {
setSignIn(true)
setSignedIn(true)
} else {
setSignIn(false)
setSignedIn(false)
}
})
@@ -64,105 +55,43 @@ function App() {
<Router>
<>
<Header
signIn={signIn}
signedIn={signedIn}
darkMode={darkMode}
setDarkmode={setDarkmode}
KonamiCodeActive= {KonamiCodeActive}
konamiCodeActive={konamiCodeActive}
notificationPermission={notificationPermission}
setNotificationPermission={setNotificationPermission}
/>
<Switch>
<Route path="/config-account">
<Account
<ConfigAccount
darkMode={darkMode}
/>
</Route>
<Route path="/identify">
<Identify
darkMode={darkMode}
/>
<Identify darkMode={darkMode}/>
</Route>
<Route path="/">
<BannerLogin
signIn={signIn}
<Main
signedIn={signedIn}
darkMode={darkMode}
/>
<ClockifyTasksDisplay
setTimerOn={setTimerOn}
signIn={signIn}
timerOn={timerOn}
apiKey={apiKey}
setApiKey={setApiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorspaceID={setWorspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
/>
<MainPomodoro
signIn={signIn}
timerOn={timerOn}
setTimerOn={setTimerOn}
apiKey={apiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorspaceID={setWorspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
setKonamiCodeActive = {setKonamiCodeActive}
KonamiCodeActive= {KonamiCodeActive}
konamiCodeActive={konamiCodeActive}
setKonamiCodeActive={setKonamiCodeActive}
notificationPermission={notificationPermission}
/>
<GoDownArrow
direction={'about-this'}
darkMode={darkMode}
/>
<AboutThis
darkMode={darkMode}
/>
<GoDownArrow
direction={'credits'}
darkMode={darkMode}
/>
<Credits
darkMode={darkMode}
/>
/>
</Route>
</Switch>

View File

@@ -1,34 +0,0 @@
import React from 'react'
const Message = (props) => {
const [message, setMessage] = React.useState('')
React.useEffect( () => {
switch (props.message) {
case 'API NOT VALID':
setMessage('The API is not valid')
break;
case 'API NOT UPLOADED':
setMessage(`There's been an error while we upload your API Key. Please try again`)
break;
case 'API UPLOADED':
setMessage(`API uploaded successfully`)
break;
default:
setMessage('')
break;
}
}, [])
return (
<div id="message" className={props.message === 'API UPLOADED' ? 'successfully' : null}>
<h1>{message}</h1>
</div>
)
}
export default Message

View File

@@ -1,27 +0,0 @@
import React from 'react'
const BannerLogin = (props) => {
return (
<>
{
!props.signIn ?
<div className="banner-login">
<p>Access to integrate and save your progress with Clockify!</p>
<div className="button-container">
<button className="register" onClick={() => {window.location = '/identify?act=r'}}>Register</button>
<button className="login" onClick={() => {window.location = '/identify?act=l'}}>Login</button>
</div>
</div>
: <div className={props.darkMode ? 'banner-login blank dark-mode-component' : 'banner-login blank'}>
</div>
}
</>
)
}
export default BannerLogin

View File

@@ -1,125 +0,0 @@
import React, {useState} from 'react'
import { makeRequest } from '../Clockify/clockify'
import { getFirestore, doc, getDoc } from 'firebase/firestore'
import { onAuthStateChanged, getAuth } from 'firebase/auth'
import { firebase } from '../Firebase/firebase'
import { withRouter } from 'react-router-dom'
const ClockifyTasksHoursCounter = (props) => {
const auth = getAuth()
const [userUID, setUserUID] = useState('')
const [workspacesArray, setWorkspacesArray] = useState([])
const [loading, setLoading] = useState(true)
const getApiKey = async () => {
try {
const db = await getFirestore(firebase)
const reference = await doc(db, 'users', userUID)
const dataSnap = await getDoc(reference)
const result = await dataSnap.data()
return result.keyClockify
} catch (error) {
}
}
const makeRequestWorkspaces = async (apiClockify) => {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiClockify,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/`, request)
const data = await response.json()
return await data
} catch (error) {
}
}
const bringData = async () => {
await makeRequest(props.apiKey)
}
React.useEffect( () => {
if (props.signIn) {
onAuthStateChanged(auth, async (user) => {
if (user) {
setUserUID(await user.uid)
if (userUID) {
const keyClockify = await getApiKey()
const workspaces = await makeRequestWorkspaces(keyClockify)
workspaces.forEach(workspace => {
let copyWorkspacesArray = workspacesArray
copyWorkspacesArray.push(workspace)
setWorkspacesArray(copyWorkspacesArray)
})
setLoading(false)
}
} else {
return (<></>)
}
})
} else {
props.history.push('/')
}
}, [props, onAuthStateChanged, setUserUID, userUID])
return (
<>
{
loading ?
<h1>Loading</h1>
:
<div>
<select>
{
workspacesArray.map(workspace => (
<option> {workspace.name} </option>
))
}
</select>
</div>
}
</>
)
}
export default withRouter(ClockifyTasksHoursCounter)

View File

@@ -1,19 +0,0 @@
const makeRequest = async (apiKey) => {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiKey.trim(),
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/`, request)
const data = await response.json()
return await data
} catch (error) {
}
}
export {makeRequest}

View File

@@ -1,35 +0,0 @@
const uploadToClockifyTimer = async (workspaceID, projectID, start, end, apiKey, taskName) => {
if (!workspaceID && !projectID) {
return
}
try {
const url = `https://api.clockify.me/api/v1/workspaces/${workspaceID}/time-entries`
const body = {
start: start,
end: end,
projectId: projectID,
description: taskName
}
const headers = {
'X-Api-Key': apiKey,
'Content-type' : 'application/json; charset=UTF-8'
}
const request = {
method: 'POST',
body: JSON.stringify(body),
headers
}
const result = await fetch(url, request)
const data = await result.json()
} catch (error) {
}
}
export default uploadToClockifyTimer

View File

@@ -1,237 +0,0 @@
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { doc, getDoc, getFirestore } from "firebase/firestore";
import React, { useState } from 'react';
import { firebase } from './Firebase/firebase';
import loadingGifDarkTheme from './img/loading-dark-theme.png';
import loadingGifLightTheme from './img/loading-light-theme.png';
const ClockifyTasksDisplay = (props) => {
const auth = getAuth()
const [userUID, setUserUID] = useState('')
const [workspaces, setWorkspaces] = useState([])
const [workspacesReady, setWorkspacesReady] = useState(false)
const [projects, setProjects] = useState([])
const [projectsDone, setProjectsDone] = useState(false)
const [loading, setLoading] = useState(true)
const [apiAvailable, setApiAvailable] = useState(false)
const getApiKey = async () => {
try {
const db = await getFirestore(firebase)
const reference = await doc(db, 'users', userUID)
const dataSnap = await getDoc(reference)
const result = await dataSnap.data()
if (result.keyClockify) {
setApiAvailable(true)
await generateArrayOfWorkspaces(result.keyClockify)
}
} catch (error) {
}
}
const makeRequestWorkspaces = async (apiClockify) => {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiClockify,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/`, request)
const data = await response.json()
props.setApiKey(apiClockify)
return await data
} catch (error) {
}
}
const generateArrayOfWorkspaces = async (key) => {
const getApiKeyReturn = key
const data = await makeRequestWorkspaces(key)
if (data.code !== 1000) {
let workspacesCopy = []
await data.forEach(workspace => {
workspacesCopy.push(workspace)
});
await setWorkspaces(workspacesCopy)
setWorkspacesReady(true)
setLoading(false)
}
}
React.useEffect( () => {
if (props.signIn) {
onAuthStateChanged(auth, async (user) => {
if (user) {
setUserUID(await user.uid)
if (userUID) {
await getApiKey()
}
} else {
return (<></>)
}
})
}
}, [props, onAuthStateChanged, setUserUID, userUID])
const makeRequestProjects = async (e) => {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': props.apiKey,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/${e}/projects`, request)
const data = await response.json()
return await data
} catch (error) {
}
}
const defineProjects = async (e) => {
if (e === 0) {
setProjectsDone(false)
setProjects([])
} else {
setProjectsDone(true)
}
props.setWorspaceID(e)
const data = await makeRequestProjects(e)
await setProjects(data)
if (projects) {
setProjectsDone(true)
}
}
const selectProject = (e) => {
props.setProjectID(e)
}
if (loading && userUID) {
if (!apiAvailable) {
return (<div></div>)
} else {
return (
<div className="clockify-tasks-display loading-container">
<img src={props.darkMode ? loadingGifDarkTheme : loadingGifLightTheme} alt=""/>
</div>
)
}
}
return (
<div className={props.darkMode ? 'clockify-tasks-display-container dark-mode-container' : 'clockify-tasks-display-container'}>
{
userUID ?
<div className={props.timerOn ? 'clockify-tasks-display disabled' : 'clockify-tasks-display'}>
<select onChange={(e) => {defineProjects(e.target.value)}} className='workspace-selector'>
<option value="0">Select a Workspace</option>
{
workspacesReady ?
workspaces.map( (workspace) => {
return <option value={workspace.id} key={workspace.id}>{workspace.name}</option>
})
: null
}
</select>
<select onChange={(e) => {selectProject(e.target.value)}} className={props.workspaceID !== 0 ? 'project-selector' : 'project-selector disabled'}>
<option value="0">Select a Project</option>
{
projectsDone && projects !== undefined ?
projects.map( (project) => {
if (!project.archived){
return <option value={project.id} key={project.id}>{project.name}</option>
}
})
: null
}
</select>
<input
type="text"
onChange={(e) => {props.setTaskName(e.target.value)}}
value={props.taskName}
placeholder="Add task description"
className={props.projectID !== 0 ? null: 'disabled'}
onKeyPress={event => {
if (event.key === 'Enter') {
props.setTimerOn(true)
}
}}
/>
</div>
: null
}
</div>
)
}
export default ClockifyTasksDisplay

View File

@@ -1,9 +1,11 @@
import './footer-styles.css'
import React from 'react'
const Footer = (props) => {
return (
<footer className={props.darkMode ? 'made-with-love dark-mode-component' : 'made-with-love'}>
Made with 💓 by <a href="https://porfolio-franp.netlify.app" target="_blank">Francisco Pessano</a>
Made with 💓 by <a href="https://porfolio-franp.netlify.app" target="_blank" rel="noreferrer">Francisco Pessano</a>
</footer>
)
}

View File

@@ -0,0 +1,13 @@
.made-with-love {
margin-top: auto;
width: 100%;
padding-top: 5vh;
padding-bottom: 5vh;
padding-left: 3vw;
box-sizing: border-box;
background-color: var(--main-background-color);
color: rgb(185, 185, 185);
}
.made-with-love a {
color: #ff8787;
}/*# sourceMappingURL=footer-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["footer-styles.scss","footer-styles.css"],"names":[],"mappings":"AAAA;EAEE,gBAAA;EACA,WAAA;EAEA,gBAAA;EACA,mBAAA;EACA,iBAAA;EAEA,sBAAA;EAEA,8CAAA;EACA,yBAAA;ACHF;ADKE;EACE,cAAA;ACHJ","file":"footer-styles.css"}

View File

@@ -0,0 +1,18 @@
.made-with-love {
margin-top: auto;
width: 100%;
padding-top: 5vh;
padding-bottom: 5vh;
padding-left: 3vw;
box-sizing: border-box;
background-color: var(--main-background-color);
color:rgb(185, 185, 185);
a {
color: #ff8787
}
}

View File

@@ -1,17 +1,19 @@
import './go-down-arrow-styles.css'
import React from 'react'
import { Link, animateScroll as scroll } from "react-scroll";
const GoDownArrow = (props) => {
const width = window.screen.width
const width = window.screen.width
if (width <= '991.98') {
return (
<div className={props.darkMode ? 'go-down-separator-line dark-mode-component' : 'go-down-separator-line'}>
<hr/>
</div>
)
}
if (width <= '991.98') {
return (
<div className={props.darkMode ? 'go-down-separator-line dark-mode-component' : 'go-down-separator-line'}>
<hr/>
</div>
)
}
return (
<div className={props.darkMode ? 'go-down dark-mode-component' : 'go-down'} id={props.direction === 'credits' ? 'go-to-credits' : null}>

View File

@@ -0,0 +1,26 @@
.go-down {
background-color: var(--main-background-color);
height: 8vh;
display: flex;
justify-content: center;
}
.go-down svg {
height: 6vh;
width: 6vh;
cursor: pointer;
}
.go-down svg path {
fill: rgb(170, 170, 170);
}
.go-down-separator-line {
width: 100%;
display: flex;
justify-content: center;
}
.go-down-separator-line hr {
height: 1px;
width: 75%;
border: none;
border-top: 1px solid rgb(184, 184, 184);
}/*# sourceMappingURL=go-down-arrow-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["go-down-arrow-styles.scss","go-down-arrow-styles.css"],"names":[],"mappings":"AAAA;EAEI,8CAAA;EACA,WAAA;EAEA,aAAA;EACA,uBAAA;ACDJ;ADGI;EACI,WAAA;EACA,UAAA;EAEA,eAAA;ACFR;ADIQ;EACI,wBAAA;ACFZ;;ADQA;EAEI,WAAA;EAEA,aAAA;EACA,uBAAA;ACPJ;ADSI;EACI,WAAA;EACA,UAAA;EAEA,YAAA;EACA,wCAAA;ACRR","file":"go-down-arrow-styles.css"}

View File

@@ -0,0 +1,36 @@
.go-down {
background-color: var(--main-background-color);
height: 8vh;
display: flex;
justify-content: center;
svg {
height: 6vh;
width: 6vh;
cursor: pointer;
path {
fill: rgb(170, 170, 170);
// CREDITS TO https://stackoverflow.com/a/49627345
}
}
}
.go-down-separator-line {
width: 100%;
display: flex;
justify-content: center;
hr {
height: 1px;
width: 75%;
border: none;
border-top: 1px solid rgb(184, 184, 184);
}
}

View File

@@ -1,42 +0,0 @@
import React from 'react'
const DarkMode = (props) => {
React.useEffect( () => {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
props.setDarkmode(true)
const darkModeSwitch = document.getElementById('dark-mode_toogle-switch')
darkModeSwitch.checked = true
}
const darkModeCache = localStorage.getItem('dark-mode')
if (darkModeCache === 'true') {
props.setDarkmode(true)
const darkModeSwitch = document.getElementById('dark-mode_toogle-switch')
darkModeSwitch.checked = true
}
}, [])
const changeTheme = () => {
props.setDarkmode(!props.darkMode)
localStorage.setItem('dark-mode', !props.darkMode)
}
return (
<div className="dark-mode">
<input type="checkbox" id="dark-mode_toogle-switch" onChange={changeTheme}/>
<label htmlFor="dark-mode_toogle-switch"></label>
</div>
)
}
export default DarkMode

View File

@@ -1,22 +0,0 @@
import React from 'react'
const GoToAccount = (props) => {
return (
<>
{
props.signIn ?
<>
<a href="/config-account" className={props.darkMode ? 'go-to-account dark-mode-component' : 'go-to-account'}>
<div className="go-to-account-text">API</div>
</a>
<a href="/identify?act=clss" className={props.darkMode ? 'close-session dark-mode-component' : 'close-session'}>
<div className="close-session-text">Close session</div>
</a>
</>
: null
}
</>
)
}
export default GoToAccount

View File

@@ -1,54 +0,0 @@
import React from 'react'
import DarkMode from './Header Childrens/DarkMode'
import GoToAccount from './Header Childrens/GoToAccount'
const Header = (props) => {
const getPermisionDesktopNotification = async () => {
let permission = await Notification.requestPermission();
if (permission === 'granted') {
props.setNotificationPermission(true)
localStorage.setItem("notification-permission", true)
} else {
props.setNotificationPermission(false)
localStorage.setItem("notification-permission", false)
}
}
return (
<>
<header className={props.darkMode ? 'header-main-page dark-mode-component' : 'header-main-page'} >
<a href="/" className='title-link'>
<h1>Clockify Pomodoro Timer</h1>
</a>
<DarkMode
darkMode={props.darkMode}
setDarkmode={props.setDarkmode}
/>
<GoToAccount
signIn={props.signIn}
darkMode={props.darkMode}
/>
<div className="konami-code">
{props.KonamiCodeActive ? 'Konami Code ON' : null}
</div>
</header>
{
props.notificationPermission === undefined || process.env.REACT_APP_ENVIROMENT !== "production" ?
<div className={props.darkMode ? 'notification-select dark-mode-component' : 'notification-select'}>
<p>Do you want to recibe a notification when a Pomodoro cicle ends?</p>
<button className="yes" onClick={getPermisionDesktopNotification}>YES</button>
<button className="no" onClick={() => {
props.setNotificationPermission(false)
localStorage.setItem("notification-permission", false)
}}>Nah</button>
</div>
: null
}
</>
)
}
export default React.memo(Header)

View File

@@ -0,0 +1,72 @@
import './header-styles.css'
import React from 'react'
import ThemeSwitch from './ThemeSwitch/ThemeSwitch'
const Header = (props) => {
const getPermisionDesktopNotification = async () => {
let permission = await Notification.requestPermission();
if (permission === 'granted') {
props.setNotificationPermission(true)
localStorage.setItem("notification-permission", true)
} else {
props.setNotificationPermission(false)
localStorage.setItem("notification-permission", false)
}
}
return (
<header className={props.darkMode ? 'header-main-page dark-mode-component' : 'header-main-page'} >
<h1 className='title-link'><a href="/">Clockify Pomodoro Timer</a></h1>
<div className="buttons-container">
{
props.signedIn ?
<>
<a href="/config-account" className={props.darkMode ? 'go-to-account dark-mode-component' : 'go-to-account'}>
<div className="go-to-account-text">API</div>
</a>
<a href="/identify?act=clss" className={props.darkMode ? 'close-session dark-mode-component' : 'close-session'}>
<div className="close-session-text">Close session</div>
</a>
</>
: null
}
<ThemeSwitch
darkMode={props.darkMode}
setDarkmode={props.setDarkmode}
/>
</div>
<div className="konami-code">
{props.KonamiCodeActive ? 'Konami Code ON' : null}
</div>
{
props.notificationPermission === undefined || process.env.REACT_APP_ENVIROMENT !== "production" ?
<div className={props.darkMode ? 'notification-select dark-mode-component' : 'notification-select'}>
<p>Do you want to recibe a notification when a Pomodoro cicle ends?</p>
<button className="yes" onClick={getPermisionDesktopNotification}>YES</button>
<button className="no" onClick={() => {
props.setNotificationPermission(false)
localStorage.setItem("notification-permission", false)
}}>Nah</button>
</div>
: null
}
{
!props.signedIn ?
<div className="banner-login">
<p>Access to integrate and save your progress with Clockify!</p>
<div className="button-container">
<button className="register" onClick={() => {window.location = '/identify?act=r'}}>Register</button>
<button className="login" onClick={() => {window.location = '/identify?act=l'}}>Login</button>
</div>
</div>
: null
}
</header>
)
}
export default React.memo(Header)

View File

@@ -0,0 +1,102 @@
import './theme-switch-styles.css'
import React from 'react'
const ThemeSwitch = () => {
React.useEffect( () => {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
changeTheme({theme: "dark"})
const darkModeSwitch = document.getElementById('theme-switch_toogle-switch')
darkModeSwitch.checked = true
}
if (localStorage.getItem('dark-mode') === 'true') {
changeTheme({theme: "dark"})
const darkModeSwitch = document.getElementById('theme-switch_toogle-switch')
darkModeSwitch.checked = true
} else {
changeTheme({theme: "white"})
}
}, [])
function changeTheme(options) {
let data
let theme
if (options.e) {
theme = options.e.target.checked ? "dark" : "white"
localStorage.setItem('dark-mode', options.e.target.checked ? "true" : "false")
}
if (options.theme) {
theme = options.theme
localStorage.setItem('dark-mode', options.theme === "dark" ? "true" : "false")
}
switch (theme) {
case "white":
data = [
["--main-text-color", "#1FAB89"],
["--second-text-color", "#2c2c2c"],
["--third-text-color", "#8d8d8d"],
["--pomodoro-counter-text-color", "#8d8d8d"],
["--main-background-color", "#fff",],
["--second-background-color", "#fff"],
["--main-color", "#62D2A2"],
["--second-color", "#1FAB89"],
["--light-color", "#9DF3C4"],
["--light-color-darker", "#3c8f61"],
["--lightest-color", "#D7FBE8"],
["--lightest-color-darker", "#b2e9cb"]
]
break;
case "dark":
data = [
["--main-text-color", "#fff"],
["--second-text-color", "#fff"],
["--third-text-color", "#4c8ad5"],
["--pomodoro-counter-text-color", "#fff"],
['--main-background-color', "#303841"],
['--second-background-color', "#3A4750"],
['--main-color', '#303841'],
["--second-color", "#3A4750"],
["--light-color", "#D72323"],
["--light-color-darker", "#7c1414"],
["--lightest-color", "#4c8ad5"],
["--lightest-color-darker", "#2f5686"]
]
break;
default:
break;
}
data.forEach((variable) => {
document.body.style.setProperty(variable[0], variable[1]);
})
}
return (
<div className="theme-switch">
<input type="checkbox" id="theme-switch_toogle-switch" onClick={(e) => changeTheme({e})}/>
<label htmlFor="theme-switch_toogle-switch"></label>
</div>
)
}
export default ThemeSwitch

View File

@@ -0,0 +1,65 @@
.theme-switch {
display: flex;
}
.theme-switch #theme-switch_toogle-switch {
width: 0;
height: 0;
visibility: hidden;
}
.theme-switch label {
display: block;
width: 4vw;
height: 4vh;
background-color: var(--lightest-color);
border-radius: 100px;
position: relative;
cursor: pointer;
transition: 0.5s;
box-shadow: 0 0 20px rgba(71, 122, 133, 0.3137254902);
}
.theme-switch label::after {
content: "";
width: 3.5vh;
height: 3.5vh;
background-color: #b632eb;
position: absolute;
border-radius: 100%;
top: 0.25vh;
left: 0.25vh;
transition: 0.5s;
}
.theme-switch input:checked + label:after {
left: calc(100% - 0.25vh);
transform: translateX(-100%);
background-color: var(--second-text-color);
}
.theme-switch input:checked + label {
background-color: #b632eb;
}
@media (max-width: 991.98px) {
.dark-mode {
position: initial;
}
.dark-mode label {
width: 50px;
height: 20px;
}
.dark-mode label::after {
width: 15px;
height: 15px;
}
}
@media (max-width: 576px) {
.dark-mode {
margin-bottom: 20px;
}
.dark-mode label {
width: 50px;
height: 20px;
}
.dark-mode label::after {
width: 15px;
height: 15px;
}
}/*# sourceMappingURL=theme-switch-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["theme-switch-styles.scss","theme-switch-styles.css"],"names":[],"mappings":"AAAA;EAEI,aAAA;ACAJ;ADEI;EACI,QAAA;EACA,SAAA;EACA,kBAAA;ACAR;ADGI;EACI,cAAA;EACA,UAAA;EACA,WAAA;EACA,uCAAA;EACA,oBAAA;EACA,kBAAA;EACA,eAAA;EACA,gBAAA;EACA,qDAAA;ACDR;ADII;EACI,WAAA;EACA,YAAA;EACA,aAAA;EACA,yBAAA;EACA,kBAAA;EACA,mBAAA;EACA,WAAA;EACA,YAAA;EACA,gBAAA;ACFR;ADKI;EACI,yBAAA;EACA,4BAAA;EAEA,0CAAA;ACJR;ADOI;EACI,yBAAA;ACLR;;ADSA;EAEI;IACI,iBAAA;ECPN;EDQM;IACI,WAAA;IACA,YAAA;ECNV;EDSM;IACI,WAAA;IACA,YAAA;ECPV;AACF;ADWA;EACI;IAWI,mBAAA;ECnBN;EDSM;IACI,WAAA;IACA,YAAA;ECPV;EDUM;IACI,WAAA;IACA,YAAA;ECRV;AACF","file":"theme-switch-styles.css"}

View File

@@ -0,0 +1,77 @@
.theme-switch {
display: flex;
#theme-switch_toogle-switch {
width: 0;
height: 0;
visibility: hidden;
}
label {
display: block;
width: 4vw;
height: 4vh;
background-color: var(--lightest-color);
border-radius: 100px;
position: relative;
cursor: pointer;
transition: 0.5s;
box-shadow: 0 0 20px #477a8550;
}
label::after {
content: "";
width: 3.5vh;
height: 3.5vh;
background-color: #b632eb;
position: absolute;
border-radius: 100%;
top: 0.25vh;
left: 0.25vh;
transition: 0.5s;
}
input:checked + label:after {
left: calc(100% - 0.25vh);
transform: translateX(-100%);
background-color: var(--second-text-color);
}
input:checked + label{
background-color: #b632eb;
}
}
@media (max-width: 991.98px) {
.dark-mode {
position: initial;
label {
width: 50px;
height: 20px;
}
label::after {
width: 15px;
height: 15px;
}
}
}
@media (max-width: 576px) {
.dark-mode {
label {
width: 50px;
height: 20px;
}
label::after {
width: 15px;
height: 15px;
}
margin-bottom: 20px;
}
}

View File

@@ -0,0 +1,234 @@
.header-main-page {
background-color: var(--main-color);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
height: 24vh;
box-sizing: border-box;
}
.header-main-page .title-link {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
color: #ffffff;
font-size: 3.3em;
display: inline;
margin-top: auto;
margin-bottom: auto;
margin-left: 1vw;
display: flex;
align-items: center;
flex-grow: 1;
}
.header-main-page .title-link a {
color: #ffffff;
font-family: var(--title-font);
text-decoration: none;
cursor: pointer;
}
.header-main-page .konami-code {
color: #4c8ad5;
}
.header-main-page .buttons-container {
display: flex;
align-items: center;
margin-top: auto;
margin-bottom: auto;
}
.header-main-page .buttons-container a, .header-main-page .buttons-container label {
margin-right: 1vw;
}
.header-main-page .buttons-container a {
text-decoration: none;
}
.header-main-page .buttons-container .go-to-account {
width: 3vw;
height: 3vw;
background-color: var(--second-color);
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
}
.header-main-page .buttons-container .go-to-account .go-to-account-text {
font-size: 1vw;
text-decoration: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
color: #ffffff;
}
.header-main-page .buttons-container .close-session {
width: 7vw;
height: 3vw;
background-color: var(--second-color);
display: flex;
justify-content: center;
align-items: center;
border-radius: 5%;
}
.header-main-page .buttons-container .close-session .close-session-text {
font-size: 1vw;
text-decoration: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
color: #ffffff;
}
@media (max-width: 991.98px) {
.go-to-account, .close-session {
position: initial;
padding: 1vw;
}
.go-to-account {
margin-top: 10px;
margin-bottom: 10px;
}
.go-to-account .go-to-account-text {
font-size: 9pt;
}
.close-session .close-session-text {
font-size: 9pt;
}
}
@media (max-width: 576px) {
.go-to-account, .close-session {
width: auto;
height: auto;
}
.go-to-account-text, .close-session-text {
position: initial;
margin: 10px 10px;
}
}
.notification-select {
width: 100%;
height: 3vh;
background-color: var(--second-color);
display: flex;
align-items: flex-end;
padding-left: 1vw;
margin-top: auto;
box-sizing: border-box;
}
.notification-select p {
display: inline-block;
align-self: center;
color: #fff;
padding: 0;
margin: 0;
margin-right: 1vw;
}
.notification-select button {
height: 80%;
width: 5%;
margin-right: 0.5vw;
border: none;
border-radius: 2px;
align-self: center;
color: #fff;
font-weight: bold;
cursor: pointer;
}
.notification-select button.yes {
background-color: rgb(76, 175, 80);
}
.notification-select button.no {
background-color: rgb(211, 47, 47);
}
@media (max-width: 991.98px) {
.header-main-page h1 {
font-size: 26pt;
}
.header-main-page h3 {
font-size: 13pt;
}
.notification-select {
height: auto;
flex-wrap: wrap;
}
.notification-select p {
width: 100%;
margin-top: 1vh;
margin-bottom: 1vh;
}
.notification-select button {
height: 30px;
width: 20%;
margin-bottom: 1vh;
}
}
.banner-login {
width: 100%;
background-color: #D17262;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 5vw;
}
.banner-login p {
color: #fff;
}
.banner-login .button-container {
width: 20vw;
display: flex;
justify-content: space-around;
}
.banner-login .button-container button {
font-size: 12pt;
font-weight: bold;
color: #ffffff;
height: 6vh;
cursor: pointer;
}
.banner-login .button-container .register {
border: solid 1px #ffffff;
border-radius: 24px;
}
.banner-login .button-container .login {
background-color: rgba(0, 0, 0, 0.3411764706);
border-radius: 24px;
}
.banner-login.blank {
opacity: 0%;
}
@media (max-width: 918px) {
.banner-login {
justify-content: initial;
height: auto;
padding: 2vh 2vw;
}
.banner-login p {
width: 33.3%;
}
.banner-login .button-container {
width: 66.6%;
justify-content: space-around;
}
.banner-login .button-container button {
width: 30%;
}
}
@media (max-width: 576px) {
.banner-login p {
width: 50%;
}
.banner-login .button-container {
width: 50%;
flex-direction: column;
}
.banner-login .button-container button {
width: 75%;
margin: 10px;
}
}/*# sourceMappingURL=header-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["header-styles.scss","header-styles.css"],"names":[],"mappings":"AACA;EACI,mCAAA;EAEA,aAAA;EACA,8BAAA;EACA,eAAA;EAEA,YAAA;EAEA,sBAAA;ACHJ;ADKI;EACI,0BAAA;EAAA,uBAAA;EAAA,kBAAA;EAEA,cAAA;EAEA,gBAAA;EAEA,eAAA;EAEA,gBAAA;EACA,mBAAA;EACA,gBAAA;EAEA,aAAA;EACA,mBAAA;EACA,YAAA;ACRR;ADUQ;EACI,cAAA;EACA,8BAAA;EACA,qBAAA;EAEA,eAAA;ACTZ;ADaI;EACI,cAAA;ACXR;ADcI;EACI,aAAA;EACA,mBAAA;EAEA,gBAAA;EACA,mBAAA;ACbR;ADeQ;EACI,iBAAA;ACbZ;ADgBQ;EACI,qBAAA;ACdZ;ADiBQ;EAEI,UAAA;EACA,WAAA;EAEA,qCAAA;EAEA,aAAA;EACA,uBAAA;EACA,mBAAA;EAEA,mBAAA;ACnBZ;ADqBY;EACI,cAAA;EACA,qBAAA;EACA,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EAEA,cAAA;ACpBhB;ADwBQ;EACI,UAAA;EACA,WAAA;EAEA,qCAAA;EAEA,aAAA;EACA,uBAAA;EACA,mBAAA;EAEA,iBAAA;ACzBZ;AD2BY;EACI,cAAA;EAEA,qBAAA;EACA,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EAEA,cAAA;AC3BhB;;ADkCA;EAEI;IACG,iBAAA;IACA,YAAA;EChCL;;EDmCE;IAKI,gBAAA;IACA,mBAAA;ECpCN;ED+BM;IACI,cAAA;EC7BV;;EDqCM;IACI,cAAA;EClCV;AACF;ADsCA;EACI;IACI,WAAA;IACA,YAAA;ECpCN;;EDuCE;IACI,iBAAA;IACA,iBAAA;ECpCN;AACF;ADuCA;EAEI,WAAA;EACA,WAAA;EAEA,qCAAA;EAEA,aAAA;EACA,qBAAA;EAEA,iBAAA;EACA,gBAAA;EAEA,sBAAA;AC1CJ;AD4CI;EACI,qBAAA;EAEA,kBAAA;EAEA,WAAA;EAEA,UAAA;EAEA,SAAA;EACA,iBAAA;AC9CR;ADiDI;EACI,WAAA;EACA,SAAA;EAEA,mBAAA;EAEA,YAAA;EACA,kBAAA;EAEA,kBAAA;EAEA,WAAA;EACA,iBAAA;EAEA,eAAA;ACpDR;ADuDI;EACI,kCAAA;ACrDR;ADwDI;EACI,kCAAA;ACtDR;;AD0DA;EAGQ;IACI,eAAA;ECzDV;ED4DM;IACI,eAAA;EC1DV;;ED8DE;IACI,YAAA;IACA,eAAA;EC3DN;ED6DM;IACI,WAAA;IACA,eAAA;IACA,kBAAA;EC3DV;ED8DM;IACI,YAAA;IACA,UAAA;IACA,kBAAA;EC5DV;AACF;AD+DA;EAEI,WAAA;EAEA,yBAAA;EAMA,aAAA;EACA,mBAAA;EACA,8BAAA;EAEA,gBAAA;ACrEJ;AD6DI;EACI,WAAA;AC3DR;ADoEI;EAEI,WAAA;EAEA,aAAA;EACA,6BAAA;ACpER;ADsEQ;EACI,eAAA;EACA,iBAAA;EACA,cAAA;EAEA,WAAA;EACA,eAAA;ACrEZ;ADwEQ;EACI,yBAAA;EACA,mBAAA;ACtEZ;ADyEQ;EACI,6CAAA;EACA,mBAAA;ACvEZ;;AD4EA;EACI,WAAA;ACzEJ;;AD4EA;EACI;IACI,wBAAA;IAEA,YAAA;IAEA,gBAAA;EC3EN;ED6EM;IACI,YAAA;EC3EV;ED8EM;IACI,YAAA;IAEA,6BAAA;EC7EV;ED+EU;IACI,UAAA;EC7Ed;AACF;ADkFA;EAGQ;IACI,UAAA;EClFV;EDqFM;IACI,UAAA;IAEA,sBAAA;ECpFV;EDsFU;IACI,UAAA;IACA,YAAA;ECpFd;AACF","file":"header-styles.css"}

View File

@@ -0,0 +1,313 @@
.header-main-page {
background-color: var(--main-color);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
height: calc(100vh - calc(68vh + 8vh)); // 100vh minus .main-pomodoro and .go-to-down-arrow
box-sizing: border-box;
.title-link {
width: fit-content;
color: #ffffff;
font-size: 3.3em;
display: inline;
margin-top: auto;
margin-bottom: auto;
margin-left: 1vw;
display: flex;
align-items: center;
flex-grow: 1;
a {
color: #ffffff;
font-family: var(--title-font);
text-decoration: none;
cursor: pointer;
}
}
.konami-code {
color: #4c8ad5;
}
.buttons-container {
display: flex;
align-items: center;
margin-top: auto;
margin-bottom: auto;
a, label {
margin-right: 1vw;
}
a {
text-decoration: none;
}
.go-to-account {
width: 3vw;
height: 3vw;
background-color: var(--second-color);
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
.go-to-account-text {
font-size: 1vw;
text-decoration: none;
user-select: none;
color: #ffffff;
}
}
.close-session {
width: 7vw;
height: 3vw;
background-color: var(--second-color);
display: flex;
justify-content: center;
align-items: center;
border-radius: 5%;
.close-session-text {
font-size: 1vw;
text-decoration: none;
user-select: none;
color: #ffffff;
}
}
}
}
@media (max-width: 991.98px) {
//! I fucking hate make the responsive design
.go-to-account, .close-session {
position: initial;
padding: 1vw;
}
.go-to-account {
.go-to-account-text {
font-size: 9pt;
}
margin-top: 10px;
margin-bottom: 10px;
}
.close-session {
.close-session-text {
font-size: 9pt;
}
}
}
@media (max-width: 576px) {
.go-to-account, .close-session {
width: auto;
height: auto;
}
.go-to-account-text, .close-session-text {
position: initial;
margin:10px 10px;
}
}
.notification-select {
width: 100%;
height: 3vh;
background-color: var(--second-color);
display: flex;
align-items: flex-end;
padding-left: 1vw;
margin-top: auto;
box-sizing: border-box;
p {
display: inline-block;
align-self: center;
color: #fff;
padding: 0;
margin: 0;
margin-right: 1vw;
}
button {
height: 80%;
width: 5%;
margin-right: 0.5vw;
border: none;
border-radius: 2px;
align-self: center;
color: #fff;
font-weight: bold;
cursor: pointer;
}
button.yes {
background-color: rgb(76, 175, 80);
}
button.no {
background-color: rgb(211, 47, 47);
}
}
@media (max-width: 991.98px) {
.header-main-page {
h1 {
font-size: 26pt;
}
h3 {
font-size: 13pt;
}
}
.notification-select {
height: auto;
flex-wrap: wrap;
p {
width: 100%;
margin-top: 1vh;
margin-bottom: 1vh;
}
button {
height: 30px;
width: 20%;
margin-bottom: 1vh;
}
}
}
.banner-login {
width: 100%;
background-color: #D17262;
p {
color: #fff;
}
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 5vw;
.button-container {
width: 20vw;
display: flex;
justify-content: space-around;
button {
font-size: 12pt;
font-weight: bold;
color: #ffffff;
height: 6vh;
cursor: pointer;
}
.register {
border: solid 1px #ffffff;
border-radius: 24px;
}
.login {
background-color: #00000057;
border-radius: 24px;
}
}
}
.banner-login.blank {
opacity: 0%;
}
@media (max-width: 918px) {
.banner-login {
justify-content: initial;
height: auto;
padding: 2vh 2vw;
p {
width: 33.3%;
}
.button-container {
width: 66.6%;
justify-content: space-around;
button {
width: 30%;
}
}
}
}
@media (max-width: 576px) {
.banner-login {
p {
width: 50%;
}
.button-container {
width: 50%;
flex-direction: column;
button {
width: 75%;
margin: 10px;
}
}
}
}

View File

@@ -1,33 +0,0 @@
import React from 'react'
const LoginForm = (props) => {
return (
<>
<form onSubmit={props.sendForm}>
<input
type="email"
placeholder="Email"
onChange= {(e) => {
props.setEmail(e.target.value)
}}
/>
<input
type="password"
placeholder="Password"
onChange= { (e) => {
props.setPassword(e.target.value)
}}
/>
<input type="submit" value="Login"></input>
<button class="reset-password" onClick={() => props.setAct('i forgor')}>Reset Password?</button>
</form>
</>
)
}
export default LoginForm

View File

@@ -1,39 +0,0 @@
import React from 'react'
const RegisterForm = (props) => {
return (
<form onSubmit={props.sendForm}>
<input
type="email"
placeholder="Email"
onChange={(e) => {
props.setEmail(e.target.value)
}}
/>
<input
type="password"
placeholder="Password"
onChange={(e) => {
props.setPassword(e.target.value)
}}
/>
<input
type="password"
placeholder="Confirm Password"
onChange={(e) => {
props.setConfirmPassword(e.target.value)
}}
/>
<input
type="submit"
value="Register"
/>
</form>
)
}
export default RegisterForm

View File

@@ -1,19 +0,0 @@
import React from 'react'
const ResetPassword = (props) => {
return (
<form onSubmit={props.sendForm}>
<input
type="email"
placeholder="Email"
onChange= {(e) => {
props.setEmail(e.target.value)
}}
/>
<input type="submit" value="Reset password"></input>
<button class="reset-password" onClick={() => props.setAct('login')}>Back to login</button>
</form>
)
}
export default ResetPassword

View File

@@ -1,311 +0,0 @@
import { createUserWithEmailAndPassword, getAuth, onAuthStateChanged, sendPasswordResetEmail, signInWithEmailAndPassword, signOut } from 'firebase/auth'
import { doc, getFirestore, setDoc } from 'firebase/firestore'
import React, { useState } from 'react'
import { withRouter } from 'react-router-dom'
import { firebase } from './Firebase/firebase'
import LoginForm from './Identify Childrens/LoginForm'
import RegisterForm from './Identify Childrens/RegisterForm'
import ResetPassword from './Identify Childrens/ResetPassword'
import loadingGifDarkTheme from './img/loading-dark-theme.png'
import loadingGifLightTheme from './img/loading-light-theme.png'
const Identify = (props) => {
const [act, setAct] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [message, setMessage] = React.useState('')
const [errorMessage, setErrorMessage] = React.useState(0)
const [successMessage, setSuccessMessage] = React.useState(0)
const [loading, setLoading] = useState(false)
const auth = getAuth()
const register = async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return
}
})
try {
const response = await createUserWithEmailAndPassword(auth, email, password)
const uid = response.user.uid
addNewUserToFirebase(uid)
props.history.push('/config-account')
setLoading(false)
} catch (error) {
setMessage(error.message)
setLoading(false)
}
}
const addNewUserToFirebase = async (uid) => {
try {
const db = getFirestore(firebase)
await setDoc(doc(db, 'users', uid), {
keyClockify: ''
})
} catch (error) {
setMessage(error)
}
}
const login = async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return
}
})
try {
const response = await signInWithEmailAndPassword(auth, email, password)
props.history.push('/config-account')
await setLoading(false)
} catch (error) {
setErrorMessage('USER OR PASSWORD NOT VALID')
setLoading(false)
}
}
const resetPasswordFirestore = async () => {
try {
const response = await sendPasswordResetEmail(auth, email)
setSuccessMessage('Recovery email send')
setLoading(false)
} catch (error) {
setErrorMessage('There was a problem sending the email.')
setLoading(false)
}
}
const defineLogin = () => {
if (act !== 'login') {
setAct('login')
}
}
const defineRegister = () => {
if (act !== 'register') {
setAct('register')
}
}
const sendForm = (e) => {
e.preventDefault()
setLoading(true)
if (!email.trim()) {
setErrorMessage('EMAIL EMPTY')
setLoading(false)
return
}
if (act !== 'i forgor') {
if (!password.trim()) {
setErrorMessage('PASSWORD EMPTY')
setLoading(false)
return
}
if (password.trim().length < 8) {
setErrorMessage('PASSWORD TOO SHORT')
setLoading(false)
return
}
}
if (act === 'register') {
if (!confirmPassword.trim()) {
setErrorMessage('CONFIRM PASSWORD PLEASE')
setLoading(false)
return
}
if (password !== confirmPassword) {
setErrorMessage("PASSWORDS DOESN'T MATCH")
setLoading(false)
return
}
register()
e.target.reset()
setEmail('')
setPassword('')
setConfirmPassword('')
setErrorMessage(0)
return
}
if (act === 'login') {
login()
e.target.reset()
setEmail('')
setPassword('')
return
}
if (act === 'i forgor') {
resetPasswordFirestore()
e.target.reset()
setEmail('')
setErrorMessage(0)
return
}
setErrorMessage('ACTION NOT VALID')
}
const signOutFromApp = () => {
signOut(auth)
.then(() => {
setErrorMessage('YOU CLOSED THE SESSION')
//! 'YOU CLOSE SESSION' MESSAGE CODE
})
}
React.useEffect( () => {
const urlInfo = new URLSearchParams(window.location.search)
const action = urlInfo.get('act')
if (action === 'r') {
setAct('register')
} else {
setAct('login')
}
if (action === 'clss') {
signOutFromApp()
return
}
onAuthStateChanged(auth, (user) => {
if (user) {
props.history.push('/')
}
})
}, [])
if (loading) {
return (
<div className={props.darkMode ? 'loading-container dark-mode-component' : 'loading-container'} >
<img src={props.darkMode ? loadingGifDarkTheme : loadingGifLightTheme} alt=""/>
</div>
)
}
return (
<div className={props.darkMode ? 'identify-container dark-mode-component' : 'identify-container'}>
<div className="error-message-container">
{
errorMessage ? <p>{errorMessage}</p> : null
}
</div>
<div className="success-message-container">
{
successMessage ? <p>{successMessage}</p> : null
}
</div>
<div className="identify">
<nav className="options-container">
<div
className={
act === 'login' ? 'option active-option': 'option'
}
onClick={() => {
defineLogin()
}}
>
<h2>LOGIN</h2>
</div>
<div
className={
act === 'register' ? 'option active-option': 'option'
}
onClick={() => {
defineRegister()
}}
>
<h2>REGISTER</h2>
</div>
</nav>
<div className="form-container">
{
act === 'login' ? <LoginForm setEmail={setEmail} setPassword={setPassword} sendForm={sendForm} setAct={setAct}/> : null
}
{
act === 'register' ? <RegisterForm setEmail={setEmail} setPassword={setPassword} setConfirmPassword={setConfirmPassword} sendForm={sendForm}/> : null
}
{
act === 'i forgor' ? <ResetPassword setEmail={setEmail} sendForm={sendForm} setAct={setAct}/> : null
}
</div>
</div>
</div>
)
}
export default withRouter(Identify)

View File

@@ -0,0 +1,14 @@
import React from 'react'
import loadingImage from './img/loading-light-theme.png'
const Loading = () => {
return (
<>
<div className="loading-container">
<img src={loadingImage} alt="loading"/>
</div>
</>
)
}
export default Loading

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,5 @@
.loading-container {
width: 100%;
height: 100%;
background-color: #fff;
}/*# sourceMappingURL=loading-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["loading-styles.scss","loading-styles.css"],"names":[],"mappings":"AAAA;EACI,WAAA;EACA,YAAA;EAEA,sBAAA;ACAJ","file":"loading-styles.css"}

View File

@@ -0,0 +1,6 @@
.loading-container {
width: 100%;
height: 100%;
background-color: #fff;
}

View File

@@ -1,117 +0,0 @@
const setTimeStyleExternal = (props, setMinutes, setSeconds, setBreakTime, setActualStyle, breakTime) => {
if (props.style === 'Can I play, Daddy?') {
const minutes = 10
const seconds = 0
setMinutes(minutes)
setSeconds(seconds)
setBreakTime(
{
normal: {
minutes: 5,
seconds: 0
},
extended: {
minutes: 15,
seconds: 0
}
}
)
setActualStyle(props.style)
return {
minutes, seconds, breakTime
}
}
if (props.style === 'Regular'){
const minutes = 25
const seconds = 0
setMinutes(minutes)
setSeconds(seconds)
setBreakTime(
{
normal: {
minutes: 5,
seconds: 0
},
extended: {
minutes: 15,
seconds: 0
}
}
)
setActualStyle(props.style)
return {
minutes, seconds, breakTime
}
}
if (props.style === 'Creative work') {
const minutes = 50
const seconds = 0
setMinutes(minutes)
setSeconds(seconds)
setBreakTime(
{
normal: {
minutes: 10,
seconds: 0
},
extended: {
minutes: 20,
seconds: 0
}
}
)
setActualStyle(props.style)
return {
minutes, seconds, breakTime
}
}
if (props.style === 'Last minute delivery') {
const minutes = 90
const seconds = 0
setMinutes(minutes)
setSeconds(seconds)
setBreakTime(
{
normal: {
minutes: 15,
seconds: 0
},
extended: {
minutes: 30,
seconds: 0
}
}
)
setActualStyle(props.style)
return {
minutes, seconds, breakTime
}
}
}
export default setTimeStyleExternal

View File

@@ -1,22 +0,0 @@
import React from 'react'
const PomodoroCounter = (props) => {
return (
<div className={props.darkMode ? 'pomodoro-counter dark-mode-component': 'pomodoro-counter'}>
<ul>
<li>
<span className="quantity">{props.pomodoros}</span><span className="separator"> - </span>Pomodoros
</li>
<li>
<span className="quantity">{props.rests}</span><span className="separator"> - </span>Rests
</li>
<li>
<span className="quantity">{props.longRests}</span><span className="separator"> - </span>Long rests
</li>
</ul>
</div>
)
}
export default PomodoroCounter

View File

@@ -1,33 +0,0 @@
import React from 'react'
const StyleSelectionChildren = (props) => {
return (
<div
className="style-container"
onClick = { (e) => {
props.setStyle(props.title)
}}
>
<span class="checkbox__input">
<input
id={`input-${props.number}`}
type="checkbox"
checked=
{
props.style === props.title ? true : false
}
>
</input>
<span className="checkbox_control"></span>
</span>
<label for={`input-${props.number}`}>
<span className="title">{props.title}</span>
<span className="times">{props.times}</span>
</label>
</div>
)
}
export default StyleSelectionChildren

View File

@@ -1,21 +0,0 @@
import React from 'react'
import StyleSelectionChildren from './Style Selector Children/StyleSelectionChildren'
const StyleSelector = (props) => {
return (
<div className={props.darkMode ? 'style-selector-container dark-mode-component' : 'style-selector-container'}>
<div className={`style-selector ${props.displayHidden ? 'style-selector-hidden': 'style-selector-show'}`} >
<h2>Select Style</h2>
<div className="style-selection-container">
<StyleSelectionChildren number={1} title={"Can I play, Daddy?"} times={"10 min. | 5 min. | 15 min."} style={props.style} setStyle={props.setStyle}/>
<StyleSelectionChildren number={2} title={"Regular"} times={"25 min. | 5 min. | 15 min."} style={props.style} setStyle={props.setStyle}/>
<StyleSelectionChildren number={3} title={"Creative work"} times={"50 min. | 10 min. | 20 min."} style={props.style} setStyle={props.setStyle}/>
<StyleSelectionChildren number={4} title={"Last minute delivery"} times={"90 min. | 15 min. | 30 min."} style={props.style} setStyle={props.setStyle}/>
</div>
</div>
</div>
)
}
export default StyleSelector

View File

@@ -1,112 +0,0 @@
import React, {useState} from 'react'
import MainPomodoroTimer from './Main Pomodoro Childrens/MainPomodoroTimer'
import PomodoroCounter from './Main Pomodoro Childrens/PomodoroCounter'
import StyleSelector from './Main Pomodoro Childrens/StyleSelector'
import uploadToClockifyTimer from './Clockify/uploadToClockifyTimer'
const MainPomodoro = (props) => {
const [style, setStyle] = useState('Regular')
const [displayHidden, setDisplayHidden] = useState(true)
const [pomodoros, setPomodoros] = useState(0)
const [rests, setRests] = useState(0)
const [longRests, setLongRests] = useState(0)
const [startTime, setStartTime] = useState('')
const [endTime, setEndTime] = useState('')
const [letsUpload, setLetsUpload] = useState(false)
const showStyles = () => {
setDisplayHidden(!displayHidden)
}
React.useEffect( () => {
if (letsUpload) {
uploadToClockifyTimer(props.workspaceID, props.projectID, startTime, endTime, props.apiKey, props.taskName)
setLetsUpload(false)
setStartTime('')
setEndTime('')
}
})
return (
<div className={props.darkMode ? 'main-pomodoro-container dark-mode-component' : 'main-pomodoro-container'}>
<div className="main-pomodoro">
<MainPomodoroTimer
style={style}
timerOn={props.timerOn}
setTimerOn={props.setTimerOn}
pomodoros={pomodoros}
setPomodoros={setPomodoros}
rests={rests}
setRests={setRests}
longRests={longRests}
setLongRests={setLongRests}
workspaceID={props.workspaceID}
setWorspaceID={props.setWorspaceID}
projectID={props.projectID}
setProjectID={props.setProjectID}
apiKey={props.apiKey}
/*alreadyCountingStart={alreadyCountingStart}
setAlreadyCountingStart={setAlreadyCountingStart}
alreadyCountingEnd={alreadyCountingEnd}
setAlreadyCountingEnd={setAlreadyCountingEnd}*/
startTime={startTime}
setStartTime={setStartTime}
endtime={endTime}
setEndTime={setEndTime}
setLetsUpload={setLetsUpload}
setKonamiCodeActive={props.setKonamiCodeActive}
KonamiCodeActive= {props.KonamiCodeActive}
notificationPermission={props.notificationPermission}
/>
<div className="style-display">
<h4>
Style
</h4>
<h3 onClick={showStyles}>
{style}
</h3>
</div>
<button className="start-pomodoro" onClick={() => props.setTimerOn(!props.timerOn)}>{
props.timerOn ? 'STOP' : 'START'
}</button>
</div>
<StyleSelector displayHidden={displayHidden} style={style} setStyle={setStyle} darkMode={props.darkMode}/>
<PomodoroCounter pomodoros={pomodoros} rests={rests} longRests={longRests} darkMode={props.darkMode}/>
</div>
)
}
export default MainPomodoro

View File

@@ -0,0 +1,12 @@
import React from 'react'
const Message = (props) => {
return (
<div id="message" className={props.message.success === true ? 'successfully' : null}>
<h1>{props.message.text}</h1>
</div>
)
}
export default Message

View File

@@ -1,8 +1,11 @@
import './about-this-styles.css'
import React from 'react'
import GoDownArrow from '../../components/GoDownArrow/GoDownArrow'
const AboutThis = (props) => {
return (
<>
<div id="about-this" className={props.darkMode ? 'dark-mode-component' : null}>
<div className="titles">
<h3>About<span className="line-through"> us </span>this</h3>
@@ -56,12 +59,17 @@ const AboutThis = (props) => {
You have more projects?
</h4>
<p>
Yes!, all there are in my <a target="_blank" href="https://porfolio-franp.netlify.app">personal website</a>.
Yes!, all there are in my <a target="_blank" href="https://porfolio-franp.netlify.app" rel="noreferrer">personal website</a>.
</p>
</li>
</ul>
</div>
</div>
<GoDownArrow
direction={'credits'}
darkMode={props.darkMode}
/>
</>
)
}

View File

@@ -0,0 +1,50 @@
#about-this, #credits {
height: 83vh;
background-color: var(--second-background-color);
color: var(--second-text-color);
padding: 0px 3vw;
padding-top: 5vh;
padding-bottom: 5vh;
display: flex;
flex-direction: column;
align-items: stretch;
}
#about-this .titles, #credits .titles {
display: flex;
}
#about-this .titles h3, #credits .titles h3 {
width: 50%;
font-size: 22pt;
}
#about-this .titles h3 .line-through, #credits .titles h3 .line-through {
text-decoration: line-through;
text-decoration-thickness: 3px;
}
#about-this .information, #credits .information {
display: flex;
}
#about-this .information ul, #credits .information ul {
width: 50%;
}
#about-this .information ul li, #credits .information ul li {
margin-top: 3vh;
}
#about-this .information ul li h4, #credits .information ul li h4 {
font-size: 18pt;
}
#about-this .information ul li p, #credits .information ul li p {
font-size: 13pt;
margin-left: 2vw;
margin-top: 1vh;
}
#credits {
height: 95vh;
}
@media (max-width: 918px) {
#about-this, #credits {
height: auto;
padding-bottom: 5vh;
}
}/*# sourceMappingURL=about-this-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["about-this-styles.scss","about-this-styles.css"],"names":[],"mappings":"AAAA;EACI,YAAA;EAEA,gDAAA;EACA,+BAAA;EAEA,gBAAA;EACA,gBAAA;EACA,mBAAA;EAEA,aAAA;EACA,sBAAA;EACA,oBAAA;ACFJ;ADII;EAEI,aAAA;ACHR;ADKQ;EACI,UAAA;EAEA,eAAA;ACJZ;ADMY;EACI,6BAAA;EACA,8BAAA;ACJhB;ADSI;EAEI,aAAA;ACRR;ADUQ;EACI,UAAA;ACRZ;ADUY;EAEI,eAAA;ACThB;ADWgB;EACI,eAAA;ACTpB;ADYgB;EACI,eAAA;EACA,gBAAA;EACA,eAAA;ACVpB;;ADiBA;EACI,YAAA;ACdJ;;ADiBA;EACI;IACI,YAAA;IAEA,mBAAA;ECfN;AACF","file":"about-this-styles.css"}

View File

@@ -0,0 +1,66 @@
#about-this, #credits {
height: 83vh;
background-color: var(--second-background-color);
color: var(--second-text-color);
padding: 0px 3vw;
padding-top: 5vh;
padding-bottom: 5vh;
display: flex;
flex-direction: column;
align-items: stretch;
.titles {
display: flex;
h3 {
width: 50%;
font-size: 22pt;
.line-through {
text-decoration: line-through;
text-decoration-thickness: 3px;
}
}
}
.information {
display: flex;
ul {
width: 50%;
li {
margin-top: 3vh;
h4 {
font-size: 18pt;
}
p {
font-size: 13pt;
margin-left: 2vw;
margin-top: 1vh;
}
}
}
}
}
#credits {
height: 95vh;
}
@media (max-width: 918px) {
#about-this, #credits {
height: auto;
padding-bottom: 5vh;
}
}

View File

@@ -1,14 +1,16 @@
import './config-account-styles.css'
import React, { useState } from 'react';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
import { doc, getDoc, getFirestore, updateDoc } from "firebase/firestore";
import React, { useState } from 'react';
import Message from './Account Childrens/Message';
import { firebase } from './Firebase/firebase';
import loadingGifDarkTheme from './img/loading-dark-theme.png';
import loadingGifLightTheme from './img/loading-light-theme.png';
const Account = (props) => {
import Loading from '../../components/Loading/Loading';
import Message from '../../components/Message/Message';
import { firebase } from '../../Firebase/firebase';
const [signIn, setSignIn] = useState('false')
const ConfigAccount = (props) => {
const [signedIn, setSignedIn] = useState('false')
const [apiKey, setApiKey] = useState('')
const [fristThreeApiKey, setFristThreeApiKey] = useState('')
@@ -21,14 +23,13 @@ const Account = (props) => {
let userUID
onAuthStateChanged(auth, (user) => {
if (user) {
setSignIn(true)
setSignedIn(true)
userUID = user.uid
} else {
setSignIn(false)
setSignedIn(false)
setLoading(false)
}
})
React.useEffect( () => {
@@ -44,24 +45,31 @@ const Account = (props) => {
const data = await makeRequest()
if (await validateRequest(data)) {
setActualState('API VALID')
if (await uploadApiKey()) {
setActualState('API UPLOADED')
setActualState({
text: "API uploaded successfully",
success: true
})
setFristThreeApiKey(apiKey[0] + apiKey[1] + apiKey[2])
setProcessing(false)
} else {
setActualState('API NOT UPLOADED')
setActualState({
text: "There's been an error while we upload your API Key. Please try again",
success: false
})
setProcessing(false)
}
} else {
setActualState('API NOT VALID')
setActualState({
text: 'The API is not valid',
sucess: false
})
setProcessing(false)
}
@@ -135,7 +143,10 @@ const Account = (props) => {
const applyApiState = (data) => {
if (data.keyClockify !== '') {
setActualState('API UPLOADED')
setActualState({
text: "API uploaded successfully",
success: true
})
setApiKey(data.keyClockify)
setFristThreeApiKey(apiKey[0] + apiKey[1] + apiKey[2])
}
@@ -159,27 +170,26 @@ const Account = (props) => {
setApiKey('aa')
setApiKey('')
}
if (loading) {
return (
<div className={props.darkMode ? "loading-container dark-mode-component" : "loading-container"}>
<img src={props.darkMode ? loadingGifDarkTheme : loadingGifLightTheme} alt="" />
</div>
)
}
return (
<>
{
loading ?
<Loading/>
:
<div className={props.darkMode ? "account-container dark-mode-component" : "account-container"}>
{
actualState === 'API NOT VALID' || actualState === 'API NOT UPLOADED' || actualState === 'API UPLOADED' ? <Message message={actualState}/> : null
actualState !== '' ?
<Message message={actualState}/>
: null
}
{
signIn ?
signedIn ?
<div className="next-step">
<div className={actualState === 'API UPLOADED' ? 'disabled' : null}>
<div className={actualState.success === true ? 'disabled' : null}>
<div className="next-step-title">
<h1>One more step...</h1>
<h2>Insert your <a href="https://clockify.me/help/faq/where-can-find-api-information" target="_blank">Clockify API</a> here</h2>
<h2>Insert your <a href="https://app.clockify.me/user/settings" target="_blank" rel="noreferrer">Clockify API Key</a> here</h2>
</div>
<form
@@ -204,7 +214,7 @@ const Account = (props) => {
</div>
{
actualState === 'API UPLOADED' ?
actualState.success === true ?
<div className="flex-container-change-api-container">
<div className="change-api-container">
@@ -233,11 +243,14 @@ const Account = (props) => {
</div>
: null
}
</div>:
</div>
:
<h1>Sign in before access to this page...</h1>
}
</div>
}
</>
)
}
export default Account
export default ConfigAccount

View File

@@ -0,0 +1,105 @@
.account-container #message {
padding-left: 5vw;
background-color: #ff6a6a;
}
.account-container #message h1 {
color: #ffffff;
padding: 2vh 0px;
}
.account-container .successfully {
background-color: #75b7ff !important;
}
.account-container .successfully h1 {
color: #3a3a3a !important;
}
.account-container .next-step {
display: flex;
align-items: center;
background-color: var(--second-background-color);
}
.account-container .next-step .disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.account-container .next-step .next-step-title {
padding-left: 5vw;
padding-top: 5vh;
background-color: --light-color;
}
.account-container .next-step .next-step-title h1, .account-container .next-step .next-step-title h2 {
color: --second-color;
padding-bottom: 3vh;
}
.account-container .next-step form {
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1vh;
}
.account-container .next-step form input {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
}
.account-container .next-step form input[type=submit] {
width: 50%;
border: var(--border-color) solid 1px;
}
.account-container .next-step form .disabled {
opacity: 0%;
}
.account-container .next-step .flex-container-change-api-container {
padding-left: 5vw;
}
.account-container .next-step .flex-container-change-api-container .change-api-container {
color: var(--second-text-color);
background-color: var(--main-background-color);
display: flex;
flex-direction: column;
padding: 1vh 3vw;
border-radius: 5px;
}
.account-container .next-step .flex-container-change-api-container .change-api-container .api-preview-container .api-preview {
font-weight: bold;
}
.account-container .next-step .flex-container-change-api-container .change-api-container #change-API-button {
height: 4vh;
width: 20vw;
margin: 1vh 0px;
border: none;
outline: none;
background-color: var(--lightest-color);
border: var(--border-color) solid 1px;
}
.loading-container {
width: 100%;
height: 83vh;
display: flex;
justify-content: center;
align-items: center;
}
@media (max-width: 991.98px) {
.account-container .next-step {
display: flex;
}
.account-container .next-step .next-step-title h1 {
font-size: 15pt;
}
.account-container .next-step .next-step-title h2 {
font-size: 12pt;
}
.account-container .next-step .flex-container-change-api-container .change-api-container {
width: auto;
}
}/*# sourceMappingURL=config-account-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["config-account-styles.scss","config-account-styles.css"],"names":[],"mappings":"AAEI;EACI,iBAAA;EAEA,yBAAA;ACFR;ADIQ;EACI,cAAA;EACA,gBAAA;ACFZ;ADMI;EACI,oCAAA;ACJR;ADMQ;EAAI,yBAAA;ACHZ;ADMI;EAEI,aAAA;EACA,mBAAA;EAEA,gDAAA;ACNR;ADQQ;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACNZ;ADSQ;EACI,iBAAA;EAEA,gBAAA;EAEA,+BAAA;ACTZ;ADWY;EACI,qBAAA;EAEA,mBAAA;ACVhB;ADcQ;EACI,WAAA;EAEA,aAAA;EACA,sBAAA;EACA,mBAAA;EAEA,gBAAA;ACdZ;ADiBY;EACI,WAAA;EACA,UAAA;EAEA,kBAAA;EAEA,YAAA;EACA,4CAAA;EACA,aAAA;ACjBhB;ADoBY;EACI,UAAA;EACA,qCAAA;AClBhB;ADsBQ;EACI,WAAA;ACpBZ;ADuBQ;EACI,iBAAA;ACrBZ;ADuBY;EACI,+BAAA;EACA,8CAAA;EAEA,aAAA;EACA,sBAAA;EAEA,gBAAA;EAEA,kBAAA;ACxBhB;AD4BoB;EACI,iBAAA;AC1BxB;AD8BgB;EACI,WAAA;EACA,WAAA;EAEA,eAAA;EAEA,YAAA;EACA,aAAA;EAEA,uCAAA;EACA,qCAAA;AC/BpB;;ADsCA;EACI,WAAA;EACA,YAAA;EAEA,aAAA;EACA,uBAAA;EACA,mBAAA;ACpCJ;;ADwCA;EAIQ;IAEI,aAAA;ECzCV;ED8Cc;IACI,eAAA;EC5ClB;ED+Cc;IACI,eAAA;EC7ClB;EDkDc;IACI,WAAA;EChDlB;AACF","file":"config-account-styles.css"}

View File

@@ -0,0 +1,154 @@
.account-container {
#message {
padding-left: 5vw;
background-color: #ff6a6a;
h1 {
color: #ffffff;
padding: 2vh 0px;
}
}
.successfully {
background-color: #75b7ff !important;
h1 {color: #3a3a3a !important;}
}
.next-step {
display: flex;
align-items: center;
background-color: var(--second-background-color);
.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.next-step-title {
padding-left: 5vw;
padding-top: 5vh;
background-color: --light-color;
h1, h2 {
color: --second-color;
padding-bottom: 3vh;
}
}
form {
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1vh;
input {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
}
input[type=submit] {
width: 50%;
border: var(--border-color) solid 1px;
}
}
form .disabled {
opacity: 0%;
}
.flex-container-change-api-container {
padding-left: 5vw;
.change-api-container {
color: var(--second-text-color);
background-color: var(--main-background-color);
display: flex;
flex-direction: column;
padding: 1vh 3vw;
border-radius: 5px;
.api-preview-container {
.api-preview {
font-weight: bold;
}
}
#change-API-button {
height: 4vh;
width: 20vw;
margin: 1vh 0px;
border: none;
outline: none;
background-color: var(--lightest-color);
border: var(--border-color) solid 1px;
}
}
}
}
}
.loading-container {
width: 100%;
height: 83vh;
display: flex;
justify-content: center;
align-items: center;
}
@media (max-width: 991.98px) {
.account-container {
.next-step {
display: flex;
.next-step-title {
h1 {
font-size: 15pt;
}
h2 {
font-size: 12pt;
}
}
.flex-container-change-api-container{
.change-api-container {
width: auto;
}
}
}
}
}

View File

@@ -66,9 +66,6 @@ const Credits = (props) => {
</p>
</li>
</ul>
<ul>
<li>
<h4>

View File

@@ -0,0 +1,349 @@
import './identify-styles.css'
import React, { useState } from 'react'
import { doc, getFirestore, setDoc } from 'firebase/firestore'
import { createUserWithEmailAndPassword, getAuth, onAuthStateChanged, sendPasswordResetEmail, signInWithEmailAndPassword, signOut } from 'firebase/auth'
import { withRouter } from 'react-router-dom'
import { firebase } from '../../Firebase/firebase'
import Loading from '../../components/Loading/Loading'
const Identify = (props) => {
const [act, setAct] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [message, setMessage] = React.useState('')
const [errorMessage, setErrorMessage] = React.useState(0)
const [successMessage, setSuccessMessage] = React.useState(0)
const [loading, setLoading] = useState(false)
const auth = getAuth()
const register = async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return
}
})
try {
const response = await createUserWithEmailAndPassword(auth, email, password)
const uid = response.user.uid
addNewUserToFirebase(uid)
props.history.push('/config-account')
setLoading(false)
} catch (error) {
setMessage(error.message)
setLoading(false)
}
}
const addNewUserToFirebase = async (uid) => {
try {
const db = getFirestore(firebase)
await setDoc(doc(db, 'users', uid), {
keyClockify: ''
})
} catch (error) {
setMessage(error)
}
}
const login = async () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return
}
})
try {
await signInWithEmailAndPassword(auth, email, password)
props.history.push('/config-account')
} catch (error) {
setErrorMessage('USER OR PASSWORD NOT VALID')
}
setLoading(false)
}
const resetPasswordFirestore = async () => {
try {
await sendPasswordResetEmail(auth, email)
setSuccessMessage('Recovery email send')
setLoading(false)
} catch (error) {
setErrorMessage('There was a problem sending the email.')
setLoading(false)
}
}
const defineLogin = () => {
if (act !== 'login') {
setAct('login')
}
}
const defineRegister = () => {
if (act !== 'register') {
setAct('register')
}
}
const sendForm = (e) => {
e.preventDefault()
setLoading(true)
if (!email.trim()) {
setErrorMessage('EMAIL EMPTY')
setLoading(false)
return
}
if (act !== 'i forgor') {
if (!password.trim()) {
setErrorMessage('PASSWORD EMPTY')
setLoading(false)
return
}
if (password.trim().length < 8) {
setErrorMessage('PASSWORD TOO SHORT')
setLoading(false)
return
}
}
if (act === 'register') {
if (!confirmPassword.trim()) {
setErrorMessage('CONFIRM PASSWORD PLEASE')
setLoading(false)
return
}
if (password !== confirmPassword) {
setErrorMessage("PASSWORDS DOESN'T MATCH")
setLoading(false)
return
}
register()
e.target.reset()
setEmail('')
setPassword('')
setConfirmPassword('')
setErrorMessage(0)
return
}
if (act === 'login') {
login()
e.target.reset()
setEmail('')
setPassword('')
return
}
if (act === 'i forgor') {
resetPasswordFirestore()
e.target.reset()
setEmail('')
setErrorMessage(0)
return
}
setErrorMessage('ACTION NOT VALID')
}
const signOutFromApp = () => {
signOut(auth)
.then(() => {
setErrorMessage('YOU CLOSED THE SESSION')
//! 'YOU CLOSE SESSION' MESSAGE CODE
})
}
React.useEffect( () => {
const urlInfo = new URLSearchParams(window.location.search)
const action = urlInfo.get('act')
if (action === 'r') {
setAct('register')
} else {
setAct('login')
}
if (action === 'clss') {
signOutFromApp()
return
}
onAuthStateChanged(auth, (user) => {
if (user) {
props.history.push('/')
}
})
}, [])
return (
<>
{
loading ?
<Loading />
:
<div className={props.darkMode ? 'identify-container dark-mode-component' : 'identify-container'}>
<div className="error-message-container">
{
errorMessage ? <p>{errorMessage}</p> : null
}
</div>
<div className="success-message-container">
{
successMessage ? <p>{successMessage}</p> : null
}
</div>
<div className="identify">
<nav className="options-container">
<div
className={act === 'login' ? 'option active-option': 'option'}
onClick={() => {
defineLogin()
}}
>
<h2>LOGIN</h2>
</div>
<div
className={act === 'register' ? 'option active-option': 'option'}
onClick={() => {
defineRegister()
}}
>
<h2>REGISTER</h2>
</div>
</nav>
<div className="form-container">
{
act === 'login' ?
<form onSubmit={sendForm}>
<input
type="email"
placeholder="Email"
onChange= {(e) => {
setEmail(e.target.value)
}}
/>
<input
type="password"
placeholder="Password"
onChange= { (e) => {
setPassword(e.target.value)
}}
/>
<input className="login" type="submit" value="Login"></input>
<button className="reset-password" onClick={() => setAct('i forgor')}>Reset Password?</button>
</form>
: null
}
{
act === 'register' ?
<form onSubmit={sendForm}>
<input
type="email"
placeholder="Email"
onChange={(e) => {
setEmail(e.target.value)
}}
/>
<input
type="password"
placeholder="Password"
onChange={(e) => {
setPassword(e.target.value)
}}
/>
<input
type="password"
placeholder="Confirm Password"
onChange={(e) => {
setConfirmPassword(e.target.value)
}}
/>
<input
type="submit"
value="Register"
className="register"
/>
</form>
: null
}
{
act === 'i forgor' ?
<form onSubmit={sendForm}>
<input
type="email"
placeholder="Email"
onChange= {(e) => {
setEmail(e.target.value)
}}
/>
<input type="submit" value="Reset password"></input>
<button class="reset-password" onClick={() => setAct('login')}>Back to login</button>
</form>
: null
}
</div>
</div>
</div>
}
</>
)
}
export default withRouter(Identify)

View File

@@ -0,0 +1,98 @@
.identify-container {
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
}
.identify-container:has {
height: 100%;
}
.identify-container .error-message-container, .identify-container .success-message-container {
width: 70vw;
background-color: #D17262;
display: flex;
justify-content: center;
align-items: center;
}
.identify-container .error-message-container p, .identify-container .success-message-container p {
margin: 2vw;
color: #ffffff;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-size: 22pt;
}
.identify-container .success-message-container {
background-color: var(--lightest-color);
}
.identify-container .success-message-container p {
color: #464646;
}
.identify-container .identify {
width: 70vw;
}
.identify-container .identify .options-container {
display: flex;
}
.identify-container .identify .options-container .option {
width: 35vw;
display: flex;
justify-content: space-around;
padding: 5vh 0px;
background-color: var(--second-color);
cursor: pointer;
transition: 0.4s ease-in-out;
filter: brightness(80%);
}
.identify-container .identify .options-container .option h2 {
color: #ffffff;
}
.identify-container .identify .options-container .active-option {
cursor: initial;
filter: brightness(100%);
}
.identify-container .identify .form-container {
display: flex;
justify-content: center;
}
.identify-container .identify .form-container form {
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1vh;
}
.identify-container .identify .form-container form input {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
background-color: var(--main-background-color);
color: var(--second-text-color);
}
.identify-container .identify .form-container form input.login {
background-color: var(--second-background-color);
color: var(--second-text-color);
}
.identify-container .identify .form-container form input.register {
background-color: var(--second-background-color);
}
.identify-container .identify .form-container form input[type=submit] {
width: 50%;
border: var(--border-color) solid 1px;
}
.reset-password {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
width: 50%;
border: var(--border-color) solid 1px;
background-color: var(--lightest-color-darker);
}/*# sourceMappingURL=identify-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["identify-styles.scss","identify-styles.css"],"names":[],"mappings":"AAAA;EAMI,aAAA;EACA,sBAAA;EACA,mBAAA;EAEA,YAAA;ACLJ;ADHI;EACI,YAAA;ACKR;ADII;EACI,WAAA;EAEA,yBAAA;EAEA,aAAA;EACA,uBAAA;EACA,mBAAA;ACJR;ADMQ;EACI,WAAA;EACA,cAAA;EAEA,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EAEA,eAAA;ACNZ;ADYI;EAEI,uCAAA;ACXR;ADaQ;EAEI,cAAA;ACZZ;ADgBI;EACI,WAAA;ACdR;ADgBQ;EACI,aAAA;ACdZ;ADgBY;EACI,WAAA;EAEA,aAAA;EACA,6BAAA;EAEA,gBAAA;EACA,qCAAA;EAEA,eAAA;EAEA,4BAAA;EAEA,uBAAA;ACnBhB;ADqBgB;EACI,cAAA;ACnBpB;ADuBY;EACI,eAAA;EACA,wBAAA;ACrBhB;ADyBQ;EAEI,aAAA;EACA,uBAAA;ACxBZ;AD0BY;EACI,WAAA;EAEA,aAAA;EACA,sBAAA;EACA,mBAAA;EAEA,gBAAA;AC1BhB;AD6BgB;EACI,WAAA;EACA,UAAA;EAEA,kBAAA;EAEA,YAAA;EACA,4CAAA;EACA,aAAA;EAEA,8CAAA;EACA,+BAAA;AC9BpB;ADgCoB;EACI,gDAAA;EACA,+BAAA;AC9BxB;ADiCoB;EACI,gDAAA;AC/BxB;ADmCgB;EACI,UAAA;EACA,qCAAA;ACjCpB;;ADwCA;EACI,WAAA;EACA,UAAA;EAEA,kBAAA;EAEA,YAAA;EACA,4CAAA;EACA,aAAA;EAEA,UAAA;EACA,qCAAA;EACA,8CAAA;ACxCJ","file":"identify-styles.css"}

View File

@@ -0,0 +1,136 @@
.identify-container {
&:has {
height: 100%;
}
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
.error-message-container, .success-message-container {
width: 70vw;
background-color: #D17262;
display: flex;
justify-content: center;
align-items: center;
p {
margin: 2vw;
color: #ffffff;
user-select: none;
font-size: 22pt;
}
}
.success-message-container {
background-color: var(--lightest-color);
p {
color: #464646;
}
}
.identify {
width: 70vw;
.options-container {
display: flex;
.option {
width: 35vw;
display: flex;
justify-content: space-around;
padding: 5vh 0px;
background-color: var(--second-color);
cursor: pointer;
transition: 0.4s ease-in-out;
filter: brightness(80%);
h2 {
color: #ffffff;
}
}
.active-option {
cursor: initial;
filter: brightness(100%);
}
}
.form-container {
display: flex;
justify-content: center;
form {
width: 50vw;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 1vh;
input {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
background-color: var(--main-background-color);
color: var(--second-text-color);
&.login {
background-color: var(--second-background-color);
color: var(--second-text-color);
}
&.register {
background-color: var(--second-background-color);
}
}
input[type=submit] {
width: 50%;
border: var(--border-color) solid 1px;
}
}
}
}
}
.reset-password {
height: 4vh;
width: 60%;
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid var(--border-color);
outline: none;
width: 50%;
border: var(--border-color) solid 1px;
background-color: var(--lightest-color-darker);
}

View File

@@ -0,0 +1,206 @@
import './clockify-task-form.css'
import React, { useState } from 'react';
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { doc, getDoc, getFirestore } from "firebase/firestore";
import { firebase } from '../../../Firebase/firebase';
import Loading from "../../../components/Loading/Loading";
const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, taskName, setTaskName, workspaceID, setWorkspaceID, projectID, setProjectID, darkMode}) => {
const auth = getAuth()
const [userUID, setUserUID] = useState('')
const [workspaces, setWorkspaces] = useState([])
const [workspacesReady, setWorkspacesReady] = useState(false)
const [projects, setProjects] = useState([])
const [projectsDone, setProjectsDone] = useState(false)
const [loading, setLoading] = useState(true)
async function getApiKey() {
try {
const db = getFirestore(firebase)
const reference = doc(db, 'users', userUID)
const dataSnap = await getDoc(reference)
const result = dataSnap.data()
if (result.keyClockify) {
await generateArrayOfWorkspaces(result.keyClockify)
}
} catch (error) {
}
setLoading(false)
}
React.useEffect(() => {
if (signedIn) {
onAuthStateChanged(auth, async (user) => {
if (user) {
setUserUID(user.uid)
if (user.uid) {
await getApiKey()
setLoading(false)
}
} else {
return (<></>)
}
})
} else {
setLoading(false)
}
}, [signedIn, getApiKey])
async function makeRequestWorkspaces(apiClockify) {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiClockify,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/`, request)
const data = await response.json()
setApiKey(apiClockify)
return await data
} catch (error) {
}
}
async function generateArrayOfWorkspaces(key) {
const data = await makeRequestWorkspaces(key)
if (data.code !== 1000) {
let workspacesCopy = []
await data.forEach(workspace => {
workspacesCopy.push(workspace)
});
setWorkspaces(workspacesCopy)
setWorkspacesReady(true)
setLoading(false)
}
}
async function requestProjects(e) {
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiKey,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/${e}/projects`, request)
const data = response.json()
return data
} catch (error) {
console.log(error);
}
}
const defineProjects = async (e) => {
if (e === 0) {
setProjectsDone(false)
setProjects([])
}
setWorkspaceID(e)
const data = await requestProjects(e)
setProjects(data)
setProjectsDone(true)
}
const selectProject = (e) => {
setProjectID(e)
}
return (
<>
{
loading ?
<Loading />
:
<div className={darkMode ? 'clockify-task-form-container dark-mode-container' : 'clockify-tasks-display-container'}>
{
userUID ?
<div className={`clockify-task-form ${timerOn || !workspacesReady ? "disabled" : ""}`}>
<select onChange={(e) => {defineProjects(e.target.value)}} className='workspace-selector'>
<option value="0">Select a Workspace</option>
{
workspacesReady ?
workspaces.map( (workspace) => {
return <option value={workspace.id} key={workspace.id}>{workspace.name}</option>
})
: null
}
</select>
<select onChange={(e) => {selectProject(e.target.value)}} className={workspaceID !== 0 ? 'project-selector' : 'project-selector disabled'}>
<option value="0">Select a Project</option>
{
projectsDone && projects.length !== 0 ?
projects.map((project) => (
!project.archived ?
<option value={project.id} key={project.id}>{project.name}</option>
: null
))
: null
}
</select>
<input
type="text"
onChange={(e) => {setTaskName(e.target.value)}}
value={taskName}
placeholder="Add task description"
className={projectID !== 0 ? null: 'disabled'}
onKeyPress={event => {
if (event.key === 'Enter') {
setTimerOn(true)
}
}}
/>
</div>
: null
}
</div>
}
</>
)
}
export default ClockifyTaskForm

View File

@@ -0,0 +1,91 @@
.clockify-task-form {
width: 20vw;
position: absolute;
top: 60vh;
left: 2.5vw;
}
.clockify-task-form.loading-container {
width: 20vw;
height: auto;
background: none;
}
.clockify-task-form.disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.clockify-task-form .workspace-selector, .clockify-task-form .project-selector {
width: 15vw;
height: 3vw;
}
.clockify-task-form .workspace-selector.disabled, .clockify-task-form .project-selector.disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.clockify-task-form select {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.clockify-task-form input[type=text] {
width: 15vw;
height: 3vw;
font-size: 12pt;
outline: none;
padding: 1px 2px;
box-sizing: border-box;
}
.clockify-task-form input[type=text].disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
@media (max-width: 991.98px) {
.clockify-tasks-display {
position: initial;
width: auto;
display: flex;
justify-content: space-around;
padding: 3vh 5vw;
}
.clockify-tasks-display.loading-container {
width: 90%;
height: auto;
}
.clockify-tasks-display .workspace-selector, .clockify-tasks-display .project-selector, .clockify-tasks-display input[type=text] {
width: 20vw;
height: 5vw;
}
}
@media (max-width: 576px) {
.clockify-tasks-display {
position: initial;
width: auto;
display: flex;
flex-direction: column;
align-items: center;
}
.clockify-tasks-display.loading-container {
width: 90%;
height: auto;
}
.clockify-tasks-display .workspace-selector, .clockify-tasks-display .project-selector, .clockify-tasks-display input[type=text] {
width: 70%;
height: 4vh;
margin-top: 1vh;
}
}/*# sourceMappingURL=clockify-task-form.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["clockify-task-form.scss","clockify-task-form.css"],"names":[],"mappings":"AAAA;EAEI,WAAA;EACA,kBAAA;EAEA,SAAA;EACA,WAAA;ACDJ;ADGI;EACI,WAAA;EACA,YAAA;EACA,gBAAA;ACDR;ADII;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACFR;ADKI;EACI,WAAA;EACA,WAAA;ACHR;ADKQ;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACHZ;ADOI;EACI,gBAAA;EACA,mBAAA;EACA,uBAAA;ACLR;ADQI;EACI,WAAA;EACA,WAAA;EAEA,eAAA;EAEA,aAAA;EACA,gBAAA;EAEA,sBAAA;ACTR;ADWQ;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACTZ;;ADcA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,6BAAA;IAEA,gBAAA;ECdN;EDgBM;IACI,UAAA;IACA,YAAA;ECdV;EDiBM;IACI,WAAA;IACA,WAAA;ECfV;AACF;ADoBA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,sBAAA;IACA,mBAAA;ECpBN;EDsBM;IACI,UAAA;IACA,YAAA;ECpBV;EDuBM;IACI,UAAA;IACA,WAAA;IACA,eAAA;ECrBV;AACF","file":"clockify-task-form.css"}

View File

@@ -0,0 +1,105 @@
.clockify-task-form {
width: 20vw;
position: absolute;
top: 60vh;
left: 2.5vw;
&.loading-container {
width: 20vw;
height: auto;
background: none;
}
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.workspace-selector, .project-selector {
width: 15vw;
height: 3vw;
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
}
select {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
input[type=text] {
width: 15vw;
height: 3vw;
font-size: 12pt;
outline: none;
padding: 1px 2px;
box-sizing: border-box;
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
}
}
@media (max-width: 991.98px) {
.clockify-tasks-display {
position: initial;
width: auto;
display: flex;
justify-content:space-around;
padding: 3vh 5vw;
&.loading-container {
width: 90%;
height: auto;
}
.workspace-selector, .project-selector, input[type=text] {
width: 20vw;
height: 5vw;
}
}
}
@media (max-width: 576px) {
.clockify-tasks-display {
position: initial;
width: auto;
display: flex;
flex-direction: column;
align-items: center;
&.loading-container {
width: 90%;
height: auto;
}
.workspace-selector, .project-selector, input[type=text] {
width: 70%;
height: 4vh;
margin-top: 1vh;
}
}
}

69
src/pages/Main/Main.jsx Normal file
View File

@@ -0,0 +1,69 @@
import React, { useState } from 'react'
import GoDownArrow from '../../components/GoDownArrow/GoDownArrow'
import ClockifyTaskForm from './ClockifyTaskForm/ClockifyTaskForm'
import Pomodoro from './Pomodoro/Pomodoro'
const Main = ({signedIn, darkMode, setKonamiCodeActive, KonamiCodeActive, notificationPermission}) => {
const [timerOn, setTimerOn] = useState(false)
const [apiKey, setApiKey] = useState('')
const [taskName, setTaskName] = useState('')
const [workspaceID, setWorkspaceID] = useState(0)
const [projectID, setProjectID] = useState(0)
return (
<>
<ClockifyTaskForm
timerOn={timerOn}
setTimerOn={setTimerOn}
signedIn={signedIn}
apiKey={apiKey}
setApiKey={setApiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorkspaceID={setWorkspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
/>
<Pomodoro
signedIn={signedIn}
timerOn={timerOn}
setTimerOn={setTimerOn}
apiKey={apiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorkspaceID={setWorkspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
setKonamiCodeActive = {setKonamiCodeActive}
KonamiCodeActive= {KonamiCodeActive}
notificationPermission={notificationPermission}
/>
<GoDownArrow
direction={'about-this'}
darkMode={darkMode}
/>
</>
)
}
export default Main

View File

@@ -0,0 +1,152 @@
import './pomodoro-styles.css'
import React, {useState} from 'react'
import PomodoroTimer from './PomodoroTimer/PomodoroTimer'
import StyleSelector from './StyleSelector/StyleSelector'
const Pomodoro = (props) => {
const [style, setStyle] = useState({title: "Regular", times: [25, 5, 15]}) //Regular time set
const [displayHidden, setDisplayHidden] = useState(true)
const [stats, setStats] = useState({pomodoros: 0, rests: 0, longRests: 0})
const [startTime, setStartTime] = useState('')
const [endTime, setEndTime] = useState('')
const [letsUpload, setLetsUpload] = useState(false)
const showStyles = () => {
setDisplayHidden(!displayHidden)
}
React.useEffect( () => {
if (letsUpload) {
async function uploadToClockifyTimer() {
if (!props.workspaceID && !props.projectID) {
return
}
try {
const url = `https://api.clockify.me/api/v1/workspaces/${props.workspaceID}/time-entries`
const body = {
start: startTime,
end: endTime,
projectId: props.projectID,
description: props.taskName
}
const headers = {
'X-Api-Key': props.apiKey,
'Content-type' : 'application/json; charset=UTF-8'
}
const request = {
method: 'POST',
body: JSON.stringify(body),
headers
}
await fetch(url, request)
} catch (error) {
}
}
uploadToClockifyTimer()
setLetsUpload(false)
setStartTime('')
setEndTime('')
}
})
return (
<div className={props.darkMode ? 'main-pomodoro-container dark-mode-component' : 'main-pomodoro-container'}>
<div className="main-pomodoro">
<PomodoroTimer
style={style}
timerOn={props.timerOn}
setTimerOn={props.setTimerOn}
stats={stats}
setStats={setStats}
workspaceID={props.workspaceID}
setWorkspaceID={props.setWorkspaceID}
projectID={props.projectID}
setProjectID={props.setProjectID}
apiKey={props.apiKey}
startTime={startTime}
setStartTime={setStartTime}
endtime={endTime}
setEndTime={setEndTime}
setLetsUpload={setLetsUpload}
setKonamiCodeActive={props.setKonamiCodeActive}
KonamiCodeActive= {props.KonamiCodeActive}
notificationPermission={props.notificationPermission}
/>
<div className="style-display">
<h4>
Style
</h4>
<h3 onClick={showStyles}>
{style.title}
</h3>
</div>
<button className="start-pomodoro" onClick={() => props.setTimerOn(!props.timerOn)}>{
props.timerOn ? 'STOP' : 'START'
}</button>
</div>
<StyleSelector
displayHidden={displayHidden}
style={style}
setStyle={setStyle}
darkMode={props.darkMode}
/>
<div className={props.darkMode ? 'pomodoro-counter dark-mode-component': 'pomodoro-counter'}>
<ul>
{
Object.keys(stats).map(stat => {
//camelCase to Natural Case
const text = stat.replace(/([A-Z])/g, " $1")
const result = text.charAt(0).toUpperCase() + text.slice(1);
//Credits: https://stackoverflow.com/a/7225450/18740899
return (
<li key={stat}>
<span className="quantity">{stats[stat]}</span><span className="separator"> - </span>{result}
</li>
)
})
}
</ul>
</div>
</div>
)
}
export default Pomodoro

View File

@@ -1,15 +1,13 @@
import React, {useState} from 'react'
import uploadToClockifyTimer from '../Clockify/uploadToClockifyTimer'
import getAndFormatCurrentTime from '../Clockify/getAndFormatCurrentTime'
import randomText from '../Misc/randomText'
import detectKeys from '../Misc/detectKeys'
import getAndFormatCurrentTime from './getAndFormatCurrentTime'
import randomText from './randomText'
import detectKeys from './detectKeys'
import bell_x2 from '../sounds/bell-x2.mp3'
import bell_x3 from '../sounds/bell-x3.mp3'
import setTimeStyleExternal from './MainPomodoroTimer Children/setTimeStyle'
import bell_x2 from '../../../../sounds/bell-x2.mp3'
import bell_x3 from '../../../../sounds/bell-x3.mp3'
const MainPomodoroTimer = (props) => {
const PomodoroTimer = (props) => {
const [minutes, setMinutes] = useState(25)
const [seconds, setSeconds] = useState(0)
@@ -21,7 +19,7 @@ const MainPomodoroTimer = (props) => {
const [timerActivity, setTimerActivity] = useState(false)
const [actualStyle, setActualStyle] = useState('')
const [actualStyle, setActualStyle] = useState({title: "", times: []})
const [alreadyCountingStart, setAlreadyCountingStart] = useState(false) /* TOO MUCH FUCKING STATES https://pbs.twimg.com/media/EoM2rXuW8AMRxZh?format=png&name=large*/
const [alreadyCountingEnd, setAlreadyCountingEnd] = useState(false)
@@ -30,13 +28,29 @@ const MainPomodoroTimer = (props) => {
const [velocity, setVelocity] = useState(1)
const setTimeStyle = () => setTimeStyleExternal(props, setMinutes, setSeconds, setBreakTime, setActualStyle, breakTime)
function setPomodoroTime() {
setMinutes(props.style.times[0])
setSeconds(0)
React.useEffect (() => {
setBreakTime(
{
normal: {
minutes: props.style.times[1],
seconds: 0
},
extended: {
minutes: props.style.times[2],
seconds: 0
}
}
)
if (actualStyle !== props.style) {
setTimeStyle()
setActualStyle(props.style)
}
React.useEffect(() => {
if (actualStyle.title !== props.style.title) {
setPomodoroTime()
}
if (controlKonamiCode) {
@@ -45,7 +59,7 @@ const MainPomodoroTimer = (props) => {
setControlKonamiCode(false)
}
})
}, [props.style])
const startTimer = () => {
@@ -101,44 +115,20 @@ const MainPomodoroTimer = (props) => {
if (!counter) {
console.error('NOT PARAMETER PASSED')
}
if (counter === "Pomodoros") {
props.setPomodoros(props.pomodoros + 1)
return
}
if (counter === "Rest") {
props.setRests(props.rests + 1)
return
}
if (counter === "Long Rest") {
props.setLongRests(props.longRests + 1)
return
}
if (counter) {
console.error('PARAMETER NOT VALID');
}
//Get propiertys of stats obj
Object.keys(props.stats).forEach(stat => {
if (counter === stat) {
const statsCopy = {...props.stats}
statsCopy[stat] = props.stats[stat] + 1
props.setStats(statsCopy)
}
})
}
/*React.useEffect( () => {
if (props.timerOn) {
getAndFormatCurrentTime()
}
if (!props.timerOn){
getAndFormatCurrentTime()
//uploadToClockifyTimer( props.workspaceID, props.projectID, '2021-10-02T13:00:14Z', '2021-10-02T15:00:14Z', props.apiKey)
}
})*/
const getFavicon = () => {
return document.getElementById('favicon')
}
React.useEffect ( () => {
@@ -175,23 +165,21 @@ const MainPomodoroTimer = (props) => {
if (restCounter !== 3){
setPomodoroCounter('Pomodoros')
setPomodoroCounter('pomodoros')
setRestCounter((restCounter + 1))
setBreak(1, 0)
setWeAreInBreakTime(true)
}
if (restCounter === 3) {
setPomodoroCounter('Pomodoros')
setPomodoroCounter('pomodoros')
setRestCounter((restCounter + 1))
setBreak(0, 1)
setWeAreInBreakTime(true)
setWeAreInBreakTime(true)
}
if (!alreadyCountingEnd) {
@@ -208,7 +196,6 @@ const MainPomodoroTimer = (props) => {
}
if (minutes >= 0 || seconds > 0) {
idTimeOut = startTimer()
}
}
@@ -231,18 +218,17 @@ const MainPomodoroTimer = (props) => {
}
if (restCounter === 4) {
setPomodoroCounter('Long Rest')
setPomodoroCounter('longRests')
setRestCounter(0)
} else {
setPomodoroCounter('Rest')
setPomodoroCounter('rests')
}
setWeAreInBreakTime(false)
props.setTimerOn(false)
setTimeStyle()
setPomodoroTime()
}, 1000)
}
@@ -253,25 +239,18 @@ const MainPomodoroTimer = (props) => {
}
}
return () => {
clearInterval(idTimeOut)
}
return () => { clearInterval(idTimeOut) }
}
if (!props.timerOn) {
document.title = 'Clockify Pomodoro Timer'
getFavicon().href = './img/favicon.ico'
if ( timerActivity === true) {
if (!weAreInBreakTime) {
if (restCounter !== 3){
setPomodoroCounter('Pomodoros')
setPomodoroCounter('pomodoros')
setRestCounter((restCounter + 1))
if (!alreadyCountingEnd) {
@@ -288,12 +267,11 @@ const MainPomodoroTimer = (props) => {
setWeAreInBreakTime(true)
}, 10)
}
if (restCounter === 3) {
setTimeout(() => {
setPomodoroCounter('Pomodoros')
setPomodoroCounter('pomodoros')
setRestCounter((restCounter + 1))
setBreak(0, 1)
@@ -319,21 +297,19 @@ const MainPomodoroTimer = (props) => {
if (weAreInBreakTime) {
if (restCounter === 4) {
setPomodoroCounter('Long Rest')
setPomodoroCounter('longRests')
setRestCounter(0)
} else {
setPomodoroCounter('Rest')
setPomodoroCounter('rests')
}
setWeAreInBreakTime(false)
}
setTimerActivity(false)
setTimeStyle()
setPomodoroTime()
}
setAlreadyCountingStart(false)
@@ -346,7 +322,7 @@ const MainPomodoroTimer = (props) => {
if (minutes < 10) {
return '0' + minutes
}
}
else {
return minutes
}
@@ -356,7 +332,7 @@ const MainPomodoroTimer = (props) => {
if (seconds < 10) {
return '0' + seconds
}
}
else {
return seconds
}
@@ -394,4 +370,4 @@ const MainPomodoroTimer = (props) => {
)
}
export default MainPomodoroTimer
export default PomodoroTimer

View File

@@ -0,0 +1,75 @@
import './style-selector.css'
import React from 'react'
const StyleSelector = (props) => {
const data = [
{
title: "Can I play, Daddy?",
times: [10, 5, 15]
},
{
title: "Regular",
times: [25, 5, 15]
},
{
title: "Creative work",
times: [50, 10, 20]
},
{
title: "Last minute delivery",
times: [90, 15, 30]
}
]
return (
<div className={props.darkMode ? 'style-selector-container dark-mode-component' : 'style-selector-container'}>
<div className={`style-selector ${props.displayHidden ? 'style-selector-hidden': 'style-selector-show'}`} >
<h2>Select Style</h2>
<div className="style-selection-container">
{
data.map((obj, index) => (
<div
key={obj.title}
className="style-container"
>
<span class="checkbox__input"
onClick = { (e) => {
props.setStyle(obj)
}}
>
<input
id={`input-${index}`}
type="checkbox"
checked={ props.style.title === obj.title ? true : false}
/>
<span className="checkbox_control"></span>
</span>
<label for={`input-${index}`} onClick = { (e) => {
props.setStyle(obj)
}}
>
<span className="title">{obj.title}</span>
<span className="times">
{
obj.times.map((subObj, index) => (
index !== obj.times.length - 1 ?
`${subObj} min. | `
:
`${subObj} min.`
))
}
</span>
</label>
</div>
))
}
</div>
</div>
</div>
)
}
export default StyleSelector

View File

@@ -0,0 +1,112 @@
.style-selector {
width: calc(20vw - 1px);
padding-left: 3vw;
padding-bottom: 12vh;
position: absolute;
top: 29vh;
left: 75vw;
box-shadow: 1px 6px 15px rgba(0, 0, 0, 0.1254901961);
z-index: 50;
transition: 0.2s ease-in-out;
}
.style-selector h2 {
color: var(--main-text-color);
font-size: 30pt;
text-align: center;
margin-left: -3vw;
margin-top: 1vh;
margin-bottom: 3vh;
}
.style-selector .style-selection-container .style-container {
display: flex;
height: 10vh;
align-items: center;
}
.style-selector .style-selection-container .style-container label {
display: flex;
flex-direction: column;
justify-content: center;
color: var(--second-text-color);
}
.style-selector .style-selection-container .style-container label .title {
font-size: 13pt;
}
.style-selector .style-selection-container .style-container label .times {
color: var(--third-text-color);
font-size: 13pt;
}
.style-selector .style-selection-container input {
width: 65px;
opacity: 0;
}
.style-selector .style-selection-container .checkbox__input {
display: grid;
grid-template-areas: "checkbox";
}
.style-selector .style-selection-container .checkbox__input > * {
grid-area: checkbox;
}
.style-selector .style-selection-container .checkbox_control {
display: inline-grid;
width: 45px;
height: 45px;
border: 3px solid var(--main-text-color);
border-radius: 0.25em;
border-radius: 100%;
transition: ease-in-out 0.2s;
}
.style-selector .style-selection-container .checkbox__input input:checked + .checkbox_control {
background-color: var(--lightest-color);
}
.style-selector-show {
opacity: 100%;
pointer-events: auto;
}
.style-selector-hidden {
opacity: 0%;
pointer-events: none;
}
@media (max-width: 991.98px) {
.style-selector {
position: initial;
margin: 0 0 5% 0;
width: 100%;
padding: 0;
padding: 0px 5vw;
box-sizing: border-box;
}
.style-selector h2 {
text-align: start;
margin-left: 0;
}
.style-selector .style-selection-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.style-selector .style-selection-container .style-container {
height: 100%;
width: 30%;
margin: 1% 10%;
}
}
@media (max-width: 918px) {
.style-selector {
margin-bottom: 5vh;
}
.style-selector .style-selection-container {
flex-wrap: nowrap;
flex-direction: column;
}
.style-selector .style-selection-container .style-container {
width: 60%;
}
}
@media (max-width: 462px) {
.style-selector .style-selection-container .style-container {
width: 100%;
}
}/*# sourceMappingURL=style-selector.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["style-selector.scss","style-selector.css"],"names":[],"mappings":"AAAA;EAEI,uBAAA;EAEA,iBAAA;EACA,oBAAA;EAEA,kBAAA;EAEA,SAAA;EACA,UAAA;EAEA,oDAAA;EAEA,WAAA;EAyEA,4BAAA;AC7EJ;ADMI;EACI,6BAAA;EACA,eAAA;EACA,kBAAA;EAEA,iBAAA;EACA,eAAA;EACA,kBAAA;ACLR;ADSQ;EACI,aAAA;EACA,YAAA;EAEA,mBAAA;ACRZ;ADUY;EACI,aAAA;EACA,sBAAA;EACA,uBAAA;EAEA,+BAAA;ACThB;ADWgB;EACI,eAAA;ACTpB;ADYgB;EACI,8BAAA;EACA,eAAA;ACVpB;ADeQ;EACI,WAAA;EACA,UAAA;ACbZ;ADgBQ;EACI,aAAA;EACA,+BAAA;ACdZ;ADgBY;EACI,mBAAA;ACdhB;ADmBQ;EACI,oBAAA;EAEA,WAAA;EACA,YAAA;EAEA,wCAAA;EACA,qBAAA;EACA,mBAAA;EAEA,4BAAA;ACpBZ;ADuBQ;EAEI,uCAAA;ACtBZ;;AD+BA;EACI,aAAA;EACA,oBAAA;AC5BJ;;AD+BA;EACI,WAAA;EACA,oBAAA;AC5BJ;;AD+BA;EACI;IACI,iBAAA;IAEA,gBAAA;IAEA,WAAA;IAEA,UAAA;IACA,gBAAA;IAEA,sBAAA;EChCN;EDkCM;IACI,iBAAA;IAEA,cAAA;ECjCV;EDqCM;IAEI,aAAA;IAEA,eAAA;IAEA,WAAA;ECtCV;EDwCU;IACI,YAAA;IACA,UAAA;IAEA,cAAA;ECvCd;AACF;AD4CA;EAEI;IAWI,kBAAA;ECrDN;ED4CM;IACI,iBAAA;IAEA,sBAAA;EC3CV;ED6CU;IACI,UAAA;EC3Cd;AACF;ADkDA;EAMY;IACI,WAAA;ECrDd;AACF","file":"style-selector.css"}

View File

@@ -0,0 +1,169 @@
.style-selector{
width: calc(20vw - 1px);
padding-left: 3vw;
padding-bottom: 12vh;
position: absolute;
top: 29vh;
left: 75vw;
box-shadow: 1px 6px 15px #00000020;
z-index: 50;
h2 {
color: var(--main-text-color);
font-size: 30pt;
text-align: center;
margin-left: -3vw;
margin-top: 1vh;
margin-bottom: 3vh;
}
.style-selection-container {
.style-container{
display: flex;
height: 10vh;
align-items: center;
label {
display: flex;
flex-direction: column;
justify-content: center;
color: var(--second-text-color);
.title {
font-size: 13pt;
}
.times {
color: var(--third-text-color);
font-size: 13pt;
}
}
}
input {
width: 65px;
opacity: 0;
}
.checkbox__input {
display: grid;
grid-template-areas: "checkbox";
> * {
grid-area: checkbox;
}
}
.checkbox_control {
display: inline-grid;
width: 45px;
height: 45px;
border: 3px solid var(--main-text-color);
border-radius: 0.25em;
border-radius: 100%;
transition: ease-in-out 0.2s ;
}
.checkbox__input input:checked + .checkbox_control {
background-color: var(--lightest-color);
}
// CREDITS TO https://moderncss.dev/pure-css-custom-checkbox-style/
}
transition: 0.2s ease-in-out;
}
.style-selector-show {
opacity: 100%;
pointer-events: auto;
}
.style-selector-hidden {
opacity: 0%;
pointer-events: none;
}
@media (max-width: 991.98px) {
.style-selector {
position: initial;
margin: 0 0 5% 0;
width: 100%;
padding: 0;
padding: 0px 5vw;
box-sizing: border-box;
h2 {
text-align: start;
margin-left: 0;
}
.style-selection-container {
display: flex;
flex-wrap: wrap;
width: 100%;
.style-container{
height: 100%;
width: 30%;
margin: 1% 10%;
}
}
}
}
@media (max-width: 918px) {
.style-selector {
.style-selection-container {
flex-wrap: nowrap;
flex-direction: column;
.style-container {
width: 60% ;
}
}
margin-bottom: 5vh;
}
}
@media (max-width: 462px) {
.style-selector {
.style-selection-container {
.style-container{
width: 100%;
}
}
}
}

View File

@@ -0,0 +1,101 @@
.main-pomodoro {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 68vh;
background-color: var(--main-background-color);
}
.main-pomodoro .timer {
display: flex;
justify-content: center;
font-weight: bold;
font-size: 130pt;
color: var(--main-text-color);
}
.main-pomodoro .style-display {
display: flex;
align-items: center;
flex-direction: column;
}
.main-pomodoro .style-display h4 {
font-size: 24pt;
color: var(--main-text-color);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.main-pomodoro .style-display h3 {
font-size: 30pt;
color: var(--main-text-color);
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.main-pomodoro .start-pomodoro {
margin-top: 6vh;
width: 30vw;
height: 8vh;
font-family: var(--title-font);
font-weight: 700;
background: var(--second-color);
border-radius: 24px;
font-size: 5vh;
color: white;
}
.pomodoro-counter {
position: absolute;
top: 29vh;
color: var(--pomodoro-counter-text-color);
}
.pomodoro-counter ul li {
display: flex;
list-style-type: none;
margin-top: 2vh;
font-size: 15pt;
}
.pomodoro-counter ul li .separator {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0px 0.5vw;
}
.pomodoro-counter ul li .quantity {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--lightest-color);
height: 30px;
width: 30px;
border-radius: 100%;
font-family: var(--title-font);
font-weight: 700;
color: var(--main-text-color);
}
@media (max-width: 991.98px) {
.pomodoro-counter {
position: initial;
padding-bottom: 5vh;
}
}
@media (max-width: 576px) {
.main-pomodoro {
height: auto;
}
.main-pomodoro .timer {
font-size: 80pt;
}
.main-pomodoro .start-pomodoro {
font-size: 13pt;
}
}/*# sourceMappingURL=pomodoro-styles.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["pomodoro-styles.scss","pomodoro-styles.css"],"names":[],"mappings":"AAAA;EAEI,aAAA;EACA,sBAAA;EACA,mBAAA;EACA,uBAAA;EAEA,YAAA;EAEA,8CAAA;ACFJ;ADII;EAEI,aAAA;EACA,uBAAA;EAEA,iBAAA;EACA,gBAAA;EAEA,6BAAA;ACLR;ADQI;EAEI,aAAA;EACA,mBAAA;EACA,sBAAA;ACPR;ADSQ;EACI,eAAA;EAEA,6BAAA;EACA,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;ACRZ;ADWQ;EACI,eAAA;EAEA,6BAAA;EAEA,eAAA;EACA,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;ACXZ;ADeI;EACI,eAAA;EACA,WAAA;EACA,WAAA;EAEA,8BAAA;EACA,gBAAA;EAEA,+BAAA;EACA,mBAAA;EAEA,cAAA;EACA,YAAA;AChBR;;ADoBA;EACI,kBAAA;EACA,SAAA;EACA,yCAAA;ACjBJ;ADoBQ;EACI,aAAA;EAEA,qBAAA;EACA,eAAA;EAEA,eAAA;ACpBZ;ADsBY;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,iBAAA;ACpBhB;ADuBY;EAEI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EAEA,aAAA;EACA,uBAAA;EACA,mBAAA;EAEA,uCAAA;EAEA,YAAA;EACA,WAAA;EAEA,mBAAA;EAEA,8BAAA;EACA,gBAAA;EACA,6BAAA;AC3BhB;;ADiCA;EAEI;IAEI,iBAAA;IACA,mBAAA;EChCN;AACF;ADmCA;EAEI;IAEI,YAAA;ECnCN;EDqCM;IACI,eAAA;ECnCV;EDsCM;IACI,eAAA;ECpCV;AACF","file":"pomodoro-styles.css"}

View File

@@ -0,0 +1,127 @@
.main-pomodoro {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 68vh;
background-color: var(--main-background-color);
.timer {
display: flex;
justify-content: center;
font-weight: bold;
font-size: 130pt;
color: var(--main-text-color);
}
.style-display {
display: flex;
align-items: center;
flex-direction: column;
h4 {
font-size: 24pt;
color: var(--main-text-color);
user-select: none;
}
h3 {
font-size: 30pt;
color: var(--main-text-color);
cursor: pointer;
user-select: none;
}
}
.start-pomodoro {
margin-top: 6vh;
width: 30vw;
height: 8vh;
font-family: var(--title-font);
font-weight: 700;
background: var(--second-color);
border-radius: 24px;
font-size: 5vh;
color: white;
}
}
.pomodoro-counter {
position: absolute;
top: 29vh;
color: var(--pomodoro-counter-text-color);
ul {
li {
display: flex;
list-style-type: none;
margin-top: 2vh;
font-size: 15pt;
.separator {
user-select: none;
margin: 0px 0.5vw;
}
.quantity {
user-select: none;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--lightest-color);
height: 30px;
width: 30px;
border-radius: 100%;
font-family: var(--title-font);
font-weight: 700;
color: var(--main-text-color);
}
}
}
}
@media (max-width: 991.98px) {
.pomodoro-counter {
position: initial;
padding-bottom: 5vh;
}
}
@media (max-width: 576px) {
.main-pomodoro {
height: auto;
.timer {
font-size: 80pt;
}
.start-pomodoro {
font-size: 13pt;
}
}
}

51
src/styles.css Normal file
View File

@@ -0,0 +1,51 @@
body {
--main-text-color: #1FAB89;
--second-text-color: #272727;
--third-text-color: #fff;
--pomodoro-counter-text-color: #8d8d8d;
--main-background-color: #fff;
--second-background-color: #fff;
--main-color: #62D2A2;
--second-color: #1FAB89;
--light-color: #9DF3C4;
--light-color-darker: #3c8f61;
--lightest-color: #D7FBE8;
--lightest-color-darker: #b2e9cb;
--body-font: "Rambla", sans-serif;
--title-font: "Raleway", sans-serif;
--border-color: #969696;
}
html, body {
margin: 0;
padding: 0;
}
body {
height: 100%;
background-color: var(--main-background-color);
}
button {
background: none;
border: none;
width: 8vw;
}
h1, h2, h3, h4, h5, h6, p {
margin: 0;
padding: 0;
}
* {
font-family: "Rambla", sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Raleway", sans-serif;
font-weight: 700;
}
a {
color: rgb(85, 185, 252);
}/*# sourceMappingURL=styles.css.map */

1
src/styles.css.map Normal file
View File

@@ -0,0 +1 @@
{"version":3,"sources":["styles.scss","styles.css"],"names":[],"mappings":"AAAA;EAEI,0BAAA;EACA,4BAAA;EACA,wBAAA;EACA,sCAAA;EAEA,6BAAA;EACA,+BAAA;EAEA,qBAAA;EACA,uBAAA;EAEA,sBAAA;EACA,6BAAA;EAEA,yBAAA;EACA,gCAAA;EAEA,iCAAA;EACA,mCAAA;EACA,uBAAA;ACLJ;;ADQA;EACI,SAAA;EACA,UAAA;ACLJ;;ADQA;EACI,YAAA;EACA,8CAAA;ACLJ;;ADQA;EACI,gBAAA;EACA,YAAA;EACA,UAAA;ACLJ;;ADQA;EACI,SAAA;EACA,UAAA;ACLJ;;ADQA;EACI,iCAAA;ACLJ;;ADQA;EACI,kCAAA;EACA,gBAAA;ACLJ;;ADQA;EACI,wBAAA;ACLJ","file":"styles.css"}

57
src/styles.scss Normal file
View File

@@ -0,0 +1,57 @@
body {
--main-text-color: #1FAB89;
--second-text-color: #272727;
--third-text-color: #fff;
--pomodoro-counter-text-color: #8d8d8d;
--main-background-color: #fff;
--second-background-color: #fff;
--main-color: #62D2A2;
--second-color: #1FAB89;
--light-color: #9DF3C4;
--light-color-darker: #3c8f61;
--lightest-color: #D7FBE8;
--lightest-color-darker: #b2e9cb;
--body-font: 'Rambla', sans-serif;
--title-font: 'Raleway', sans-serif;
--border-color: #969696;
}
html, body {
margin: 0;
padding: 0;
}
body {
height: 100%;
background-color: var(--main-background-color);
}
button {
background: none;
border: none;
width: 8vw;
}
h1, h2, h3, h4, h5, h6, p {
margin: 0;
padding: 0;
}
* {
font-family: 'Rambla', sans-serif;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Raleway', sans-serif;
font-weight: 700;
}
a {
color: rgb(85, 185, 252);
}