feat: remove start.sh script and add appearance preferences management

- Deleted the start.sh script for container management.
- Added AGENTS.md for project guidelines and development principles.
- Introduced new SQL migration files for user appearance preferences and platform settings.
- Implemented appearance provider to manage user interface themes and preferences.
- Created branding utility to define and manage branding-related constants and types.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-27 22:12:16 -04:00
parent b582b6c88e
commit fbeca7cfee
39 changed files with 3388 additions and 977 deletions
@@ -30,15 +30,15 @@ export function InvoiceDetailsSkeleton() {
<Skeleton className="h-8 w-48" />
<Skeleton className="h-6 w-24 rounded-full" />
</div>
<div className="space-y-1 sm:space-y-0 text-sm">
<div className="space-y-1 text-sm sm:space-y-0">
<div className="flex gap-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-32 hidden sm:block" />
<Skeleton className="hidden h-4 w-32 sm:block" />
</div>
</div>
</div>
<div className="flex-shrink-0 text-left sm:text-right">
<Skeleton className="h-4 w-24 mb-1 sm:ml-auto" />
<Skeleton className="mb-1 h-4 w-24 sm:ml-auto" />
<Skeleton className="h-9 w-32 sm:ml-auto" />
</div>
</div>
@@ -118,7 +118,7 @@ export function InvoiceDetailsSkeleton() {
<div className="space-y-3">
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0 flex-1">
<Skeleton className="h-5 w-3/4 mb-2" />
<Skeleton className="mb-2 h-5 w-3/4" />
<div className="flex gap-4">
<Skeleton className="h-4 w-24" />
<Skeleton className="h-4 w-16" />
@@ -156,7 +156,7 @@ export function InvoiceDetailsSkeleton() {
{/* Right Column - Actions */}
<div className="space-y-6">
<Card className="sticky top-20">
<Card className="lg:sticky lg:top-6">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Skeleton className="h-5 w-5 rounded-full" />
@@ -25,6 +25,9 @@ export function PDFDownloadButton({
{ id: invoiceId },
{ enabled: false },
);
const { data: platformTheme } = api.settings.getTheme.useQuery(undefined, {
staleTime: 60_000,
});
const handleDownloadPDF = async () => {
if (isGenerating) return;
@@ -55,7 +58,13 @@ export function PDFDownloadButton({
items: invoiceData.items,
};
await generateInvoicePDF(pdfData);
await generateInvoicePDF(pdfData, {
pdfTemplate: platformTheme?.pdfTemplate,
pdfAccentColor: platformTheme?.pdfAccentColor,
pdfFooterText: platformTheme?.pdfFooterText,
pdfShowLogo: platformTheme?.pdfShowLogo,
pdfShowPageNumbers: platformTheme?.pdfShowPageNumbers,
});
toast.success("PDF downloaded successfully");
} catch (error) {
console.error("PDF generation error:", error);
+1 -1
View File
@@ -411,7 +411,7 @@ function InvoiceViewContent({ invoiceId }: { invoiceId: string }) {
{/* Right Column - Actions */}
<div className="space-y-6">
<Card className="sticky top-20">
<Card className="lg:sticky lg:top-6">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Check className="h-5 w-5" />
+8 -3
View File
@@ -25,6 +25,11 @@ import {
} from "recharts";
import { TrendingUp, DollarSign, Clock, Users, Download, Receipt, FileText } from "lucide-react";
function toNumericChartValue(value: unknown) {
const numericValue = typeof value === "number" ? value : Number(value ?? 0);
return Number.isFinite(numericValue) ? numericValue : 0;
}
export default function ReportsPage() {
const { data: invoices = [], isLoading: invoicesLoading } = api.invoices.getAll.useQuery();
const { data: expenses = [], isLoading: expensesLoading } = api.expenses.getAll.useQuery();
@@ -259,7 +264,7 @@ export default function ReportsPage() {
<CartesianGrid strokeDasharray="3 3" className="stroke-border" />
<XAxis dataKey="month" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} />
<YAxis tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} tickFormatter={(v: number) => `$${v >= 1000 ? `${(v / 1000).toFixed(0)}k` : v}`} />
<Tooltip formatter={(v: number) => [formatCurrency(v), "Revenue"]} contentStyle={{ background: "hsl(var(--card))", border: "1px solid hsl(var(--border))", borderRadius: "8px", fontSize: 12 }} />
<Tooltip formatter={(value) => [formatCurrency(toNumericChartValue(value)), "Revenue"]} contentStyle={{ background: "hsl(var(--card))", border: "1px solid hsl(var(--border))", borderRadius: "8px", fontSize: 12 }} />
<Area type="monotone" dataKey="revenue" stroke="hsl(142, 76%, 36%)" fill="url(#revenueGrad)" strokeWidth={2} dot={false} />
</AreaChart>
</ResponsiveContainer>
@@ -281,7 +286,7 @@ export default function ReportsPage() {
<BarChart data={overviewData.topClients} layout="vertical">
<XAxis type="number" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} tickFormatter={(v: number) => `$${v >= 1000 ? `${(v / 1000).toFixed(0)}k` : v}`} />
<YAxis type="category" dataKey="name" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} width={80} />
<Tooltip formatter={(v: number) => [formatCurrency(v), "Revenue"]} contentStyle={{ background: "hsl(var(--card))", border: "1px solid hsl(var(--border))", borderRadius: "8px", fontSize: 12 }} />
<Tooltip formatter={(value) => [formatCurrency(toNumericChartValue(value)), "Revenue"]} contentStyle={{ background: "hsl(var(--card))", border: "1px solid hsl(var(--border))", borderRadius: "8px", fontSize: 12 }} />
<Bar dataKey="revenue" fill="hsl(142, 76%, 36%)" radius={[0, 4, 4, 0]} />
</BarChart>
</ResponsiveContainer>
@@ -433,7 +438,7 @@ export default function ReportsPage() {
<XAxis dataKey="label" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} />
<YAxis tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} axisLine={false} tickLine={false} tickFormatter={(v: number) => `$${v >= 1000 ? `${(v / 1000).toFixed(0)}k` : v}`} />
<Tooltip
formatter={(v: number, name: string) => [formatCurrency(v), name === "income" ? "Income" : "Expenses"]}
formatter={(value, name) => [formatCurrency(toNumericChartValue(value)), name === "income" ? "Income" : "Expenses"]}
contentStyle={{ background: "hsl(var(--card))", border: "1px solid hsl(var(--border))", borderRadius: "8px", fontSize: 12 }}
/>
<Bar dataKey="income" name="income" fill="hsl(142, 76%, 36%)" radius={[4, 4, 0, 0]} />
File diff suppressed because it is too large Load Diff