Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/no-prs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permissions:
jobs:
close-pr:
runs-on: ubuntu-latest
if: github.actor != github.repository_owner && github.actor != 'github-copilot[bot]' && github.actor != 'copilot-workspace[bot]'
steps:
- name: Close Pull Request
uses: actions/github-script@v7
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "JiraTime",
"version": "1.2.2",
"version": "1.3.0",
"description": "Simple Jira Time Tracking for Developers. By yours truly Bernhard Dorn.",
"author": "Bernhard Dorn",
"action": {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jiratime",
"private": true,
"version": "0.0.0",
"version": "1.3.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down Expand Up @@ -35,4 +35,4 @@
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}
}
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ function App() {
</div>
<div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-1">JiraTime</h2>
<p className="text-sm text-gray-500 dark:text-gray-400">v1.2.0</p>
<p className="text-sm text-gray-500 dark:text-gray-400">v{__APP_VERSION__}</p>
<p className="text-xs text-gray-400 mt-2">
Found a bug? <a href="https://github.com/devdorn/JiraTime/issues" target="_blank" rel="noreferrer" className="text-blue-500 hover:underline">Report an issue</a>
</p>
</div>

<div className="text-gray-600 dark:text-gray-300 space-y-1">
Expand Down
21 changes: 18 additions & 3 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const Settings = ({ onSave, onCancel, showCancel, onThemeChange }: Settin
// Initialize formData with default values. The useEffect hook will load actual settings.
const [formData, setFormData] = useState<Partial<AppSettings>>({
jiraHost: "",
jiraEmail: "",
jiraPat: "",
theme: "system", // Default theme
});
Expand All @@ -37,8 +38,13 @@ export const Settings = ({ onSave, onCancel, showCancel, onThemeChange }: Settin

try {
// Basic validation
if (!formData.jiraHost || !formData.jiraPat) {
throw new Error("All fields are required");
if (!formData.jiraHost || !formData.jiraPat || !formData.jiraEmail) {
throw new Error("Host, Email and PAT fields are required");
}

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(formData.jiraEmail)) {
throw new Error("Invalid email format");
}

let host = formData.jiraHost.trim();
Expand All @@ -51,7 +57,8 @@ export const Settings = ({ onSave, onCancel, showCancel, onThemeChange }: Settin

const cleanSettings: AppSettings = {
jiraHost: host,
jiraPat: formData.jiraPat.trim(),
jiraEmail: formData.jiraEmail?.trim() || "",
jiraPat: formData.jiraPat.trim() || "",
theme: (formData.theme as 'light' | 'dark' | 'system') || 'system',
pinnedTicketKeys: formData.pinnedTicketKeys || [],
filterStatuses: formData.filterStatuses?.trim() || "",
Expand Down Expand Up @@ -109,6 +116,14 @@ export const Settings = ({ onSave, onCancel, showCancel, onThemeChange }: Settin
</select>
</div>

<Input
label="Jira Email"
placeholder="name@company.com"
value={formData.jiraEmail || ""}
onChange={(e) => setFormData({ ...formData, jiraEmail: e.target.value })}

/>

<Input
label="Personal Access Token / API Token"
type="password"
Expand Down
24 changes: 19 additions & 5 deletions src/components/TicketItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { formatDuration, formatDurationFromStart, parseDuration, cn } from "../l
import { Button } from "./ui/Button";
import { Input } from "./ui/Input";
import {
Trash2, Play, Square, ExternalLink, ChevronDown, ChevronUp, Clock,
Play, Square, ExternalLink, ChevronDown, ChevronUp, Clock,
Bug, CheckSquare, Bookmark, Zap, GitCommit, FileQuestion,
HelpCircle, Microscope
HelpCircle, Microscope, PinOff
} from "lucide-react";

// Helper for Issue Type Icon
Expand Down Expand Up @@ -77,6 +77,7 @@ export const TicketItem = ({
}: TicketItemProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const [manualTime, setManualTime] = useState("");
const [description, setDescription] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [liveDuration, setLiveDuration] = useState("");

Expand Down Expand Up @@ -118,8 +119,9 @@ export const TicketItem = ({
checkTouchGrass(seconds);
}

await addWorklog(settings, ticket.id, manualTime);
await addWorklog(settings, ticket.id, manualTime, description);
setManualTime("");
setDescription("");
onRefresh();
// Optional: Show success feedback
} catch (error) {
Expand All @@ -146,8 +148,9 @@ export const TicketItem = ({
seconds = 60;
}

await addWorklog(settings, ticket.id, seconds);
await addWorklog(settings, ticket.id, seconds, description);
onStopTimer();
setDescription("");
onRefresh();
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -221,7 +224,7 @@ export const TicketItem = ({
className="p-1.5 text-gray-400 hover:text-red-600 dark:hover:text-red-400 dark:text-gray-500 transition-colors rounded-full hover:bg-red-50 dark:hover:bg-red-900/20"
title="Unpin Ticket"
>
<Trash2 size={16} />
<PinOff size={16} />
</button>
)}
<div className="text-gray-400 mt-1">
Expand Down Expand Up @@ -284,6 +287,17 @@ export const TicketItem = ({
Log
</Button>
</form>

<div className="pt-2 mt-2 border-t border-gray-100 dark:border-slate-700">
<textarea
placeholder="Work description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="flex w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-xs placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all dark:bg-slate-900 dark:border-slate-700 dark:text-white dark:placeholder:text-gray-500 dark:focus:ring-blue-400 min-h-[36px] resize-y"
rows={1}
disabled={isSubmitting}
/>
</div>
</div>
)}
</div>
Expand Down
3 changes: 3 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// <reference types="vite/client" />

declare const __APP_VERSION__: string;
8 changes: 7 additions & 1 deletion src/lib/jira.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { AppSettings, JiraTicket } from "./types";

const createHeaders = (settings: AppSettings) => {
// Jira Cloud requires Basic Auth with email:token
// Jira Server/DC uses Bearer token (PAT)
const auth = settings.jiraEmail
? `Basic ${btoa(`${settings.jiraEmail}:${settings.jiraPat}`)}`
: `Bearer ${settings.jiraPat}`;

const headers: HeadersInit = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": `Bearer ${settings.jiraPat}`
"Authorization": auth
};
return headers;
};
Expand Down
3 changes: 2 additions & 1 deletion src/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ export const saveSettings = async (settings: AppSettings): Promise<void> => {
};

export const getSettings = async (): Promise<AppSettings | null> => {
const result = await chrome.storage.sync.get(["jiraHost", "jiraPat", "theme", "pinnedTicketKeys", "filterStatuses", "filterIssueTypes"]);
const result = await chrome.storage.sync.get(["jiraHost", "jiraEmail", "jiraPat", "theme", "pinnedTicketKeys", "filterStatuses", "filterIssueTypes"]);
if (result.jiraHost && result.jiraPat) {
return {
jiraHost: result.jiraHost as string,
jiraEmail: (result.jiraEmail as string) || "",
jiraPat: result.jiraPat as string,
theme: (result.theme as 'light' | 'dark' | 'system') || 'system',
pinnedTicketKeys: (result.pinnedTicketKeys as string[]) || [],
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface AppSettings {
jiraHost: string;
jiraEmail: string;
jiraPat: string;
theme: 'light' | 'dark' | 'system';
pinnedTicketKeys: string[];
Expand Down
3 changes: 3 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export default defineConfig({
react(),
crx({ manifest: manifest as any }),
],
define: {
'__APP_VERSION__': JSON.stringify(manifest.version),
},
})