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: 0 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Work Hours Tracker</title>
</head>
Expand Down
Binary file added public/favicon/initial.ico
Binary file not shown.
Binary file added public/favicon/on-break.ico
Binary file not shown.
Binary file added public/favicon/working.ico
Binary file not shown.
7 changes: 7 additions & 0 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { type User } from "./lib/tracker";
import WorkdayForm from "./ui/WorkdayForm.svelte";
import { getDatabase } from "./lib/database";
import Favicon from "./ui/Favicon.svelte";

let user: User | null = $state(null);

Expand All @@ -30,6 +31,12 @@
}
</script>

<svelte:head>
{#if !user}
<Favicon iconName="initial.ico" />
{/if}
</svelte:head>

<main>
<h1>Welcome {user ? user.settings.username : "to Work Hours Tracker"}</h1>

Expand Down
9 changes: 9 additions & 0 deletions src/ui/Favicon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
const { iconName }: { iconName: string } = $props();
</script>

<link
data-testid="favicon"
rel="icon"
href="{import.meta.env.BASE_URL}favicon/{iconName}"
/>
38 changes: 38 additions & 0 deletions src/ui/Status.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
const { tracker } = $props();

const mapping = {
"not-working": "Not working",
working: "Working",
"on-break": "On break",
};

function getStatusClass() {
if (!tracker.hasWorkdayStarted()) {
return "not-working";
}

return tracker.hasBreakStarted() ? "on-break" : "working";
}
</script>

<div class="status {getStatusClass()}">{mapping[getStatusClass()]}</div>

<style>
.status {
font-size: 1.3rem;
font-weight: 700;
}

.working {
color: #7fff00;
}

.not-working {
color: #ff0000;
}

.on-break {
color: #ffea00;
}
</style>
2 changes: 1 addition & 1 deletion src/ui/WorkdayEvents.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{#if type === "start-workday"}
Workday Started
{:else if type === "break-ongoing"}
On Break
Break started
{:else if type === "break"}
Break
{:else if type === "end-workday"}
Expand Down
14 changes: 14 additions & 0 deletions src/ui/WorkdayForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { createTracker, type User } from "../lib/tracker";
import Button from "./Button.svelte";
import ConfirmationModal from "./ConfirmationModal.svelte";
import Favicon from "./Favicon.svelte";
import Status from "./Status.svelte";
import WorkdayEvents from "./WorkdayEvents.svelte";
import WorkDuration from "./WorkDuration.svelte";

Expand All @@ -26,6 +28,18 @@
}
</script>

<svelte:head>
<Favicon
iconName={tracker.hasWorkdayStarted()
? tracker.hasBreakStarted()
? "on-break.ico"
: "working.ico"
: "initial.ico"}
/>
</svelte:head>

<Status {tracker} />

{#if tracker.hasWorkdayStarted()}
<WorkDuration timeWorked={tracker.getTimeWorked()} />
{/if}
Expand Down
47 changes: 47 additions & 0 deletions tests/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ test("first visit, full workday", async ({ page }) => {
await page.goto("/");

await expect(page).toHaveTitle("Work Hours Tracker");
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/initial.ico",
);
await expect(
page.getByRole("heading", { name: "Welcome to Work Hours Tracker" }),
).toBeVisible();
Expand Down Expand Up @@ -36,6 +40,18 @@ test("first visit, full workday", async ({ page }) => {
page.getByRole("button", { name: "End Workday" }),
).toBeDisabled();

await expect(page.getByText("Not working")).toBeVisible();
await expect(page.getByText("Not working")).toHaveCSS(
"color",
"rgb(255, 0, 0)",
);

// Favicon updated
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/initial.ico",
);

// Workday starts at 8:05:00
await page.clock.setFixedTime(new Date(2025, 2, 2, 8, 5, 0));
await page.getByRole("button", { name: "Start Workday" }).click();
Expand All @@ -45,6 +61,15 @@ test("first visit, full workday", async ({ page }) => {
).toBeDisabled();
await expect(page.getByRole("button", { name: "Start Break" })).toBeEnabled();
await expect(page.getByRole("button", { name: "End Workday" })).toBeEnabled();
await expect(page.getByText("Working")).toBeVisible();
await expect(page.getByText("Working")).toHaveCSS(
"color",
"rgb(127, 255, 0)",
);
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/working.ico",
);

// Take a break at 8:35:00
await page.clock.setFixedTime(new Date(2025, 2, 2, 8, 35, 0));
Expand All @@ -60,6 +85,15 @@ test("first visit, full workday", async ({ page }) => {
page.getByRole("button", { name: "Start Break" }),
).not.toBeVisible();
await expect(page.getByRole("button", { name: "End Break" })).toBeVisible();
await expect(page.getByText("On break")).toBeVisible();
await expect(page.getByText("On break")).toHaveCSS(
"color",
"rgb(255, 234, 0)",
);
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/on-break.ico",
);

// End the break at 9:05:00
await page.clock.setFixedTime(new Date(2025, 2, 2, 9, 5, 0));
Expand Down Expand Up @@ -89,6 +123,10 @@ test("first visit, full workday", async ({ page }) => {
page.getByRole("button", { name: "Yes, I'm done for today" }),
).toBeVisible();
await expect(page.getByRole("button", { name: "Cancel" })).toBeVisible();
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/working.ico",
);

await page.getByRole("button", { name: "Yes, I'm done for today" }).click();

Expand All @@ -115,6 +153,15 @@ test("first visit, full workday", async ({ page }) => {
await expect(
page.getByRole("button", { name: "End Workday" }),
).toBeDisabled();
await expect(page.getByText("Not working")).toBeVisible();
await expect(page.getByText("Not working")).toHaveCSS(
"color",
"rgb(255, 0, 0)",
);
await expect(page.getByTestId("favicon")).toHaveAttribute(
"href",
"/work-hours-tracker/favicon/initial.ico",
);
});

test("User data persists through reloads", async ({ page }) => {
Expand Down