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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,15 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- CSS -->
<link rel="stylesheet" href="./css_styles/styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Rambla:wght@400;700&display=swap" rel="stylesheet">
<!-- CSS -->
<meta charset="utf-8" />
<link rel="icon" href="./img/favicon.ico" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@@ -1,104 +0,0 @@
.banner-login {
background-color: #D17262;
color: white;
@include bodyFont();
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 5vw;
height: 8vh;
p {
@include normalizeBody();
}
.button-container {
width: 20vw;
display: flex;
justify-content: space-around;
button {
@include normalizeButton();
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;
}
}
}
}
.banner-login.blank.dark-mode-component {
opacity: 100%;
background-color: $main-color-dark;
}

View File

@@ -1,104 +0,0 @@
.go-to-account {
width: 3vw;
height: 3vw;
position: absolute;
top: 10.5vh;
left: 87vw;
background-color: $second-color;
display: flex;
justify-content: center;
align-items: center;
border-radius: 100%;
.go-to-account-text {
@include titleFont();
font-size: 1vw;
text-decoration: none;
user-select: none;
color: #ffffff;
}
}
.close-session {
width: 7vw;
height: 3vw;
position: absolute;
top: 10.5vh;
left: 91vw;
background-color: $second-color;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5%;
.close-session-text {
@include titleFont();
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;
}
}
.go-to-account.dark-mode-component, .close-session.dark-mode-component {
background-color: $second-color-dark;
}

View File

@@ -1,134 +0,0 @@
.header-main-page {
background-color: $main-color;
display: flex;
flex-direction: column;
// align-items: center;
justify-content: center;
height: 14vh;
padding: 0vh 1vw;
box-sizing: border-box;
a {
text-decoration: none;
}
.title-link {
width: fit-content;
}
h1 {
color: #ffffff;
font-size: 3vw;
display: inline;
@include titleFont();
@include normalizeTitle();
}
.konami-code {
@include titleFont();
color: $lightest-color-dark;
}
}
.notification-select {
background-color: $second-color;
height: 3vh;
display: flex;
align-items: flex-end;
padding-left: 1vw;
box-sizing: border-box;
color: #fff;
@include bodyFont();
p {
display: inline-block;
align-self: center;
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 {
height: auto;
padding: 3vw 0px;
align-items: center;
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;
}
}
}
.header-main-page.dark-mode-component {
background-color: $main-color-dark;
border-bottom: 1px solid #ffffff72;
}
.notification-select.dark-mode-component {
background-color: $second-color-dark;
}

View File

@@ -1,49 +0,0 @@
.history-button {
@include normalizeButton;
height: 9vh;
width: 9vh;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 6vh;
border-radius: 100%;
background-color: $light-color;
svg {
@include svgStyle;
path {
fill: rgba(80, 80, 80, 0.556);
}
width: 6vh;
height: 6vh;
}
&:hover {
animation: spin 0.4s ease-in-out;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
}

View File

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

View File

@@ -1,109 +0,0 @@
.main-pomodoro {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 68vh;
.timer {
display: flex;
justify-content: center;
@include bodyFont;
font-weight: bold;
font-size: 130pt;
color: $second-color;
}
.style-display {
display: flex;
align-items: center;
flex-direction: column;
@include bodyFont;
h4 {
font-size: 24pt;
@include normalizeBody;
color: $second-color;
user-select: none;
}
h3 {
font-size: 30pt;
@include normalizeBody;
color: $second-color;
cursor: pointer;
user-select: none;
}
}
.start-pomodoro {
@include normalizeButton;
@include titleFont;
margin-top: 6vh;
width: 30vw;
height: 8vh;
background: $second-color;
border-radius: 24px;
font-size: 5vh;
color: white;
}
}
@media (max-width: 576px) {
.main-pomodoro {
height: auto;
.timer {
font-size: 80pt;
}
.start-pomodoro {
font-size: 13pt;
}
}
}
.main-pomodoro-container.dark-mode-component {
background-color: $main-color-dark;
.main-pomodoro {
.timer {
color: #ffffff;
}
.style-display {
h3, h4 {
color: #ffffff;
}
}
.start-pomodoro{
background-color: $lightest-color-dark;
}
}
}

View File

@@ -1,71 +0,0 @@
.pomodoro-counter {
position: absolute;
top: 29vh;
ul {
li {
display: flex;
@include bodyFont;
list-style-type: none;
margin-top: 2vh;
font-size: 15pt;
color: #00000070;
.separator {
user-select: none;
margin: 0px 0.5vw;
}
.quantity {
@include titleFont;
user-select: none;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 30px;
border-radius: 100%;
color: $second-color;
background-color: $light-color;
}
}
}
}
@media (max-width: 991.98px) {
.pomodoro-counter {
position: initial;
padding-bottom: 5vh;
}
}
.pomodoro-counter.dark-mode-component {
ul {
li {
color: #ffffff;
.quantity {
color: $lightest-color-dark;
background: $second-color-dark;
}
}
}
}

View File

@@ -1,77 +0,0 @@
@mixin titleFont {
font-family: 'Raleway', sans-serif;
font-weight: 700;
}
@mixin bodyFont {
font-family: 'Rambla', sans-serif;
}
@mixin normalizeTitle {
margin: 0;
padding: 0;
user-select: none;
}
@mixin normalizeBody {
margin: 0;
padding: 0;
}
@mixin normalizeButton {
background: none;
border: none;
width: 8vw;
}
@mixin svgStyle {
cursor: pointer;
path {
fill: rgb(170, 170, 170);
// CREDITS TO https://stackoverflow.com/a/49627345
}
}
$background-color: #ffffff;
$border-color: #969696;
$main-color: #62D2A2;
$second-color: #1FAB89;
$second-color-darker: #15745c;
$light-color: #9DF3C4;
$lightest-color: #D7FBE8;
$main-color-dark: #303841;
$second-color-dark: #3A4750;
$light-color-dark: #D72323;
$light-color-dark-darker: #7c1414;
$lightest-color-dark: #4c8ad5;
$lightest-color-dark-darker: #eff3f8;
@import 'header';
@import 'banner-login';
@import 'mainPomodoro';
@import 'styleSelector';
@import 'goDownArrow';
@import 'aboutThis';
@import 'historyButton';
@import 'pomodoro-counter';
@import 'identify';
@import 'dark-mode_toogle-switch';
@import 'account';
@import 'goToAccount';
@import 'clockify-tasks-display';
@import 'made-with-love';
html, body {
margin: 0;
padding: 0;
}
body {
height: 100%;
background-color: $background-color;
}

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

@@ -1,9 +1,10 @@
.go-down {
background-color: var(--main-background-color);
height: 8vh;
display: flex;
justify-content: center;
svg {
height: 6vh;
@@ -32,18 +33,4 @@
border: none;
border-top: 1px solid rgb(184, 184, 184);
}
}
.go-down-separator-line.dark-mode-component {
background-color: $main-color-dark;
}
.go-down.dark-mode-component {
background-color: $main-color-dark;
}
.go-down.dark-mode-component#go-to-credits {
background-color: $second-color-dark;
}

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

@@ -1,11 +1,8 @@
.dark-mode {
.theme-switch {
position: absolute;
top: 1.7vh;
left: calc(100% - 5vw);
display: flex;
#dark-mode_toogle-switch {
#theme-switch_toogle-switch {
width: 0;
height: 0;
visibility: hidden;
@@ -15,7 +12,7 @@
display: block;
width: 4vw;
height: 4vh;
background-color: $lightest-color;
background-color: var(--lightest-color);
border-radius: 100px;
position: relative;
cursor: pointer;
@@ -39,7 +36,7 @@
left: calc(100% - 0.25vh);
transform: translateX(-100%);
background-color: $lightest-color;
background-color: var(--second-text-color);
}
input:checked + label{
@@ -63,8 +60,7 @@
}
}
@media (max-width: 576px) {
@media (max-width: 576px) {
.dark-mode {
label {
width: 50px;

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

@@ -1,6 +1,9 @@
#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;
@@ -16,9 +19,6 @@
h3 {
width: 50%;
@include titleFont;
@include normalizeTitle;
font-size: 22pt;
.line-through {
@@ -40,14 +40,10 @@
margin-top: 3vh;
h4 {
@include normalizeTitle;
@include bodyFont;
font-size: 18pt;
}
p {
@include normalizeBody;
@include bodyFont;
font-size: 13pt;
margin-left: 2vw;
margin-top: 1vh;
@@ -67,29 +63,4 @@
padding-bottom: 5vh;
}
}
#about-this.dark-mode-component, #credits.dark-mode-component {
background-color: $second-color-dark;
color: #ffffff;
.information {
display: flex;
ul {
li {
p {
a {
color: $lightest-color-dark;
}
}
}
}
}
}
}

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

@@ -1,20 +1,11 @@
html, body {
height: 100%;
}
.account-container {
height: 69vh; //xd
#message {
padding-left: 5vw;
background-color: #ff6a6a;
h1 {
@include titleFont();
@include normalizeTitle();
color: #ffffff;
padding: 2vh 0px;
}
@@ -28,6 +19,11 @@ html, body {
.next-step {
display: flex;
align-items: center;
background-color: var(--second-background-color);
.disabled {
user-select: none;
cursor: initial;
@@ -40,14 +36,10 @@ html, body {
padding-top: 5vh;
background-color: $light-color;
background-color: --light-color;
h1, h2 {
@include normalizeTitle();
@include titleFont();
color: $second-color;
color: --second-color;
padding-bottom: 3vh;
}
@@ -70,14 +62,13 @@ html, body {
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid $border-color;
border-bottom: 1px solid var(--border-color);
outline: none;
}
input[type=submit] {
width: 50%;
border: $border-color solid 1px;
border: var(--border-color) solid 1px;
}
}
@@ -89,26 +80,18 @@ html, body {
padding-left: 5vw;
.change-api-container {
width: 50vw;
background-color: $lightest-color;
color: var(--second-text-color);
background-color: var(--main-background-color);
display: flex;
flex-direction: column;
padding-top: 1vh;
padding: 1vh 3vw;
border-radius: 2px;
padding-left: 1vw;
border-radius: 5px;
.api-preview-container {
.title {
@include normalizeTitle();
@include titleFont();
}
.api-preview {
font-weight: bold;
}
@@ -118,12 +101,13 @@ html, body {
height: 4vh;
width: 20vw;
margin-bottom: 1vh;
margin: 1vh 0px;
border: none;
outline: none;
border: $border-color solid 1px;
background-color: var(--lightest-color);
border: var(--border-color) solid 1px;
}
}
}
@@ -167,47 +151,4 @@ html, body {
}
}
}
}
.account-container.dark-mode-component {
background-color: $second-color-dark;
.next-step {
.next-step-title {
background-color: $lightest-color-dark;
h1, h2 {
color: #ffffff;
a {
color: $lightest-color;
}
a:visited {
color: $light-color;
}
}
}
.flex-container-change-api-container {
.change-api-container {
background-color: $lightest-color-dark;
.api-preview-container {
p {
color: rgb(255, 255, 255)
}
}
}
}
}
}
.loading-container.dark-mode-component {
background-color: $second-color-dark
}

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

@@ -1,12 +1,9 @@
html, body {
height: 100%;
}
.identify-container {
&:has {
height: 100%;
}
display: flex;
flex-direction: column;
align-items: center;
@@ -26,11 +23,9 @@ html, body {
margin: 2vw;
color: #ffffff;
@include titleFont();
user-select: none;
font-size: 22pt;
}
@@ -38,7 +33,7 @@ html, body {
.success-message-container {
background-color: $lightest-color;
background-color: var(--lightest-color);
p {
@@ -59,23 +54,22 @@ html, body {
justify-content: space-around;
padding: 5vh 0px;
background-color: $second-color;
background-color: var(--second-color);
cursor: pointer;
transition: 0.4s ease-in-out;
filter: brightness(80%);
h2 {
@include titleFont();
@include normalizeTitle();
color: #ffffff;
}
}
.active-option {
cursor: initial;
background-color: $second-color-darker;
filter: brightness(100%);
}
}
@@ -101,33 +95,29 @@ html, body {
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid $border-color;
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: $border-color solid 1px;
border: var(--border-color) solid 1px;
}
}
}
}
}
.loading-container {
width: 100%;
height: 83vh;
display: flex;
justify-content: center;
align-items: center;
&.dark-mode-component {
background-color:$main-color-dark ;
}
}
.reset-password {
@@ -137,48 +127,10 @@ html, body {
margin-bottom: 1vh;
border: none;
border-bottom: 1px solid $border-color;
border-bottom: 1px solid var(--border-color);
outline: none;
width: 50%;
border: $border-color solid 1px;
background-color: $main-color;
}
.identify-container.dark-mode-component {
background-color: $second-color-dark;
.identify {
.options-container {
.option {
background-color: $light-color-dark;
}
.active-option{
background-color: $light-color-dark-darker;
}
}
.form-container {
form {
input {
color: white;
background: none;
}
input[type=submit] {
background-color: $main-color-dark;
}
}
}
}
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

@@ -1,4 +1,4 @@
.clockify-tasks-display {
.clockify-task-form {
width: 20vw;
position: absolute;
@@ -40,8 +40,6 @@
input[type=text] {
width: 15vw;
height: 3vw;
@include bodyFont();
font-size: 12pt;
@@ -104,11 +102,4 @@
margin-top: 1vh;
}
}
}
.clockify-tasks-display-container.dark-mode-container {
background-color: $second-color-dark;
}

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

@@ -15,22 +15,16 @@
z-index: 50;
h2 {
@include bodyFont;
color: $second-color;
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;
@@ -42,16 +36,15 @@
flex-direction: column;
justify-content: center;
color: var(--second-text-color);
.title {
@include bodyFont;
font-size: 13pt;
}
.times {
@include bodyFont;
color: $second-color;
color: var(--third-text-color);
font-size: 13pt;
}
}
}
@@ -77,7 +70,7 @@
width: 45px;
height: 45px;
border: 3px solid $main-color;
border: 3px solid var(--main-text-color);
border-radius: 0.25em;
border-radius: 100%;
@@ -86,7 +79,7 @@
.checkbox__input input:checked + .checkbox_control {
background-color: $light-color;
background-color: var(--lightest-color);
}
// CREDITS TO https://moderncss.dev/pure-css-custom-checkbox-style/
@@ -173,43 +166,4 @@
}
}
}
.style-selector-container.dark-mode-component {
.style-selector {
h2 {
color: #ffffff;
}
.style-selection-container {
.style-container{
label {
.title {
color: #ffffff;
}
.times {
color: $lightest-color-dark;
}
}
}
.checkbox_control {
border: 3px solid $lightest-color-dark-darker;
}
.checkbox__input input:checked + .checkbox_control {
background-color: $lightest-color-dark;
}
}
}
}

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);
}