@nuxt-customer-portal/clients 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/app/components/ClientsClientDetail.vue +407 -0
  5. package/app/components/ClientsClientForm.vue +135 -0
  6. package/app/components/ClientsClientPicker.vue +24 -0
  7. package/app/composables/useClients.ts +25 -0
  8. package/app/pages/clients/[id].vue +47 -0
  9. package/app/pages/clients/index.vue +265 -0
  10. package/app/plugins/clients-feature.ts +3 -0
  11. package/i18n/locales/en.json +74 -0
  12. package/i18n/locales/nl.json +74 -0
  13. package/migrations/0000_baseline.sql +33 -0
  14. package/nuxt.config.ts +17 -0
  15. package/package.json +55 -0
  16. package/portal.manifest.mjs +8 -0
  17. package/server/api/clients/[id]/archive/index.patch.ts +11 -0
  18. package/server/api/clients/[id]/deletion/index.get.ts +7 -0
  19. package/server/api/clients/[id]/index.delete.ts +10 -0
  20. package/server/api/clients/[id]/index.get.ts +12 -0
  21. package/server/api/clients/[id]/index.patch.ts +10 -0
  22. package/server/api/clients/[id]/invitations/[invitationId].patch.ts +8 -0
  23. package/server/api/clients/[id]/invitations/index.post.ts +66 -0
  24. package/server/api/clients/[id]/members/[memberId].delete.ts +29 -0
  25. package/server/api/clients/[id]/members/[memberId].patch.ts +20 -0
  26. package/server/api/clients/[id]/modules/[moduleId].put.ts +12 -0
  27. package/server/api/clients/index.get.ts +8 -0
  28. package/server/api/clients/index.post.ts +10 -0
  29. package/server/api/clients/selectable.get.ts +11 -0
  30. package/server/db/schema/clients.ts +55 -0
  31. package/server/utils/client-access.ts +23 -0
  32. package/server/utils/client-email-locale.ts +18 -0
  33. package/server/utils/client-repository.ts +390 -0
  34. package/server/utils/client-validation.ts +57 -0
  35. package/shared/feature.ts +50 -0
  36. package/shared/types/client.ts +51 -0
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod'
2
+
3
+ const id = z.string().min(1).max(128)
4
+ const slug = z
5
+ .string()
6
+ .trim()
7
+ .min(1)
8
+ .max(160)
9
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)
10
+
11
+ export const genericClientListQuerySchema = z.object({
12
+ search: z.string().trim().max(200).optional(),
13
+ status: z.enum(['all', 'active', 'archived']).optional(),
14
+ moduleId: z.string().trim().max(100).optional(),
15
+ page: z.coerce.number().int().min(1).default(1),
16
+ pageSize: z.coerce.number().int().min(1).max(100).default(20),
17
+ sortBy: z.enum(['name', 'createdAt', 'status']).default('name'),
18
+ sortDir: z.enum(['asc', 'desc']).default('asc')
19
+ })
20
+
21
+ export const genericClientCreateSchema = z.object({
22
+ name: z.string().trim().min(2).max(160),
23
+ slug,
24
+ officialName: z.string().trim().min(2).max(200),
25
+ address: z.string().trim().max(1000).default(''),
26
+ registrationNumber: z.string().trim().max(200).nullable().optional(),
27
+ vatNumber: z.string().trim().max(100).nullable().optional(),
28
+ invoiceEmail: z.string().trim().toLowerCase().email().max(320).nullable().optional(),
29
+ preferredLocale: z.enum(['nl', 'en']).default('nl'),
30
+ moduleIds: z.array(z.string().trim().min(1).max(100)).max(50).optional()
31
+ })
32
+
33
+ export const clientUpdateSchema = genericClientCreateSchema
34
+ .omit({ slug: true, moduleIds: true })
35
+ .partial()
36
+ .refine((value) => Object.keys(value).length > 0, 'At least one field is required')
37
+ export const clientArchiveSchema = z.object({ archived: z.boolean() })
38
+ export const clientModuleUpdateSchema = z.object({ enabled: z.boolean() })
39
+ export const genericClientDeleteSchema = z.object({ clientName: z.string().trim().min(1).max(200) })
40
+ export const clientIdSchema = id
41
+
42
+ export type GenericClientListQuery = z.infer<typeof genericClientListQuerySchema>
43
+ export type ClientCreateInput = z.infer<typeof genericClientCreateSchema>
44
+ export type ClientUpdateInput = z.infer<typeof clientUpdateSchema>
45
+
46
+ export const genericClientInvitationSchema = z.object({
47
+ email: z.email(),
48
+ role: z.enum(['owner', 'admin', 'member']).default('member')
49
+ })
50
+
51
+ export const genericClientMemberUpdateSchema = z
52
+ .object({
53
+ role: z.enum(['owner', 'admin', 'member']).optional(),
54
+ phone: z.string().trim().max(50).nullable().optional(),
55
+ jobTitle: z.string().trim().max(120).nullable().optional()
56
+ })
57
+ .refine((value) => Object.keys(value).length > 0)
@@ -0,0 +1,50 @@
1
+ import type { PortalFeatureDefinition } from '@nuxt-customer-portal/core/shared/types/feature'
2
+
3
+ export const clientActions = [
4
+ 'read',
5
+ 'create',
6
+ 'update',
7
+ 'archive',
8
+ 'delete',
9
+ 'manageMembers',
10
+ 'manageModules'
11
+ ] as const
12
+ export type ClientAction = (typeof clientActions)[number]
13
+
14
+ export const clientsFeature: PortalFeatureDefinition<ClientAction> = {
15
+ id: 'clients',
16
+ navigation: [
17
+ {
18
+ id: 'clients',
19
+ labelKey: 'features.clients.title',
20
+ icon: 'i-lucide-building-2',
21
+ to: '/clients',
22
+ audiences: ['providerAdmin'],
23
+ order: 20
24
+ }
25
+ ],
26
+ modules: [
27
+ {
28
+ id: 'clients',
29
+ labelKey: 'features.clients.title',
30
+ icon: 'i-lucide-building-2',
31
+ to: '/clients',
32
+ routePrefixes: ['/clients'],
33
+ audiences: ['providerAdmin'],
34
+ order: 20,
35
+ menuItems: [
36
+ {
37
+ id: 'clients-list',
38
+ labelKey: 'features.clients.title',
39
+ icon: 'i-lucide-building-2',
40
+ to: '/clients',
41
+ audiences: ['providerAdmin']
42
+ }
43
+ ]
44
+ }
45
+ ],
46
+ policy: {
47
+ PROVIDER: { owner: clientActions, admin: clientActions.filter((action) => action !== 'delete'), member: [] },
48
+ CLIENT: { owner: ['read', 'update', 'manageMembers'], admin: ['read', 'update', 'manageMembers'], member: ['read'] }
49
+ }
50
+ }
@@ -0,0 +1,51 @@
1
+ export type ClientModuleState = { moduleId: string; enabled: boolean }
2
+
3
+ export interface ClientMemberDto {
4
+ id: string
5
+ userId: string
6
+ name: string
7
+ email: string
8
+ image: string | null
9
+ role: string
10
+ phone: string | null
11
+ jobTitle: string | null
12
+ }
13
+
14
+ export interface ClientInvitationDto {
15
+ id: string
16
+ email: string
17
+ role: string
18
+ status: string
19
+ expiresAt: string
20
+ }
21
+
22
+ export interface GenericClientDto {
23
+ avatarLogo?: string | null
24
+ id: string
25
+ organizationId: string
26
+ name: string
27
+ slug: string
28
+ logo: string | null
29
+ officialName: string
30
+ address: string
31
+ registrationNumber: string | null
32
+ vatNumber: string | null
33
+ invoiceEmail: string | null
34
+ preferredLocale: 'nl' | 'en'
35
+ archivedAt: string | null
36
+ modules: ClientModuleState[]
37
+ members: ClientMemberDto[]
38
+ invitations: ClientInvitationDto[]
39
+ }
40
+
41
+ export interface ClientListResponse {
42
+ items: GenericClientDto[]
43
+ pagination: {
44
+ page: number
45
+ pageSize: number
46
+ totalItems: number
47
+ totalPages: number
48
+ hasPrevious: boolean
49
+ hasNext: boolean
50
+ }
51
+ }