Skip to content

Commit d8379c0

Browse files
authored
Merge pull request #1 from dmunasingha/dev
Dev
2 parents d6d50f4 + 4a2bce5 commit d8379c0

File tree

8 files changed

+221
-48
lines changed

8 files changed

+221
-48
lines changed

README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# TaskTimer Browser Extension
2+
3+
A modern, beautiful browser extension for tracking time spent on tasks with pause/resume functionality. Built with React, TypeScript, and Tailwind CSS.
4+
5+
6+
## Features
7+
8+
- ⏱️ Simple and intuitive task time tracking
9+
- ⏸️ Pause and resume timer functionality
10+
- 📊 Task history with detailed time logs
11+
- 🎯 Background tracking even when popup is closed
12+
- 💾 Persistent storage across browser sessions
13+
- 🎨 Modern, responsive UI with Tailwind CSS
14+
- 🌙 Clean, minimalist design
15+
16+
## Installation
17+
18+
### Development
19+
20+
1. Clone this repository
21+
2. Install dependencies:
22+
```bash
23+
npm install
24+
```
25+
3. Start the development server:
26+
```bash
27+
npm run dev
28+
```
29+
30+
### Loading the Extension
31+
32+
#### Chrome/Edge
33+
1. Build the extension:
34+
```bash
35+
npm run build:extension
36+
```
37+
2. Open Chrome/Edge and navigate to `chrome://extensions`
38+
3. Enable "Developer mode"
39+
4. Click "Load unpacked" and select the `dist` folder
40+
41+
#### Firefox
42+
1. Build the extension:
43+
```bash
44+
npm run build:extension
45+
```
46+
2. Open Firefox and navigate to `about:debugging`
47+
3. Click "This Firefox"
48+
4. Click "Load Temporary Add-on" and select any file from the `dist` folder
49+
50+
## Usage
51+
52+
1. Click the extension icon in your browser toolbar
53+
2. Enter a task name and click "Start Timer"
54+
3. Use the pause/resume button to temporarily stop/continue timing
55+
4. Click "Stop Timer" when done to save the task
56+
5. View your task history in the "History" tab
57+
58+
## Development
59+
60+
### Project Structure
61+
62+
```
63+
├── src/
64+
│ ├── components/ # React components
65+
│ ├── utils/ # Utility functions
66+
│ ├── types.ts # TypeScript types
67+
│ └── App.tsx # Main application
68+
├── public/
69+
│ └── icons/ # Extension icons
70+
└── background.js # Service worker
71+
```
72+
73+
### Available Scripts
74+
75+
- `npm run dev` - Start development server
76+
- `npm run build` - Build the extension
77+
- `npm run build:extension` - Build extension with icon conversion
78+
- `npm run lint` - Run ESLint
79+
80+
## Technical Details
81+
82+
- Built with React 18 and TypeScript
83+
- Uses Chrome Extension Storage API for persistence
84+
- Background service worker for timer management
85+
- Tailwind CSS for styling
86+
- Lucide React for icons
87+
88+
## Contributing
89+
90+
1. Fork the repository
91+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
92+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
93+
4. Push to the branch (`git push origin feature/amazing-feature`)
94+
5. Open a Pull Request
95+
96+
## License
97+
98+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
99+
100+
## Acknowledgments
101+
102+
- [React](https://reactjs.org/)
103+
- [TypeScript](https://www.typescriptlang.org/)
104+
- [Tailwind CSS](https://tailwindcss.com/)
105+
- [Lucide Icons](https://lucide.dev/)
106+
- [Vite](https://vitejs.dev/)

background.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,21 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1313
updateBadge('⏱️');
1414
return true;
1515
}
16-
16+
17+
if (message.type === 'PAUSE_TIMER') {
18+
const { task } = message;
19+
activeTimers.set(task.id, task);
20+
updateBadge('⏸️');
21+
return true;
22+
}
23+
24+
if (message.type === 'RESUME_TIMER') {
25+
const { task } = message;
26+
activeTimers.set(task.id, task);
27+
updateBadge('⏱️');
28+
return true;
29+
}
30+
1731
if (message.type === 'STOP_TIMER') {
1832
const { taskId } = message;
1933
activeTimers.delete(taskId);
@@ -27,7 +41,8 @@ chrome.alarms.create('keepAlive', { periodInMinutes: 1 });
2741
chrome.alarms.onAlarm.addListener((alarm) => {
2842
if (alarm.name === 'keepAlive') {
2943
if (activeTimers.size > 0) {
30-
updateBadge('⏱️');
44+
const timer = Array.from(activeTimers.values())[0];
45+
updateBadge(timer.status === 'paused' ? '⏸️' : '⏱️');
3146
}
3247
}
3348
});

index.html

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
<!doctype html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
8-
</head>
9-
<body>
10-
<div id="root"></div>
11-
<script type="module" src="/src/main.tsx"></script>
12-
</body>
13-
</html>
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Task Timer Extension</title>
9+
</head>
10+
11+
<body>
12+
<div id="root"></div>
13+
<script type="module" src="/src/main.tsx"></script>
14+
</body>
15+
16+
</html>

manifest.json

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,22 @@
33
"name": "TaskTimer Pro",
44
"version": "1.0.0",
55
"description": "Track time spent on tasks with a beautiful, modern interface",
6-
"permissions": [
7-
"storage",
8-
"alarms",
9-
"notifications"
10-
],
6+
"permissions": ["storage", "alarms", "notifications"],
117
"action": {
128
"default_popup": "index.html",
139
"default_icon": {
14-
"16": "/icons/icon16.png",
15-
"48": "/icons/icon48.png",
16-
"128": "/icons/icon128.png"
10+
"16": "/icons/icon16.svg",
11+
"48": "/icons/icon48.svg",
12+
"128": "/icons/icon128.svg"
1713
}
1814
},
1915
"icons": {
20-
"16": "/icons/icon16.png",
21-
"48": "/icons/icon48.png",
22-
"128": "/icons/icon128.png"
16+
"16": "/icons/icon16.svg",
17+
"48": "/icons/icon48.svg",
18+
"128": "/icons/icon128.svg"
2319
},
2420
"background": {
2521
"scripts": ["background.js"],
2622
"type": "module"
2723
}
28-
}
24+
}

src/App.tsx

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,46 @@ function App() {
2929
name: taskName,
3030
startTime: Date.now(),
3131
duration: 0,
32-
status: 'running'
32+
status: 'running',
33+
totalPausedTime: 0
3334
};
3435
setActiveTask(newTask);
3536
chromeApi.storage.local.set({ activeTask: newTask });
3637
chromeApi.runtime.sendMessage({ type: 'START_TIMER', task: newTask });
3738
};
3839

40+
const pauseTimer = () => {
41+
if (activeTask && activeTask.status === 'running') {
42+
const pausedTask: Task = {
43+
...activeTask,
44+
status: 'paused',
45+
pausedAt: Date.now()
46+
};
47+
setActiveTask(pausedTask);
48+
chromeApi.storage.local.set({ activeTask: pausedTask });
49+
chromeApi.runtime.sendMessage({ type: 'PAUSE_TIMER', task: pausedTask });
50+
}
51+
};
52+
53+
const resumeTimer = () => {
54+
if (activeTask && activeTask.status === 'paused' && activeTask.pausedAt) {
55+
const pauseDuration = Date.now() - activeTask.pausedAt;
56+
const resumedTask: Task = {
57+
...activeTask,
58+
status: 'running',
59+
totalPausedTime: (activeTask.totalPausedTime || 0) + pauseDuration,
60+
pausedAt: undefined
61+
};
62+
setActiveTask(resumedTask);
63+
chromeApi.storage.local.set({ activeTask: resumedTask });
64+
chromeApi.runtime.sendMessage({ type: 'RESUME_TIMER', task: resumedTask });
65+
}
66+
};
67+
3968
const stopTimer = () => {
4069
if (activeTask) {
41-
const duration = Date.now() - activeTask.startTime;
70+
const totalPaused = activeTask.totalPausedTime || 0;
71+
const duration = Date.now() - activeTask.startTime - totalPaused;
4272
const completedTask: Task = {
4373
...activeTask,
4474
duration,
@@ -52,7 +82,7 @@ function App() {
5282
};
5383

5484
return (
55-
<div className="w-[350px] min-h-[400px] bg-gray-50">
85+
<div className="min-w-[350px] min-h-[400px] bg-gray-50">
5686
<header className="bg-indigo-600 text-white p-4">
5787
<div className="flex items-center justify-between mb-4">
5888
<div className="flex items-center gap-2">
@@ -75,29 +105,29 @@ function App() {
75105
activeTask={activeTask}
76106
onStart={startTimer}
77107
onStop={stopTimer}
108+
onPause={pauseTimer}
109+
onResume={resumeTimer}
78110
/>
79111
</div>
80112
) : (
81113
<TaskList tasks={tasks} />
82114
)}
83115
</main>
84116

85-
<footer className="absolute bottom-0 w-full border-t border-gray-200">
117+
<footer className="fixed bottom-0 w-full border-t bg-white border-gray-200">
86118
<div className="flex justify-around p-3">
87119
<button
88120
onClick={() => setView('timer')}
89-
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${
90-
view === 'timer' ? 'text-indigo-600 bg-indigo-50' : 'text-gray-600 hover:bg-gray-100'
91-
}`}
121+
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${view === 'timer' ? 'text-indigo-600 bg-indigo-50' : 'text-gray-600 hover:bg-gray-100'
122+
}`}
92123
>
93124
<Timer className="w-5 h-5" />
94125
Timer
95126
</button>
96127
<button
97128
onClick={() => setView('history')}
98-
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${
99-
view === 'history' ? 'text-indigo-600 bg-indigo-50' : 'text-gray-600 hover:bg-gray-100'
100-
}`}
129+
className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-colors ${view === 'history' ? 'text-indigo-600 bg-indigo-50' : 'text-gray-600 hover:bg-gray-100'
130+
}`}
101131
>
102132
<History className="w-5 h-5" />
103133
History

src/components/TaskList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export default function TaskList({ tasks }: TaskListProps) {
3030
No tasks recorded yet
3131
</div>
3232
) : (
33-
<div className="space-y-3">
33+
<div className="space-y-4">
3434
{tasks.map((task) => (
3535
<div
3636
key={task.id}

src/components/Timer.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import React, { useState, useEffect } from 'react';
2-
import { Play, Square, Clock } from 'lucide-react';
2+
import { Play, Square, Clock, Pause, PlayCircle } from 'lucide-react';
33
import { Task } from '../types';
44

55
interface TimerProps {
66
activeTask: Task | null;
77
onStart: (taskName: string) => void;
88
onStop: () => void;
9+
onPause: () => void;
10+
onResume: () => void;
911
}
1012

11-
export default function Timer({ activeTask, onStart, onStop }: TimerProps) {
13+
export default function Timer({ activeTask, onStart, onStop, onPause, onResume }: TimerProps) {
1214
const [taskName, setTaskName] = useState('');
1315
const [elapsed, setElapsed] = useState(0);
1416

1517
useEffect(() => {
1618
let interval: number;
17-
if (activeTask) {
19+
if (activeTask && activeTask.status === 'running') {
1820
interval = setInterval(() => {
19-
setElapsed(Date.now() - activeTask.startTime);
21+
const totalPaused = activeTask.totalPausedTime || 0;
22+
setElapsed(Date.now() - activeTask.startTime - totalPaused);
2023
}, 1000);
2124
}
2225
return () => clearInterval(interval);
@@ -61,13 +64,31 @@ export default function Timer({ activeTask, onStart, onStop }: TimerProps) {
6164
<Clock className="w-6 h-6 mr-2" />
6265
{formatTime(elapsed)}
6366
</div>
64-
<button
65-
onClick={onStop}
66-
className="w-full flex items-center justify-center gap-2 bg-red-500 text-white py-2 px-4 rounded-lg hover:bg-red-600 transition-colors"
67-
>
68-
<Square className="w-5 h-5" />
69-
Stop Timer
70-
</button>
67+
<div className="w-full flex items-center justify-center gap-2">
68+
69+
<button
70+
onClick={onStop}
71+
className="w-full flex items-center justify-center gap-2 bg-red-500 text-white py-2 px-4 rounded-lg hover:bg-red-600 transition-colors"
72+
>
73+
<Square className="w-5 h-5" />
74+
Stop Timer
75+
</button>
76+
{activeTask.status === 'running' ? (
77+
<button
78+
onClick={onPause}
79+
className="flex items-center justify-center gap-2 bg-yellow-500 text-white py-2 px-4 rounded-lg hover:bg-yellow-600 transition-colors"
80+
>
81+
<Pause />
82+
</button>
83+
) : (
84+
<button
85+
onClick={onResume}
86+
className="flex items-center justify-center gap-2 bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors"
87+
>
88+
<PlayCircle />
89+
</button>
90+
)}
91+
</div>
7192
</div>
7293
</div>
7394
)}

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ export interface Task {
33
name: string;
44
startTime: number;
55
duration: number;
6-
status: 'running' | 'completed';
6+
status: 'running' | 'paused' | 'completed';
7+
pausedAt?: number;
8+
totalPausedTime?: number;
79
}

0 commit comments

Comments
 (0)