Fix Docker signup, refresh docs, and improve blank invoice flow.

Parse DISABLE_SIGNUPS and related env booleans correctly for Compose string values, and derive auth trustedOrigins from BETTER_AUTH_URL. Rewrite README and architecture docs with the git.soconnor.dev remote and accurate deployment guidance. Allow zero-line-item draft invoices with validation when sending email.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-25 23:45:57 -04:00
co-authored by Cursor
parent 480c50981d
commit 463b1f503e
12 changed files with 323 additions and 394 deletions
+36 -22
View File
@@ -2,7 +2,7 @@
import * as React from "react";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { Label } from "~/components/ui/label";
@@ -95,7 +95,7 @@ function plainTextToHtml(value: string) {
.replace(/\n/g, "<br>");
}
function createDefaultInvoiceFormData(): InvoiceFormData {
function createDefaultInvoiceFormData(blank = false): InvoiceFormData {
return {
invoiceNumber: `INV-${new Date().toISOString().slice(0, 10).replace(/-/g, "")}-${Date.now().toString().slice(-6)}`,
invoicePrefix: "#",
@@ -109,26 +109,30 @@ function createDefaultInvoiceFormData(): InvoiceFormData {
taxRate: 0,
currency: "USD",
defaultHourlyRate: null,
items: [
{
id: crypto.randomUUID(),
date: new Date(),
description: "",
hours: 1,
rate: 0,
amount: 0,
},
],
items: blank
? []
: [
{
id: crypto.randomUUID(),
date: new Date(),
description: "",
hours: 1,
rate: 0,
amount: 0,
},
],
};
}
export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
const router = useRouter();
const searchParams = useSearchParams();
const isBlank = searchParams.get("blank") === "1";
const utils = api.useUtils();
// State
const [formData, setFormData] = useState<InvoiceFormData>(
createDefaultInvoiceFormData,
const [formData, setFormData] = useState<InvoiceFormData>(() =>
createDefaultInvoiceFormData(isBlank),
);
const [loading, setLoading] = useState(false);
@@ -368,13 +372,13 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
return;
}
// Validate Items - Check for empty description
const itemsToSave = formData.items.filter((item) => item.description?.trim());
let invalidItemIndex = -1;
for (let i = 0; i < formData.items.length; i++) {
if (
!formData.items[i]?.description ||
formData.items[i]?.description.trim() === ""
) {
const item = formData.items[i];
const desc = item?.description?.trim() ?? "";
if (!desc && ((item?.hours ?? 0) > 0 || (item?.rate ?? 0) > 0)) {
invalidItemIndex = i;
break;
}
@@ -421,7 +425,7 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
emailMessage: formData.emailMessage,
taxRate: formData.taxRate,
currency: formData.currency,
items: formData.items.map((i) => ({
items: itemsToSave.map((i) => ({
date: i.date,
description: i.description,
hours: i.hours,
@@ -460,8 +464,18 @@ export default function InvoiceForm({ invoiceId }: InvoiceFormProps) {
<>
<div className="page-enter space-y-6 pb-8">
<PageHeader
title={invoiceId !== "new" ? "Edit Invoice" : "Create Invoice"}
description="Manage your invoice"
title={
invoiceId !== "new"
? "Edit Invoice"
: isBlank
? "Blank Invoice"
: "Create Invoice"
}
description={
isBlank
? "Set up a draft to clock time into later"
: "Manage your invoice"
}
variant="gradient"
>
{invoiceId !== "new" && (