Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/options/components/TogglSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useEffect, useState } from 'react';
import StorageHandler from '../../storage/StorageHandler';

/**
* @typedef {Object} TogglConfig
* @property {boolean} enabled
* @property {string} token
* @property {string[]} breakTags
*/

/**
* @param {TogglConfig} config
*/
async function updateConfig(config) {
await StorageHandler.setTogglConfig(config);
setConfig(config);
}

/**
* @param {string[]} breakTags
* @returns {string}
*/
const breakTagsToText = (breakTags) => breakTags.join(', ');

/**
* @param {string} text
* @returns {string[]}
*/
const textToBreakTags = text => text.split(',')
.map(t => t.trim())
.filter(t => t);

export default function TogglSection() {
const [enabled, setEnabled] = useState();
const [token, setToken] = useState();
const [breakTagsText, setBreakTagsText] = useState();
const [loading, setLoading] = useState(true);

const loadConfig = () => {
StorageHandler.getTogglConfig().then(config => {
setEnabled(config.enabled);
setToken(config.token);
setBreakTagsText(breakTagsToText(config.breakTags));
setLoading(false);
});
};

const save = async () => {
setLoading(true);
await StorageHandler.setTogglConfig({
enabled,
token,
breakTags: textToBreakTags(breakTagsText),
});
loadConfig();
};

useEffect(() => {
loadConfig();
}, []);

return (
<div className="toggl">
<h3 className="toggl__header">Toggl Integration Settings</h3>
<p className="toggl__subheader">
Allow access to blocked websites when you're tracking a break in
<a className="toggl__link" href="https://toggl.com" target="_blank">Toggl</a>
</p>
<hr />
{!loading &&
<form onSubmit={() => save()}>
<div className="form-group">
<label htmlFor="enabledButton">Enable Toggl integration</label>
<input
name="enabledButton"
type="checkbox"
checked={enabled}
onChange={e => setEnabled(!!e.target.checked)}
/>
</div>
<div className="form-group">
<label htmlFor="token">Toggl API token</label>
<input
className="toggl__text-input"
type="text"
value={token}
onChange={e => setToken(e.target.value)}
/>
</div>
<div className="form-group">
<label htmlFor="breakTags">Break tags (comma separated)</label>
<input
className="toggl__text-input"
name="breakTags"
type="text"
value={breakTagsText}
onChange={e => setBreakTagsText(e.target.value)}
/>
</div>
<input className="button button--black toggl__save" type="submit" value="Save" />
</form>
}
</div>
);
}
2 changes: 2 additions & 0 deletions src/options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import MessageTypes from '../enums/messages';
import DomainListItem from './components/DomainListItem';
import SettingsSection from './components/SettingsSection';
import ExtensionStatus from './components/ExtensionStatus';
import TogglSection from './components/TogglSection';

class Options extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -153,6 +154,7 @@ class Options extends React.Component {
<hr />
<ul className="blocklist__list">{this.listItems()}</ul>
</div>
<TogglSection />
<SettingsSection />
</div>
</div>
Expand Down
23 changes: 23 additions & 0 deletions src/options/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,26 @@ hr {
font-size: 20px;
}
}

.toggl__header {
font-size: 1.8rem;
color: #1a1a1a;
}

.toggl__subheader {
margin: 0;
}

.toggl__link {
margin-left: 0.2em;
}

.toggl__text-input {
margin-left: 0.4em;
width: 20em;
}

.toggl__save {
margin-left: 0;
margin-top: 0.2em;
}
31 changes: 31 additions & 0 deletions src/storage/StorageHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ export default class StorageHandler {
return browser.storage.local.get('sites');
}


/**
* @typedef {Object} TogglConfig
* @property {boolean} enabled
* @property {string} token
* @property {string[]} breakTags
*/

/**
* @returns {Promise<TogglConfig>}
*/
static async getTogglConfig() {
const storage = await browser.storage.local.get('togglConfig');
return storage.togglConfig || {
enabled: false,
token: '',
breakTags: [
'pomodoro-break',
'break',
],
};
}

/**
*
* @param {TogglConfig} togglConfig
*/
static setTogglConfig(togglConfig) {
return browser.storage.local.set({ togglConfig });
}

static async isDomainBlocked(urlToMatch) {
const websites = await StorageHandler.getWebsiteDomains();
return websites.includes(urlToMatch);
Expand Down
39 changes: 38 additions & 1 deletion src/utils/functions.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,51 @@
/**
* @typedef {Object} TogglConfig
* @property {boolean} enabled
* @property {string} token
* @property {string[]} breakTags
*/
import StorageHandler from "../storage/StorageHandler";

export function openOptionsPage() {
browser.runtime.openOptionsPage();
window.close();
}

export function redirectToBlockedPage(requestDetails) {
export async function redirectToBlockedPage(requestDetails) {
const original = encodeURIComponent(requestDetails.url);
const interceptPage = `/resources/redirect.html?target=${original}`;

const togglConfig = await StorageHandler.getTogglConfig();
if (togglConfig.enabled && await isOnBreak(togglConfig)) {
return;
}

browser.tabs.update(requestDetails.tabId, { url: interceptPage });
}

export function backgroundResponse(value) {
return new Promise(res => res(value));
}

/**
* @param togglConfig {TogglConfig}
* @returns {Promise<boolean>}
*/
async function isOnBreak(togglConfig) {
const endpoint = 'https://www.toggl.com/api/v8/time_entries/current';
const auth = `Basic ${btoa(`${togglConfig.token}:api_token`)}`;

const response = await fetch(endpoint, {
headers: new Headers({
Authorization: auth
})
});
if (!response.ok) return false;

const body = await response.json();
if (!body.data) return false;
if (!(body.data.tags instanceof Array)) return false;

const breakTags = new Set(togglConfig.breakTags);
return body.data.tags.some(tag => breakTags.has(tag));
}