Cleaned up the theme a bit

This commit is contained in:
2026-02-14 01:11:01 -07:00
parent e99e042eea
commit 6233380682
38 changed files with 126 additions and 168 deletions

View File

@@ -34,7 +34,6 @@
"vue-chartjs": "^5.3.3"
},
"devDependencies": {
"@catppuccin/daisyui": "^2.1.1",
"@react-pdf/types": "^2.9.2",
"@types/jsonwebtoken": "^9.0.10",
"drizzle-kit": "0.31.9"

18
pnpm-lock.yaml generated
View File

@@ -69,9 +69,6 @@ importers:
specifier: ^5.3.3
version: 5.3.3(chart.js@4.5.1)(vue@3.5.28(typescript@5.9.3))
devDependencies:
'@catppuccin/daisyui':
specifier: ^2.1.1
version: 2.1.1(tailwindcss@4.1.18)
'@react-pdf/types':
specifier: ^2.9.2
version: 2.9.2
@@ -285,14 +282,6 @@ packages:
resolution: {integrity: sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==}
engines: {node: '>=18'}
'@catppuccin/daisyui@2.1.1':
resolution: {integrity: sha512-PrZttjj8kwfDBJ34sR+DN25Xtjvxx4T5p8uu/iiGYZR8UOsNwzMlO/alYDBwwTOLzP1NKLNRax09kCT39+QM+A==}
peerDependencies:
tailwindcss: ^4.0.17
'@catppuccin/palette@1.7.1':
resolution: {integrity: sha512-aRc1tbzrevOTV7nFTT9SRdF26w/MIwT4Jwt4fDMc9itRZUDXCuEDBLyz4TQMlqO9ZP8mf5Hu4Jr6D03NLFc6Gw==}
'@ceereals/vue-pdf@0.2.1':
resolution: {integrity: sha512-E7Y2GyHTYEmZ2U5ZlVuJrOWdHhco49ZTdKVOo/wcOhlfNFG+W5pAZ6rOcaua+owspC4BgGzAxlmqj/jdEM9ehA==}
peerDependencies:
@@ -4031,13 +4020,6 @@ snapshots:
dependencies:
fontkitten: 1.0.2
'@catppuccin/daisyui@2.1.1(tailwindcss@4.1.18)':
dependencies:
'@catppuccin/palette': 1.7.1
tailwindcss: 4.1.18
'@catppuccin/palette@1.7.1': {}
'@ceereals/vue-pdf@0.2.1(vue@3.5.28(typescript@5.9.3))':
dependencies:
'@react-pdf/fns': 3.1.2

View File

@@ -9,7 +9,7 @@ const initial = name ? name.charAt(0).toUpperCase() : '?';
---
<div class:list={["avatar placeholder", className]}>
<div class="bg-primary/15 text-primary w-9 h-9 rounded-full flex items-center justify-center">
<div class="bg-base-300 text-primary w-9 h-9 rounded-full flex items-center justify-center">
<span class="text-sm font-semibold">{initial}</span>
</div>
</div>

View File

@@ -148,7 +148,7 @@ function clearForm() {
<template>
<div
class="card bg-base-200/50 backdrop-blur-sm shadow-lg border border-base-300/50 hover:border-base-300 transition-all duration-200"
class="card bg-base-200 backdrop-blur-sm shadow-lg border border-base-content/20 hover:border-base-content/20 transition-all duration-200"
>
<div class="card-body gap-6">
<div class="flex justify-between items-center">
@@ -183,7 +183,7 @@ function clearForm() {
<select
id="manual-client"
v-model="selectedClientId"
class="select select-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="select select-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
>
<option value="">Select a client...</option>
@@ -203,7 +203,7 @@ function clearForm() {
id="manual-start-date"
v-model="startDate"
type="date"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
/>
</div>
@@ -216,7 +216,7 @@ function clearForm() {
id="manual-start-time"
v-model="startTime"
type="time"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
/>
</div>
@@ -232,7 +232,7 @@ function clearForm() {
id="manual-end-date"
v-model="endDate"
type="date"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
/>
</div>
@@ -245,7 +245,7 @@ function clearForm() {
id="manual-end-time"
v-model="endTime"
type="time"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
/>
</div>
@@ -261,7 +261,7 @@ function clearForm() {
v-model="description"
type="text"
placeholder="What did you work on?"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isSubmitting"
/>
</div>
@@ -278,7 +278,7 @@ function clearForm() {
'badge badge-lg cursor-pointer transition-all hover:scale-105',
selectedTagId === tag.id
? 'badge-primary shadow-lg shadow-primary/20'
: 'badge-outline hover:bg-base-300/50',
: 'badge-outline hover:bg-base-300',
]"
:disabled="isSubmitting"
type="button"

View File

@@ -18,12 +18,12 @@ const { title, value, description, icon, color = 'text-primary', valueClass } =
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wider text-base-content/60">{title}</span>
{icon && (
<div class:list={[color, "opacity-40"]}>
<div class:list={[color, "opacity-70"]}>
<Icon name={icon} class="w-5 h-5" />
</div>
)}
</div>
<div class:list={["text-2xl font-bold", color, valueClass]}>{value}</div>
{description && <div class="text-xs text-base-content/50">{description}</div>}
{description && <div class="text-xs text-base-content/60">{description}</div>}
</div>
</div>

View File

@@ -1,22 +1,22 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Icon from './Icon.vue';
import { ref, onMounted } from "vue";
import Icon from "./Icon.vue";
const theme = ref('macchiato');
const theme = ref("sunset");
onMounted(() => {
const stored = localStorage.getItem('theme');
const stored = localStorage.getItem("theme");
if (stored) {
theme.value = stored;
document.documentElement.setAttribute('data-theme', stored);
document.documentElement.setAttribute("data-theme", stored);
}
});
function toggleTheme() {
const newTheme = theme.value === 'macchiato' ? 'latte' : 'macchiato';
const newTheme = theme.value === "sunset" ? "winter" : "sunset";
theme.value = newTheme;
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
}
</script>
@@ -26,9 +26,6 @@ function toggleTheme() {
class="btn btn-ghost btn-circle"
aria-label="Toggle Theme"
>
<Icon
:name="theme === 'macchiato' ? 'moon' : 'sun'"
class="w-5 h-5"
/>
<Icon :name="theme === 'sunset' ? 'moon' : 'sun'" class="w-5 h-5" />
</button>
</template>

View File

@@ -118,7 +118,7 @@ async function stopTimer() {
<template>
<div
class="card bg-base-200/50 backdrop-blur-sm shadow-lg border border-base-300/50 mb-6 hover:border-base-300 transition-all duration-200"
class="card bg-base-200 backdrop-blur-sm shadow-lg border border-base-content/20 mb-6 hover:border-base-content/20 transition-all duration-200"
>
<div class="card-body gap-6">
<!-- Client Row -->
@@ -129,7 +129,7 @@ async function stopTimer() {
<select
id="timer-client"
v-model="selectedClientId"
class="select select-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="select select-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isRunning"
>
<option value="">Select a client...</option>
@@ -149,7 +149,7 @@ async function stopTimer() {
v-model="description"
type="text"
placeholder="What are you working on?"
class="input input-bordered w-full bg-base-300/50 hover:bg-base-300 focus:bg-base-300 border-base-300/50 focus:border-primary transition-colors"
class="input input-bordered w-full bg-base-300 hover:bg-base-300 focus:bg-base-300 border-base-content/20 focus:border-primary transition-colors"
:disabled="isRunning"
/>
</div>
@@ -166,7 +166,7 @@ async function stopTimer() {
'badge badge-lg cursor-pointer transition-all hover:scale-105',
selectedTagId === tag.id
? 'badge-primary shadow-lg shadow-primary/20'
: 'badge-outline hover:bg-base-300/50',
: 'badge-outline hover:bg-base-300',
]"
:disabled="isRunning"
type="button"

View File

@@ -107,7 +107,7 @@ function closeShowTokenModal() {
<template>
<div>
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
<div class="card-body p-4 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="card-title text-lg sm:text-xl">

View File

@@ -94,7 +94,7 @@ async function deletePasskey(id: string) {
</script>
<template>
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
<div class="card-body p-4 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="card-title text-lg sm:text-xl">

View File

@@ -86,7 +86,7 @@ async function changePassword() {
<span>{{ message.text }}</span>
</div>
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
<div class="card-body p-4 sm:p-6">
<h2 class="card-title mb-6 text-lg sm:text-xl">
<Icon name="key" class="w-5 h-5 sm:w-6 sm:h-6" />

View File

@@ -71,7 +71,7 @@ async function updateProfile() {
<span>{{ message.text }}</span>
</div>
<div class="card bg-base-100 shadow-xl border border-base-200 mb-6">
<div class="card bg-base-100 shadow-xl border border-base-content/20 mb-6">
<div class="card-body p-4 sm:p-6">
<h2 class="card-title mb-6 text-lg sm:text-xl">
<Icon name="user-circle" class="w-5 h-5 sm:w-6 sm:h-6" />

View File

@@ -28,7 +28,6 @@ const userMemberships = await db.select({
.all();
const currentTeamId = Astro.cookies.get('currentTeamId')?.value || userMemberships[0]?.organization.id;
const currentTeam = userMemberships.find(m => m.organization.id === currentTeamId);
const navItems = [
{ href: '/dashboard', label: 'Dashboard', icon: 'home', exact: true },
@@ -54,8 +53,8 @@ function isActive(item: { href: string; exact?: boolean }) {
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<script>
const theme = localStorage.getItem('theme') || 'macchiato';
<script is:inline>
const theme = localStorage.getItem('theme') || 'sunset';
document.documentElement.setAttribute('data-theme', theme);
</script>
</head>
@@ -64,7 +63,7 @@ function isActive(item: { href: string; exact?: boolean }) {
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col h-full overflow-auto">
<!-- Mobile Navbar -->
<div class="navbar bg-base-100 sticky top-0 z-50 lg:hidden border-b border-base-200">
<div class="navbar bg-base-100 sticky top-0 z-50 lg:hidden border-b border-base-content/20">
<div class="flex-none">
<label for="my-drawer-2" aria-label="open sidebar" class="btn btn-square btn-ghost btn-sm">
<Icon name="bars-3" class="w-5 h-5" />
@@ -87,7 +86,7 @@ function isActive(item: { href: string; exact?: boolean }) {
<div class="drawer-side z-50">
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
<aside class="bg-base-200 min-h-full w-72 flex flex-col border-r border-base-300/40">
<aside class="bg-base-200 min-h-full w-72 flex flex-col border-r border-base-content/20">
<!-- Logo -->
<div class="px-5 pt-5 pb-3">
<a href="/dashboard" class="flex items-center gap-2.5 group">
@@ -100,7 +99,7 @@ function isActive(item: { href: string; exact?: boolean }) {
{userMemberships.length > 0 && (
<div class="px-4 pb-2">
<select
class="select select-sm w-full bg-base-300/40 border-base-300/60 focus:border-primary/50 focus:outline-none text-sm font-medium"
class="select select-sm w-full bg-base-300 border-base-content/20 focus:border-primary focus:outline-none text-sm font-medium"
id="team-switcher"
aria-label="Switch team"
>
@@ -135,8 +134,8 @@ function isActive(item: { href: string; exact?: boolean }) {
<a href={item.href} class:list={[
"rounded-lg gap-3 px-3 py-2.5 font-medium text-sm",
isActive(item)
? "bg-primary/10 text-primary"
: "text-base-content/70 hover:text-base-content hover:bg-base-300/50"
? "bg-primary text-primary-content"
: "text-base-content/70 hover:text-base-content hover:bg-base-300"
]}>
<Icon name={item.icon} class="w-[18px] h-[18px]" />
{item.label}
@@ -153,8 +152,8 @@ function isActive(item: { href: string; exact?: boolean }) {
<a href="/admin" class:list={[
"rounded-lg gap-3 px-3 py-2.5 font-medium text-sm",
Astro.url.pathname.startsWith("/admin")
? "bg-primary/10 text-primary"
: "text-base-content/70 hover:text-base-content hover:bg-base-300/50"
? "bg-primary text-primary-content"
: "text-base-content/70 hover:text-base-content hover:bg-base-300"
]}>
<Icon name="cog-6-tooth" class="w-[18px] h-[18px]" />
Site Admin
@@ -166,25 +165,25 @@ function isActive(item: { href: string; exact?: boolean }) {
</nav>
<!-- Bottom Section -->
<div class="mt-auto border-t border-base-300/40">
<div class="mt-auto border-t border-base-content/20">
<div class="p-3">
<a href="/dashboard/settings" class="flex items-center gap-3 rounded-lg p-2.5 hover:bg-base-300/40 group">
<a href="/dashboard/settings" class="flex items-center gap-3 rounded-lg p-2.5 hover:bg-base-300 group">
<Avatar name={user.name} />
<div class="flex-1 min-w-0">
<div class="font-medium text-sm truncate">{user.name}</div>
<div class="text-xs text-base-content/50 truncate">{user.email}</div>
<div class="text-xs text-base-content/60 truncate">{user.email}</div>
</div>
<Icon name="chevron-right" class="w-4 h-4 text-base-content/30 group-hover:text-base-content/50" />
<Icon name="chevron-right" class="w-4 h-4 text-base-content/50 group-hover:text-base-content/70" />
</a>
</div>
<div class="flex items-center justify-between px-5 pb-2">
<span class="text-xs text-base-content/40 font-medium">Theme</span>
<span class="text-xs text-base-content/60 font-medium">Theme</span>
<ThemeToggle client:load />
</div>
<div class="px-3 pb-3">
<button id="logout-btn" type="button" class="btn btn-ghost btn-sm btn-block justify-start gap-2 text-base-content/60 hover:text-error hover:bg-error/10 font-medium">
<button id="logout-btn" type="button" class="btn btn-ghost btn-sm btn-block justify-start gap-2 text-base-content/60 hover:text-error hover:bg-base-300 font-medium">
<Icon name="arrow-right-on-rectangle" class="w-[18px] h-[18px]" />
Logout
</button>

View File

@@ -17,8 +17,8 @@ const { title } = Astro.props;
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<script>
const theme = localStorage.getItem('theme') || 'macchiato';
<script is:inline>
const theme = localStorage.getItem('theme') || 'sunset';
document.documentElement.setAttribute('data-theme', theme);
</script>
</head>

View File

@@ -32,7 +32,7 @@ const allUsers = await db.select().from(users).all();
title="Total Users"
value={String(allUsers.length)}
description="Registered accounts"
icon="heroicons:users"
icon="users"
color="text-primary"
/>
</div>
@@ -63,7 +63,7 @@ const allUsers = await db.select().from(users).all();
<!-- Users List -->
<div class="card card-border bg-base-100">
<div class="card-body p-0">
<div class="px-4 py-3 border-b border-base-200">
<div class="px-4 py-3 border-b border-base-content/20">
<h2 class="text-sm font-semibold">All Users</h2>
</div>
<div class="overflow-x-auto">
@@ -93,7 +93,7 @@ const allUsers = await db.select().from(users).all();
<span class="badge badge-xs badge-ghost">No</span>
)}
</td>
<td class="text-base-content/40">{u.createdAt?.toLocaleDateString() ?? 'N/A'}</td>
<td class="text-base-content/60">{u.createdAt?.toLocaleDateString() ?? 'N/A'}</td>
</tr>
))}
</tbody>

View File

@@ -31,7 +31,7 @@ const allClients = await db.select()
<div class="card-body p-4 gap-1">
<h2 class="font-semibold">{client.name}</h2>
{client.email && <p class="text-sm text-base-content/60">{client.email}</p>}
<p class="text-xs text-base-content/40">Created {client.createdAt?.toLocaleDateString() ?? 'N/A'}</p>
<p class="text-xs text-base-content/60">Created {client.createdAt?.toLocaleDateString() ?? 'N/A'}</p>
<div class="card-actions justify-end mt-3">
<a href={`/dashboard/clients/${client.id}`} class="btn btn-xs btn-ghost">View</a>
<a href={`/dashboard/clients/${client.id}/edit`} class="btn btn-xs btn-primary">Edit</a>
@@ -43,7 +43,7 @@ const allClients = await db.select()
{allClients.length === 0 && (
<div class="flex flex-col items-center justify-center py-12 text-center">
<p class="text-base-content/50 text-sm mb-4">No clients yet</p>
<p class="text-base-content/60 text-sm mb-4">No clients yet</p>
<a href="/dashboard/clients/new" class="btn btn-primary btn-sm">Add Your First Client</a>
</div>
)}

View File

@@ -75,7 +75,7 @@ if (!client) return Astro.redirect('/dashboard/clients');
/>
</fieldset>
<div class="divider text-xs text-base-content/40">Address Details</div>
<div class="divider text-xs text-base-content/60">Address Details</div>
<fieldset class="fieldset">
<legend class="fieldset-legend text-xs">Street Address (optional)</legend>

View File

@@ -149,7 +149,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
<div class="space-y-3">
<div>
<div class="text-xs text-base-content/40">Created</div>
<div class="text-xs text-base-content/60">Created</div>
<div class="text-sm">{client.createdAt?.toLocaleDateString() ?? 'N/A'}</div>
</div>
</div>
@@ -160,7 +160,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
<!-- Recent Activity -->
<div class="card card-border bg-base-100">
<div class="card-body p-0">
<div class="px-4 py-3 border-b border-base-200">
<div class="px-4 py-3 border-b border-base-content/20">
<h2 class="text-sm font-semibold">Recent Activity</h2>
</div>
@@ -191,7 +191,7 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
) : '-'}
</td>
<td class="text-base-content/60">{entryUser?.name || 'Unknown'}</td>
<td class="text-base-content/40">{entry.startTime.toLocaleDateString()}</td>
<td class="text-base-content/60">{entry.startTime.toLocaleDateString()}</td>
<td class="font-mono">{formatTimeRange(entry.startTime, entry.endTime)}</td>
</tr>
))}
@@ -199,13 +199,13 @@ const totalEntriesCount = totalEntriesResult?.count || 0;
</table>
</div>
) : (
<div class="text-center py-8 text-base-content/40 text-sm">
<div class="text-center py-8 text-base-content/60 text-sm">
No time entries recorded for this client yet.
</div>
)}
{recentEntries.length > 0 && (
<div class="flex justify-center py-3 border-t border-base-200">
<div class="flex justify-center py-3 border-t border-base-content/20">
<a href={`/dashboard/tracker?client=${client.id}`} class="btn btn-ghost btn-xs">
View All Entries
</a>

View File

@@ -45,7 +45,7 @@ if (!user) return Astro.redirect('/login');
/>
</fieldset>
<div class="divider text-xs text-base-content/40">Address Details</div>
<div class="divider text-xs text-base-content/60">Address Details</div>
<fieldset class="fieldset">
<legend class="fieldset-legend text-xs">Street Address (optional)</legend>

View File

@@ -201,9 +201,9 @@ const hasMembership = userOrgs.length > 0;
{stats.recentEntries.length > 0 ? (
<ul class="space-y-2 mt-3">
{stats.recentEntries.map(({ entry, client, tag }) => (
<ColorDot client:load as="li" color={tag?.color || 'oklch(var(--p))'} borderColor class="p-2.5 rounded-lg bg-base-200/50 border-l-3 hover:bg-base-200 transition-colors">
<ColorDot client:load as="li" color={tag?.color || 'oklch(var(--p))'} borderColor class="p-2.5 rounded-lg bg-base-200 border-l-3 hover:bg-base-300 transition-colors">
<div class="font-medium text-sm">{client.name}</div>
<div class="text-xs text-base-content/50 mt-0.5 flex flex-wrap gap-2 items-center">
<div class="text-xs text-base-content/60 mt-0.5 flex flex-wrap gap-2 items-center">
<span class="flex gap-1 flex-wrap">
{tag ? (
<span class="badge badge-xs badge-outline">{tag.name}</span>
@@ -216,8 +216,8 @@ const hasMembership = userOrgs.length > 0;
</ul>
) : (
<div class="flex flex-col items-center justify-center py-6 text-center mt-3">
<Icon name="clock" class="w-10 h-10 text-base-content/15 mb-2" />
<p class="text-base-content/40 text-sm">No recent time entries</p>
<Icon name="clock" class="w-10 h-10 text-base-content/30 mb-2" />
<p class="text-base-content/60 text-sm">No recent time entries</p>
</div>
)}
</div>

View File

@@ -113,10 +113,10 @@ const isDraft = invoice.status === 'draft';
</form>
)}
<div class="dropdown dropdown-end">
<div role="button" tabindex="0" class="btn btn-square btn-ghost btn-sm border border-base-200">
<div role="button" tabindex="0" class="btn btn-square btn-ghost btn-sm border border-base-content/20">
<Icon name="ellipsis-horizontal" class="w-4 h-4" />
</div>
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-200">
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20">
<li>
<a href={`/dashboard/invoices/${invoice.id}/edit`}>
<Icon name="pencil-square" class="w-4 h-4" />
@@ -174,7 +174,7 @@ const isDraft = invoice.status === 'draft';
)}
</div>
<div class="text-right">
<div class="text-4xl font-light text-base-content/30 uppercase tracking-widest mb-4">
<div class="text-4xl font-light text-base-content/50 uppercase tracking-widest mb-4">
{invoice.type}
</div>
<div class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
@@ -190,7 +190,7 @@ const isDraft = invoice.status === 'draft';
<!-- Bill To -->
<div class="mb-12">
<div class="text-xs font-bold uppercase tracking-wider text-base-content/40 mb-2">Bill To</div>
<div class="text-xs font-bold uppercase tracking-wider text-base-content/60 mb-2">Bill To</div>
{client ? (
<div>
<div class="font-bold text-lg">{client.name}</div>
@@ -209,7 +209,7 @@ const isDraft = invoice.status === 'draft';
)}
</div>
) : (
<div class="italic text-base-content/40">Client deleted</div>
<div class="italic text-base-content/60">Client deleted</div>
)}
</div>
@@ -218,7 +218,7 @@ const isDraft = invoice.status === 'draft';
<div class="overflow-x-auto">
<table class="w-full min-w-150">
<thead>
<tr class="border-b-2 border-base-200 text-left text-xs font-bold uppercase tracking-wider text-base-content/40">
<tr class="border-b-2 border-base-content/20 text-left text-xs font-bold uppercase tracking-wider text-base-content/60">
<th class="py-3">Description</th>
<th class="py-3 text-right w-24">Qty</th>
<th class="py-3 text-right w-32">Price</th>
@@ -226,7 +226,7 @@ const isDraft = invoice.status === 'draft';
{isDraft && <th class="py-3 w-10"></th>}
</tr>
</thead>
<tbody class="divide-y divide-base-200">
<tbody class="divide-y divide-base-content/20">
{items.map(item => (
<tr>
<td class="py-4">{item.description}</td>
@@ -237,7 +237,7 @@ const isDraft = invoice.status === 'draft';
<td class="py-4 text-right">
<form method="POST" action={`/api/invoices/${invoice.id}/items/delete`}>
<input type="hidden" name="itemId" value={item.id} />
<button type="submit" class="btn btn-ghost btn-xs btn-square text-error opacity-50 hover:opacity-100">
<button type="submit" class="btn btn-ghost btn-xs btn-square text-error opacity-70 hover:opacity-100">
<Icon name="trash" class="w-4 h-4" />
</button>
</form>
@@ -247,7 +247,7 @@ const isDraft = invoice.status === 'draft';
))}
{items.length === 0 && (
<tr>
<td colspan={isDraft ? 5 : 4} class="py-8 text-center text-base-content/40 italic">
<td colspan={isDraft ? 5 : 4} class="py-8 text-center text-base-content/60 italic">
No items added yet.
</td>
</tr>
@@ -266,7 +266,7 @@ const isDraft = invoice.status === 'draft';
</ModalButton>
</div>
<form method="POST" action={`/api/invoices/${invoice.id}/items/add`} class="bg-base-200/50 p-4 rounded-lg mb-8 border border-base-200">
<form method="POST" action={`/api/invoices/${invoice.id}/items/add`} class="bg-base-200 p-4 rounded-lg mb-8 border border-base-content/20">
<h4 class="text-xs font-semibold mb-3">Add Item</h4>
<div class="grid grid-cols-1 sm:grid-cols-12 gap-3 items-end">
<div class="sm:col-span-6">
@@ -329,8 +329,8 @@ const isDraft = invoice.status === 'draft';
<!-- Notes -->
{invoice.notes && (
<div class="mt-12 pt-8 border-t border-base-200">
<div class="text-xs font-bold uppercase tracking-wider text-base-content/40 mb-2">Notes</div>
<div class="mt-12 pt-8 border-t border-base-content/20">
<div class="text-xs font-bold uppercase tracking-wider text-base-content/60 mb-2">Notes</div>
<div class="text-sm whitespace-pre-wrap opacity-80">{invoice.notes}</div>
</div>
)}

View File

@@ -211,8 +211,8 @@ const getStatusColor = (status: string) => {
<div class="card card-border bg-base-100">
<div class="card-body p-0">
<div class="px-4 py-3 border-b border-base-200">
<p class="text-xs text-base-content/50">
<div class="px-4 py-3 border-b border-base-content/20">
<p class="text-xs text-base-content/60">
Showing <span class="font-semibold text-base-content">{allInvoices.length}</span>
{allInvoices.length === 1 ? 'result' : 'results'}
{selectedYear === 'current' ? ` for ${currentYear} (year to date)` : ` for ${selectedYear}`}
@@ -235,7 +235,7 @@ const getStatusColor = (status: string) => {
<tbody>
{allInvoices.length === 0 ? (
<tr>
<td colspan="8" class="text-center py-8 text-base-content/50 text-sm">
<td colspan="8" class="text-center py-8 text-base-content/60 text-sm">
No invoices or quotes found. Create one to get started.
</td>
</tr>
@@ -251,7 +251,7 @@ const getStatusColor = (status: string) => {
{client ? (
<div class="font-medium">{client.name}</div>
) : (
<span class="text-base-content/40 italic">Deleted Client</span>
<span class="text-base-content/60 italic">Deleted Client</span>
)}
</td>
<td>{invoice.issueDate.toLocaleDateString()}</td>
@@ -272,7 +272,7 @@ const getStatusColor = (status: string) => {
<div role="button" tabindex="0" class="btn btn-ghost btn-xs btn-square">
<Icon name="ellipsis-vertical" class="w-4 h-4" />
</div>
<ul tabindex="0" class="dropdown-content menu p-2 bg-base-100 rounded-box w-52 border border-base-200 z-100">
<ul tabindex="0" class="dropdown-content menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20 z-100">
<li>
<a href={`/dashboard/invoices/${invoice.id}`}>
<Icon name="eye" class="w-4 h-4" />
@@ -306,7 +306,7 @@ const getStatusColor = (status: string) => {
<li>
<ConfirmForm client:load message="Are you sure? This action cannot be undone." action="/api/invoices/delete" class="w-full">
<input type="hidden" name="id" value={invoice.id} />
<button type="submit" class="w-full justify-start text-error hover:bg-error/10">
<button type="submit" class="w-full justify-start text-error hover:bg-base-300">
<Icon name="trash" class="w-4 h-4" />
Delete
</button>

View File

@@ -104,11 +104,11 @@ const defaultDueDate = nextMonth.toISOString().split('T')[0];
<fieldset class="fieldset">
<legend class="fieldset-legend text-xs">Document Type</legend>
<div class="flex gap-3">
<label class="label cursor-pointer justify-start gap-2 border border-base-200 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-primary/5 transition-all font-medium text-sm" for="document-type-invoice">
<label class="label cursor-pointer justify-start gap-2 border border-base-content/20 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-base-200 transition-all font-medium text-sm" for="document-type-invoice">
<input type="radio" id="document-type-invoice" name="type" value="invoice" class="radio radio-primary radio-sm" checked />
Invoice
</label>
<label class="label cursor-pointer justify-start gap-2 border border-base-200 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-primary/5 transition-all font-medium text-sm" for="document-type-quote">
<label class="label cursor-pointer justify-start gap-2 border border-base-content/20 rounded-lg p-3 flex-1 hover:border-primary has-checked:border-primary has-checked:bg-base-200 transition-all font-medium text-sm" for="document-type-quote">
<input type="radio" id="document-type-quote" name="type" value="quote" class="radio radio-primary radio-sm" />
Quote / Estimate
</label>

View File

@@ -394,11 +394,11 @@ function getTimeRangeLabel(range: string) {
<div class="stat-title text-xs">Total Invoices</div>
<div class="stat-value text-2xl">{invoiceStats.total}</div>
</div>
<div class="stat bg-success/10 rounded-lg">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title text-xs">Paid</div>
<div class="stat-value text-2xl text-success">{invoiceStats.paid}</div>
</div>
<div class="stat bg-info/10 rounded-lg">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title text-xs">Sent</div>
<div class="stat-value text-2xl text-info">{invoiceStats.sent}</div>
</div>
@@ -430,15 +430,15 @@ function getTimeRangeLabel(range: string) {
<div class="stat-title text-xs">Total Quotes</div>
<div class="stat-value text-2xl">{quoteStats.total}</div>
</div>
<div class="stat bg-success/10 rounded-lg">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title text-xs">Accepted</div>
<div class="stat-value text-2xl text-success">{quoteStats.accepted}</div>
</div>
<div class="stat bg-info/10 rounded-lg">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title text-xs">Pending</div>
<div class="stat-value text-2xl text-info">{quoteStats.sent}</div>
</div>
<div class="stat bg-error/10 rounded-lg">
<div class="stat bg-base-200 rounded-lg">
<div class="stat-title text-xs">Declined</div>
<div class="stat-value text-2xl text-error">{quoteStats.declined}</div>
</div>
@@ -591,7 +591,7 @@ function getTimeRangeLabel(range: string) {
<td>
<div>
<div class="font-medium">{stat.member.name}</div>
<div class="text-xs text-base-content/40">{stat.member.email}</div>
<div class="text-xs text-base-content/60">{stat.member.email}</div>
</div>
</td>
<td class="font-mono text-sm">{formatDuration(stat.totalTime)}</td>
@@ -738,7 +738,7 @@ function getTimeRangeLabel(range: string) {
<tr>
<td class="whitespace-nowrap">
{e.entry.startTime.toLocaleDateString()}<br/>
<span class="text-xs text-base-content/40">
<span class="text-xs text-base-content/60">
{e.entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</span>
</td>
@@ -753,7 +753,7 @@ function getTimeRangeLabel(range: string) {
<span>{e.tag.name}</span>
</div>
) : (
<span class="text-base-content/30">-</span>
<span class="text-base-content/60">-</span>
)}
</td>
<td class="text-base-content/60">{e.entry.description || '-'}</td>
@@ -770,9 +770,9 @@ function getTimeRangeLabel(range: string) {
</div>
) : (
<div class="flex flex-col items-center justify-center py-10 text-center">
<Icon name="inbox" class="w-12 h-12 text-base-content/15 mb-3" />
<Icon name="inbox" class="w-12 h-12 text-base-content/30 mb-3" />
<h3 class="text-base font-semibold mb-1">No time entries found</h3>
<p class="text-base-content/50 text-sm mb-4">Try adjusting your filters or select a different time range.</p>
<p class="text-base-content/60 text-sm mb-4">Try adjusting your filters or select a different time range.</p>
<a href="/dashboard/tracker" class="btn btn-primary btn-sm">
<Icon name="play" class="w-4 h-4" />
Start Tracking Time

View File

@@ -77,11 +77,11 @@ const userPasskeys = await db.select()
</h2>
<div class="space-y-3">
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-200 gap-2 sm:gap-0">
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-content/20 gap-2 sm:gap-0">
<span class="text-base-content/60 text-sm">Account ID</span>
<span class="font-mono text-xs break-all">{user.id}</span>
</div>
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-200 gap-2 sm:gap-0">
<div class="flex flex-col sm:flex-row sm:justify-between py-3 border-b border-base-content/20 gap-2 sm:gap-0">
<span class="text-base-content/60 text-sm">Email</span>
<span class="text-sm break-all">{user.email}</span>
</div>

View File

@@ -82,7 +82,7 @@ const isAdmin = currentUserMember?.member.role === 'owner' || currentUserMember?
{member.role}
</span>
</td>
<td class="text-base-content/40">{member.joinedAt?.toLocaleDateString() ?? 'N/A'}</td>
<td class="text-base-content/60">{member.joinedAt?.toLocaleDateString() ?? 'N/A'}</td>
{isAdmin && (
<td>
{teamUser.id !== user.id && member.role !== 'owner' && (
@@ -90,7 +90,7 @@ const isAdmin = currentUserMember?.member.role === 'owner' || currentUserMember?
<div role="button" tabindex="0" class="btn btn-ghost btn-xs btn-square">
<Icon name="ellipsis-vertical" class="w-4 h-4" />
</div>
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-200">
<ul tabindex="0" class="dropdown-content z-1 menu p-2 bg-base-100 rounded-box w-52 border border-base-content/20">
<li>
<form method="POST" action={`/api/team/change-role`}>
<input type="hidden" name="userId" value={teamUser.id} />

View File

@@ -56,7 +56,7 @@ if (!isAdmin) return Astro.redirect('/dashboard/team');
<option value="member">Member</option>
<option value="admin">Admin</option>
</select>
<p class="text-xs text-base-content/40 mt-1">Members can track time. Admins can manage team and clients.</p>
<p class="text-xs text-base-content/60 mt-1">Members can track time. Admins can manage team and clients.</p>
</fieldset>
<div class="flex justify-end gap-2 mt-4">

View File

@@ -73,7 +73,7 @@ const successType = url.searchParams.get('success');
<legend class="fieldset-legend text-xs">Team Logo</legend>
<div class="flex items-center gap-4">
<div class="avatar placeholder">
<div class="bg-base-200 text-neutral-content rounded-xl w-20 border border-base-200 flex items-center justify-center overflow-hidden">
<div class="bg-base-200 text-neutral-content rounded-xl w-20 border border-base-content/20 flex items-center justify-center overflow-hidden">
{organization.logoUrl ? (
<img
src={organization.logoUrl}
@@ -83,7 +83,7 @@ const successType = url.searchParams.get('success');
) : (
<Icon
name="photo"
class="w-6 h-6 opacity-40 text-base-content"
class="w-6 h-6 opacity-70 text-base-content"
/>
)}
</div>
@@ -95,7 +95,7 @@ const successType = url.searchParams.get('success');
accept="image/png, image/jpeg"
class="file-input file-input-bordered file-input-sm w-full max-w-xs"
/>
<div class="text-xs text-base-content/40 mt-1">
<div class="text-xs text-base-content/60 mt-1">
Upload a company logo (PNG, JPG). Will be displayed on invoices and quotes.
</div>
</div>
@@ -113,10 +113,10 @@ const successType = url.searchParams.get('success');
class="input w-full"
required
/>
<p class="text-xs text-base-content/40 mt-1">This name is visible to all team members</p>
<p class="text-xs text-base-content/60 mt-1">This name is visible to all team members</p>
</fieldset>
<div class="divider text-xs text-base-content/40 my-2">Address Information</div>
<div class="divider text-xs text-base-content/60 my-2">Address Information</div>
<fieldset class="fieldset">
<legend class="fieldset-legend text-xs">Street Address</legend>
@@ -182,7 +182,7 @@ const successType = url.searchParams.get('success');
</fieldset>
</div>
<div class="divider text-xs text-base-content/40 my-2">Defaults</div>
<div class="divider text-xs text-base-content/60 my-2">Defaults</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<fieldset class="fieldset">
@@ -216,7 +216,7 @@ const successType = url.searchParams.get('success');
</div>
<div class="flex flex-col sm:flex-row justify-between items-center gap-3 mt-4">
<span class="text-xs text-base-content/40 text-center sm:text-left">
<span class="text-xs text-base-content/60 text-center sm:text-left">
Address information appears on invoices and quotes
</span>
@@ -280,7 +280,7 @@ const successType = url.searchParams.get('success');
{tag.rate ? (
<span class="font-mono text-sm">{new Intl.NumberFormat('en-US', { style: 'currency', currency: organization.defaultCurrency || 'USD' }).format(tag.rate / 100)}</span>
) : (
<span class="text-base-content/40 text-xs italic">No rate</span>
<span class="text-base-content/60 text-xs italic">No rate</span>
)}
</td>
<td>
@@ -315,7 +315,7 @@ const successType = url.searchParams.get('success');
<fieldset class="fieldset mb-4">
<legend class="fieldset-legend text-xs">Hourly Rate (cents)</legend>
<input type="number" name="rate" value={tag.rate || 0} min="0" class="input w-full" />
<p class="text-xs text-base-content/40 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
<p class="text-xs text-base-content/60 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
</fieldset>
<div class="modal-action">
<ModalButton client:load modalId={`edit_tag_modal_${tag.id}`} action="close" class="btn btn-sm">Cancel</ModalButton>
@@ -353,7 +353,7 @@ const successType = url.searchParams.get('success');
<fieldset class="fieldset mb-4">
<legend class="fieldset-legend text-xs">Hourly Rate (cents)</legend>
<input type="number" name="rate" value="0" min="0" class="input w-full" />
<p class="text-xs text-base-content/40 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
<p class="text-xs text-base-content/60 mt-1">Enter rate in cents (e.g. 5000 = $50.00)</p>
</fieldset>
<div class="modal-action">
<ModalButton client:load modalId="new_tag_modal" action="close" class="btn btn-sm">Cancel</ModalButton>

View File

@@ -144,9 +144,9 @@ const paginationPages = getPaginationPages(page, totalPages);
<h1 class="text-2xl font-extrabold tracking-tight mb-6">Time Tracker</h1>
<!-- Tabs for Timer and Manual Entry -->
<div class="tabs tabs-lift mb-6">
<div class="tabs tabs-border mb-6">
<input type="radio" name="tracker_tabs" class="tab" aria-label="Timer" checked="checked" />
<div class="tab-content bg-base-100 border-base-300 p-6">
<div class="tab-content border-base-content/20 p-6">
{allClients.length === 0 ? (
<div class="alert alert-warning flex flex-col sm:flex-row items-center gap-4">
<Icon name="exclamation-triangle" class="stroke-current shrink-0 h-6 w-6" />
@@ -169,7 +169,7 @@ const paginationPages = getPaginationPages(page, totalPages);
</div>
<input type="radio" name="tracker_tabs" class="tab" aria-label="Manual Entry" />
<div class="tab-content bg-base-100 border-base-300 p-6">
<div class="tab-content border-base-content/20 p-6">
{allClients.length === 0 ? (
<div class="alert alert-warning flex flex-col sm:flex-row items-center gap-4">
<Icon name="exclamation-triangle" class="stroke-current shrink-0 h-6 w-6" />
@@ -314,7 +314,7 @@ const paginationPages = getPaginationPages(page, totalPages);
<td>{entryUser?.name || 'Unknown'}</td>
<td class="whitespace-nowrap">
{entry.startTime.toLocaleDateString()}<br/>
<span class="text-xs text-base-content/40">
<span class="text-xs text-base-content/60">
{entry.startTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</span>
</td>
@@ -322,7 +322,7 @@ const paginationPages = getPaginationPages(page, totalPages);
{entry.endTime ? (
<>
{entry.endTime.toLocaleDateString()}<br/>
<span class="text-xs text-base-content/40">
<span class="text-xs text-base-content/60">
{entry.endTime.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</span>
</>

View File

@@ -31,11 +31,11 @@ if (Astro.locals.user) {
</div>
<!-- Features -->
<div class="bg-base-200/50 border-t border-base-200 px-4 py-16 sm:py-20">
<div class="bg-base-200 border-t border-base-content/20 px-4 py-16 sm:py-20">
<div class="max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="card bg-base-100 card-border">
<div class="card-body">
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-2">
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
<Icon name="bolt" class="h-5 w-5 text-primary" />
</div>
<h3 class="card-title text-base">Lightning Fast</h3>
@@ -44,7 +44,7 @@ if (Astro.locals.user) {
</div>
<div class="card bg-base-100 card-border">
<div class="card-body">
<div class="w-10 h-10 rounded-lg bg-info/10 flex items-center justify-center mb-2">
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
<Icon name="chart-bar" class="h-5 w-5 text-info" />
</div>
<h3 class="card-title text-base">Detailed Reports</h3>
@@ -53,7 +53,7 @@ if (Astro.locals.user) {
</div>
<div class="card bg-base-100 card-border">
<div class="card-body">
<div class="w-10 h-10 rounded-lg bg-accent/10 flex items-center justify-center mb-2">
<div class="w-10 h-10 rounded-lg bg-base-200 flex items-center justify-center mb-2">
<Icon name="users" class="h-5 w-5 text-accent" />
</div>
<h3 class="card-title text-base">Team Collaboration</h3>

View File

@@ -18,7 +18,7 @@ const errorMessage =
<Layout title="Login - Chronus">
<div class="flex justify-center items-center flex-1 bg-base-100">
<div class="card card-border bg-base-100 w-full max-w-sm mx-4">
<div class="card card-border bg-base-200 w-full max-w-sm mx-4">
<div class="card-body gap-0">
<img src="/logo.webp" alt="Chronus" class="h-14 w-14 mx-auto mb-3" />
<h2 class="text-2xl font-extrabold tracking-tight text-center">Welcome Back</h2>

View File

@@ -34,7 +34,7 @@ const errorMessage =
<Layout title="Sign Up - Chronus">
<div class="flex justify-center items-center flex-1 bg-base-100">
<div class="card card-border bg-base-100 w-full max-w-sm mx-4">
<div class="card card-border bg-base-200 w-full max-w-sm mx-4">
<div class="card-body gap-0">
<img src="/logo.webp" alt="Chronus" class="h-14 w-14 mx-auto mb-3" />
<h2 class="text-2xl font-extrabold tracking-tight text-center">Create Account</h2>

View File

@@ -52,8 +52,6 @@ export const GET: APIRoute = async ({ params }) => {
case ".gif":
contentType = "image/gif";
break;
// SVG excluded to prevent stored XSS
// WebP omitted — not supported in PDF generation
}
return new Response(fileContent, {

View File

@@ -305,7 +305,6 @@ export function createInvoiceDocument(props: InvoiceDocumentProps) {
if (organization.logoUrl) {
try {
let logoPath;
// Handle uploads directory which might be external to public/
if (organization.logoUrl.startsWith("/uploads/")) {
const dataDir = process.env.DATA_DIR
? process.env.DATA_DIR

View File

@@ -1,9 +1,7 @@
@import "tailwindcss";
@plugin "daisyui" {
themes: false;
themes: sunset --default, winter;
}
@plugin "./theme-dark.ts";
@plugin "./theme-light.ts";
/* Smoother transitions globally */
@layer base {
@@ -15,3 +13,8 @@
transition: none;
}
}
/* Override DaisyUI card-border to use a visible border in both themes */
.card-border {
border-color: color-mix(in oklab, var(--color-base-content) 20%, transparent);
}

View File

@@ -1,9 +0,0 @@
import { createCatppuccinPlugin } from "@catppuccin/daisyui";
export default createCatppuccinPlugin(
"macchiato",
{},
{
default: true,
},
);

View File

@@ -1,9 +0,0 @@
import { createCatppuccinPlugin } from "@catppuccin/daisyui";
export default createCatppuccinPlugin(
"latte",
{},
{
default: false,
},
);

View File

@@ -34,7 +34,6 @@ export async function recalculateInvoiceTotals(invoiceId: string) {
if (discountType === "percentage") {
discountAmount = Math.round(subtotal * (discountValue / 100));
} else {
// Fixed amount is assumed to be in cents
discountAmount = Math.round(discountValue);
}