mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-02-05 00:06:36 -05:00
Add business nickname support across app and API
This commit is contained in:
@@ -6,7 +6,17 @@ import { invoices } from "~/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
const businessSchema = z.object({
|
||||
name: z.string().min(1, "Business name is required"),
|
||||
name: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Business name is required")
|
||||
.max(255, "Business name must be 255 characters or less"),
|
||||
nickname: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(255, "Nickname must be 255 characters or less")
|
||||
.optional()
|
||||
.or(z.literal("")),
|
||||
email: z.string().email().optional().or(z.literal("")),
|
||||
phone: z.string().optional().or(z.literal("")),
|
||||
addressLine1: z.string().optional().or(z.literal("")),
|
||||
@@ -96,7 +106,54 @@ export const businessesRouter = createTRPCRouter({
|
||||
const [newBusiness] = await ctx.db
|
||||
.insert(businesses)
|
||||
.values({
|
||||
...input,
|
||||
name: input.name.trim(),
|
||||
nickname:
|
||||
input.nickname && input.nickname.trim() !== ""
|
||||
? input.nickname.trim()
|
||||
: null,
|
||||
email:
|
||||
input.email && input.email.trim() !== ""
|
||||
? input.email.trim()
|
||||
: null,
|
||||
phone:
|
||||
input.phone && input.phone.trim() !== ""
|
||||
? input.phone.trim()
|
||||
: null,
|
||||
addressLine1:
|
||||
input.addressLine1 && input.addressLine1.trim() !== ""
|
||||
? input.addressLine1.trim()
|
||||
: null,
|
||||
addressLine2:
|
||||
input.addressLine2 && input.addressLine2.trim() !== ""
|
||||
? input.addressLine2.trim()
|
||||
: null,
|
||||
city:
|
||||
input.city && input.city.trim() !== "" ? input.city.trim() : null,
|
||||
state:
|
||||
input.state && input.state.trim() !== ""
|
||||
? input.state.trim()
|
||||
: null,
|
||||
postalCode:
|
||||
input.postalCode && input.postalCode.trim() !== ""
|
||||
? input.postalCode.trim()
|
||||
: null,
|
||||
country:
|
||||
input.country && input.country.trim() !== ""
|
||||
? input.country.trim()
|
||||
: null,
|
||||
website:
|
||||
input.website && input.website.trim() !== ""
|
||||
? input.website.trim()
|
||||
: null,
|
||||
taxId:
|
||||
input.taxId && input.taxId.trim() !== ""
|
||||
? input.taxId.trim()
|
||||
: null,
|
||||
logoUrl:
|
||||
input.logoUrl && input.logoUrl.trim() !== ""
|
||||
? input.logoUrl.trim()
|
||||
: null,
|
||||
isDefault: input.isDefault ?? false,
|
||||
createdById: ctx.session.user.id,
|
||||
})
|
||||
.returning();
|
||||
@@ -126,7 +183,56 @@ export const businessesRouter = createTRPCRouter({
|
||||
const [updatedBusiness] = await ctx.db
|
||||
.update(businesses)
|
||||
.set({
|
||||
...updateData,
|
||||
name: (updateData.name ?? "").trim(),
|
||||
nickname:
|
||||
updateData.nickname && updateData.nickname.trim() !== ""
|
||||
? updateData.nickname.trim()
|
||||
: null,
|
||||
email:
|
||||
updateData.email && updateData.email.trim() !== ""
|
||||
? updateData.email.trim()
|
||||
: null,
|
||||
phone:
|
||||
updateData.phone && updateData.phone.trim() !== ""
|
||||
? updateData.phone.trim()
|
||||
: null,
|
||||
addressLine1:
|
||||
updateData.addressLine1 && updateData.addressLine1.trim() !== ""
|
||||
? updateData.addressLine1.trim()
|
||||
: null,
|
||||
addressLine2:
|
||||
updateData.addressLine2 && updateData.addressLine2.trim() !== ""
|
||||
? updateData.addressLine2.trim()
|
||||
: null,
|
||||
city:
|
||||
updateData.city && updateData.city.trim() !== ""
|
||||
? updateData.city.trim()
|
||||
: null,
|
||||
state:
|
||||
updateData.state && updateData.state.trim() !== ""
|
||||
? updateData.state.trim()
|
||||
: null,
|
||||
postalCode:
|
||||
updateData.postalCode && updateData.postalCode.trim() !== ""
|
||||
? updateData.postalCode.trim()
|
||||
: null,
|
||||
country:
|
||||
updateData.country && updateData.country.trim() !== ""
|
||||
? updateData.country.trim()
|
||||
: null,
|
||||
website:
|
||||
updateData.website && updateData.website.trim() !== ""
|
||||
? updateData.website.trim()
|
||||
: null,
|
||||
taxId:
|
||||
updateData.taxId && updateData.taxId.trim() !== ""
|
||||
? updateData.taxId.trim()
|
||||
: null,
|
||||
logoUrl:
|
||||
updateData.logoUrl && updateData.logoUrl.trim() !== ""
|
||||
? updateData.logoUrl.trim()
|
||||
: null,
|
||||
isDefault: updateData.isDefault ?? false,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(
|
||||
|
||||
@@ -77,7 +77,7 @@ export const emailRouter = createTRPCRouter({
|
||||
// Create email content
|
||||
const subject =
|
||||
input.customSubject ??
|
||||
`Invoice ${invoice.invoiceNumber} from ${invoice.business?.name ?? "Your Business"}`;
|
||||
`Invoice ${invoice.invoiceNumber} from ${invoice.business ? `${invoice.business.name}${invoice.business.nickname ? ` (${invoice.business.nickname})` : ""}` : "Your Business"}`;
|
||||
|
||||
const userName =
|
||||
invoice.business?.emailFromName ??
|
||||
@@ -124,7 +124,11 @@ export const emailRouter = createTRPCRouter({
|
||||
// Use business's custom Resend setup
|
||||
resendInstance = new Resend(invoice.business.resendApiKey);
|
||||
const fromName =
|
||||
invoice.business.emailFromName ?? invoice.business.name ?? userName;
|
||||
invoice.business.emailFromName ??
|
||||
(invoice.business.nickname
|
||||
? `${invoice.business.name} (${invoice.business.nickname})`
|
||||
: invoice.business.name) ??
|
||||
userName;
|
||||
fromEmail = `${fromName} <noreply@${invoice.business.resendDomain}>`;
|
||||
} else if (env.RESEND_DOMAIN) {
|
||||
// Use system Resend configuration
|
||||
|
||||
@@ -25,6 +25,7 @@ const ClientBackupSchema = z.object({
|
||||
|
||||
const BusinessBackupSchema = z.object({
|
||||
name: z.string(),
|
||||
nickname: z.string().optional(),
|
||||
email: z.string().optional(),
|
||||
phone: z.string().optional(),
|
||||
addressLine1: z.string().optional(),
|
||||
@@ -51,6 +52,7 @@ const InvoiceItemBackupSchema = z.object({
|
||||
const InvoiceBackupSchema = z.object({
|
||||
invoiceNumber: z.string(),
|
||||
businessName: z.string().optional(),
|
||||
businessNickname: z.string().optional(),
|
||||
clientName: z.string(),
|
||||
issueDate: z.string().transform((str) => new Date(str)),
|
||||
dueDate: z.string().transform((str) => new Date(str)),
|
||||
@@ -205,6 +207,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
nickname: true,
|
||||
email: true,
|
||||
phone: true,
|
||||
addressLine1: true,
|
||||
@@ -232,6 +235,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
business: {
|
||||
columns: {
|
||||
name: true,
|
||||
nickname: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
@@ -269,6 +273,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
})),
|
||||
businesses: userBusinesses.map((business) => ({
|
||||
name: business.name,
|
||||
nickname: business.nickname ?? undefined,
|
||||
email: business.email ?? undefined,
|
||||
phone: business.phone ?? undefined,
|
||||
addressLine1: business.addressLine1 ?? undefined,
|
||||
@@ -285,6 +290,7 @@ export const settingsRouter = createTRPCRouter({
|
||||
invoices: userInvoices.map((invoice) => ({
|
||||
invoiceNumber: invoice.invoiceNumber,
|
||||
businessName: invoice.business?.name,
|
||||
businessNickname: invoice.business?.nickname,
|
||||
clientName: invoice.client.name,
|
||||
issueDate: invoice.issueDate,
|
||||
dueDate: invoice.dueDate,
|
||||
@@ -337,6 +343,9 @@ export const settingsRouter = createTRPCRouter({
|
||||
|
||||
if (newBusiness) {
|
||||
businessIdMap.set(businessData.name, newBusiness.id);
|
||||
if (businessData.nickname) {
|
||||
businessIdMap.set(businessData.nickname, newBusiness.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,9 +356,14 @@ export const settingsRouter = createTRPCRouter({
|
||||
throw new Error(`Client ${invoiceData.clientName} not found`);
|
||||
}
|
||||
|
||||
const businessId = invoiceData.businessName
|
||||
? businessIdMap.get(invoiceData.businessName)
|
||||
: null;
|
||||
const businessId = invoiceData.businessNickname
|
||||
? (businessIdMap.get(invoiceData.businessNickname) ??
|
||||
(invoiceData.businessName
|
||||
? (businessIdMap.get(invoiceData.businessName) ?? null)
|
||||
: null))
|
||||
: invoiceData.businessName
|
||||
? (businessIdMap.get(invoiceData.businessName) ?? null)
|
||||
: null;
|
||||
|
||||
const [newInvoice] = await tx
|
||||
.insert(invoices)
|
||||
|
||||
@@ -143,6 +143,7 @@ export const businesses = createTable(
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: d.varchar({ length: 255 }).notNull(),
|
||||
nickname: d.varchar({ length: 255 }),
|
||||
email: d.varchar({ length: 255 }),
|
||||
phone: d.varchar({ length: 50 }),
|
||||
addressLine1: d.varchar({ length: 255 }),
|
||||
@@ -172,6 +173,7 @@ export const businesses = createTable(
|
||||
(t) => [
|
||||
index("business_created_by_idx").on(t.createdById),
|
||||
index("business_name_idx").on(t.name),
|
||||
index("business_nickname_idx").on(t.nickname),
|
||||
index("business_email_idx").on(t.email),
|
||||
index("business_is_default_idx").on(t.isDefault),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user