feat: add AI Dental Shopping section with sidebar nav and Login Info page

- Add AI Dental Shopping to sidebar with Search/Tag and Login Info sub-pages
- Build full-stack Login Info CRUD: save vendor name, website, username, password per user
- Add ShoppingVendor Prisma model, run db push, regenerate client and Zod schemas
- Add storage layer, REST API at /api/shopping-vendors/, and frontend table with add/edit/delete modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-17 00:35:38 -04:00
parent edec03e893
commit e34140c2b1
217 changed files with 4081 additions and 14 deletions

View File

@@ -0,0 +1,34 @@
import * as z from 'zod';
export const ShoppingVendorAggregateResultSchema = z.object({ _count: z.object({
id: z.number(),
userId: z.number(),
vendorName: z.number(),
websiteUrl: z.number(),
loginUsername: z.number(),
loginPassword: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
vendorName: z.string().nullable(),
websiteUrl: z.string().nullable(),
loginUsername: z.string().nullable(),
loginPassword: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
vendorName: z.string().nullable(),
websiteUrl: z.string().nullable(),
loginUsername: z.string().nullable(),
loginPassword: z.string().nullable()
}).nullable().optional()});

View File

@@ -0,0 +1,2 @@
import * as z from 'zod';
export const ShoppingVendorCountResultSchema = z.number();

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const ShoppingVendorCreateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorCreateResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
});

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const ShoppingVendorDeleteManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorDeleteResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorFindFirstResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,20 @@
import * as z from 'zod';
export const ShoppingVendorFindManyResultSchema = z.object({
data: z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
})),
pagination: z.object({
page: z.number().int().min(1),
pageSize: z.number().int().min(1),
total: z.number().int().min(0),
totalPages: z.number().int().min(0),
hasNext: z.boolean(),
hasPrev: z.boolean()
})
});

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorFindUniqueResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,42 @@
import * as z from 'zod';
export const ShoppingVendorGroupByResultSchema = z.array(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
_count: z.object({
id: z.number(),
userId: z.number(),
vendorName: z.number(),
websiteUrl: z.number(),
loginUsername: z.number(),
loginPassword: z.number(),
user: z.number()
}).optional(),
_sum: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_avg: z.object({
id: z.number().nullable(),
userId: z.number().nullable()
}).nullable().optional(),
_min: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
vendorName: z.string().nullable(),
websiteUrl: z.string().nullable(),
loginUsername: z.string().nullable(),
loginPassword: z.string().nullable()
}).nullable().optional(),
_max: z.object({
id: z.number().int().nullable(),
userId: z.number().int().nullable(),
vendorName: z.string().nullable(),
websiteUrl: z.string().nullable(),
loginUsername: z.string().nullable(),
loginPassword: z.string().nullable()
}).nullable().optional()
}));

View File

@@ -0,0 +1,4 @@
import * as z from 'zod';
export const ShoppingVendorUpdateManyResultSchema = z.object({
count: z.number()
});

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorUpdateResultSchema = z.nullable(z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
}));

View File

@@ -0,0 +1,10 @@
import * as z from 'zod';
export const ShoppingVendorUpsertResultSchema = z.object({
id: z.number().int(),
userId: z.number().int(),
vendorName: z.string(),
websiteUrl: z.string(),
loginUsername: z.string(),
loginPassword: z.string(),
user: z.unknown()
});

View File

@@ -11,6 +11,7 @@ export const UserAggregateResultSchema = z.object({ _count: z.object({
npiProviders: z.number(),
claims: z.number(),
insuranceCredentials: z.number(),
shoppingVendors: z.number(),
updatedPayments: z.number(),
backups: z.number(),
backupDestinations: z.number(),

View File

@@ -11,6 +11,7 @@ export const UserCreateResultSchema = z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -11,6 +11,7 @@ export const UserDeleteResultSchema = z.nullable(z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -11,6 +11,7 @@ export const UserFindFirstResultSchema = z.nullable(z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -12,6 +12,7 @@ export const UserFindManyResultSchema = z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -11,6 +11,7 @@ export const UserFindUniqueResultSchema = z.nullable(z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -17,6 +17,7 @@ export const UserGroupByResultSchema = z.array(z.object({
npiProviders: z.number(),
claims: z.number(),
insuranceCredentials: z.number(),
shoppingVendors: z.number(),
updatedPayments: z.number(),
backups: z.number(),
backupDestinations: z.number(),

View File

@@ -11,6 +11,7 @@ export const UserUpdateResultSchema = z.nullable(z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -11,6 +11,7 @@ export const UserUpsertResultSchema = z.object({
npiProviders: z.array(z.unknown()),
claims: z.array(z.unknown()),
insuranceCredentials: z.array(z.unknown()),
shoppingVendors: z.array(z.unknown()),
updatedPayments: z.array(z.unknown()),
backups: z.array(z.unknown()),
backupDestinations: z.array(z.unknown()),

View File

@@ -141,6 +141,19 @@ export { InsuranceCredentialDeleteManyResultSchema } from './InsuranceCredential
export { InsuranceCredentialAggregateResultSchema } from './InsuranceCredentialAggregateResult.schema';
export { InsuranceCredentialGroupByResultSchema } from './InsuranceCredentialGroupByResult.schema';
export { InsuranceCredentialCountResultSchema } from './InsuranceCredentialCountResult.schema';
export { ShoppingVendorFindUniqueResultSchema } from './ShoppingVendorFindUniqueResult.schema';
export { ShoppingVendorFindFirstResultSchema } from './ShoppingVendorFindFirstResult.schema';
export { ShoppingVendorFindManyResultSchema } from './ShoppingVendorFindManyResult.schema';
export { ShoppingVendorCreateResultSchema } from './ShoppingVendorCreateResult.schema';
export { ShoppingVendorCreateManyResultSchema } from './ShoppingVendorCreateManyResult.schema';
export { ShoppingVendorUpdateResultSchema } from './ShoppingVendorUpdateResult.schema';
export { ShoppingVendorUpdateManyResultSchema } from './ShoppingVendorUpdateManyResult.schema';
export { ShoppingVendorUpsertResultSchema } from './ShoppingVendorUpsertResult.schema';
export { ShoppingVendorDeleteResultSchema } from './ShoppingVendorDeleteResult.schema';
export { ShoppingVendorDeleteManyResultSchema } from './ShoppingVendorDeleteManyResult.schema';
export { ShoppingVendorAggregateResultSchema } from './ShoppingVendorAggregateResult.schema';
export { ShoppingVendorGroupByResultSchema } from './ShoppingVendorGroupByResult.schema';
export { ShoppingVendorCountResultSchema } from './ShoppingVendorCountResult.schema';
export { PdfGroupFindUniqueResultSchema } from './PdfGroupFindUniqueResult.schema';
export { PdfGroupFindFirstResultSchema } from './PdfGroupFindFirstResult.schema';
export { PdfGroupFindManyResultSchema } from './PdfGroupFindManyResult.schema';