@open-mercato/core 0.5.1-develop.2975.ccbadc8198 → 0.5.1-develop.2996.ce62fd491c
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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/sidebar_variant/index.js +25 -0
- package/dist/generated/entities/sidebar_variant/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +13 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/authUi.js +1 -1
- package/dist/helpers/integration/authUi.js.map +2 -2
- package/dist/modules/audit_logs/services/actionLogService.js +4 -5
- package/dist/modules/audit_logs/services/actionLogService.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +224 -35
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +3 -3
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js +161 -0
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js.map +7 -0
- package/dist/modules/auth/api/sidebar/variants/route.js +142 -0
- package/dist/modules/auth/api/sidebar/variants/route.js.map +7 -0
- package/dist/modules/auth/backend/sidebar-customization/page.js +16 -0
- package/dist/modules/auth/backend/sidebar-customization/page.js.map +7 -0
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js +28 -0
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js.map +7 -0
- package/dist/modules/auth/data/entities.js +45 -4
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/data/validators.js +63 -1
- package/dist/modules/auth/data/validators.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260427081815.js +15 -0
- package/dist/modules/auth/migrations/Migration20260427081815.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260427124900.js +15 -0
- package/dist/modules/auth/migrations/Migration20260427124900.js.map +7 -0
- package/dist/modules/auth/migrations/Migration20260427143311.js +72 -0
- package/dist/modules/auth/migrations/Migration20260427143311.js.map +7 -0
- package/dist/modules/auth/services/sidebarPreferencesService.js +176 -16
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/[id]/page.js +3 -1
- package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +4 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +8 -3
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +3 -3
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/lib/displayName.js +12 -0
- package/dist/modules/customers/lib/displayName.js.map +2 -2
- package/dist/modules/entities/cli.js +5 -6
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +124 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +7 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js +11 -0
- package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js.map +7 -0
- package/generated/entities/sidebar_variant/index.ts +11 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +13 -0
- package/package.json +6 -6
- package/src/helpers/integration/authUi.ts +1 -1
- package/src/modules/audit_logs/services/actionLogService.ts +5 -6
- package/src/modules/auth/api/sidebar/preferences/route.ts +266 -34
- package/src/modules/auth/api/sidebar/variants/[id]/route.ts +183 -0
- package/src/modules/auth/api/sidebar/variants/route.ts +157 -0
- package/src/modules/auth/backend/sidebar-customization/page.meta.ts +34 -0
- package/src/modules/auth/backend/sidebar-customization/page.tsx +17 -0
- package/src/modules/auth/data/entities.ts +48 -2
- package/src/modules/auth/data/validators.ts +70 -0
- package/src/modules/auth/migrations/.snapshot-open-mercato.json +790 -71
- package/src/modules/auth/migrations/Migration20260427081815.ts +16 -0
- package/src/modules/auth/migrations/Migration20260427124900.ts +19 -0
- package/src/modules/auth/migrations/Migration20260427143311.ts +83 -0
- package/src/modules/auth/services/sidebarPreferencesService.ts +243 -18
- package/src/modules/customers/backend/customers/companies/[id]/page.tsx +5 -4
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +6 -5
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +13 -9
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +3 -2
- package/src/modules/customers/components/formConfig.tsx +3 -3
- package/src/modules/customers/lib/displayName.ts +21 -0
- package/src/modules/entities/cli.ts +5 -6
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.ts +9 -0
- package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +168 -0
- package/src/modules/portal/i18n/de.json +20 -0
- package/src/modules/portal/i18n/en.json +20 -0
- package/src/modules/portal/i18n/es.json +20 -0
- package/src/modules/portal/i18n/pl.json +20 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
6
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
7
|
+
import { SIDEBAR_PREFERENCES_VERSION } from '@open-mercato/shared/modules/navigation/sidebarPreferences'
|
|
8
|
+
import {
|
|
9
|
+
createSidebarVariant,
|
|
10
|
+
listSidebarVariants,
|
|
11
|
+
type SidebarVariantRecord,
|
|
12
|
+
} from '../../../services/sidebarPreferencesService'
|
|
13
|
+
import {
|
|
14
|
+
createSidebarVariantInputSchema,
|
|
15
|
+
sidebarVariantRecordSchema,
|
|
16
|
+
} from '../../../data/validators'
|
|
17
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
18
|
+
|
|
19
|
+
export const metadata = {
|
|
20
|
+
GET: { requireAuth: true },
|
|
21
|
+
POST: { requireAuth: true },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const variantListResponseSchema = z.object({
|
|
25
|
+
locale: z.string(),
|
|
26
|
+
variants: z.array(sidebarVariantRecordSchema),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const variantCreateResponseSchema = z.object({
|
|
30
|
+
locale: z.string(),
|
|
31
|
+
variant: sidebarVariantRecordSchema,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const errorSchema = z.object({ error: z.string() })
|
|
35
|
+
|
|
36
|
+
function serializeVariant(record: SidebarVariantRecord) {
|
|
37
|
+
return {
|
|
38
|
+
id: record.id,
|
|
39
|
+
name: record.name,
|
|
40
|
+
isActive: record.isActive,
|
|
41
|
+
settings: {
|
|
42
|
+
version: record.settings.version ?? SIDEBAR_PREFERENCES_VERSION,
|
|
43
|
+
groupOrder: record.settings.groupOrder ?? [],
|
|
44
|
+
groupLabels: record.settings.groupLabels ?? {},
|
|
45
|
+
itemLabels: record.settings.itemLabels ?? {},
|
|
46
|
+
hiddenItems: record.settings.hiddenItems ?? [],
|
|
47
|
+
itemOrder: record.settings.itemOrder ?? {},
|
|
48
|
+
},
|
|
49
|
+
createdAt: record.createdAt.toISOString(),
|
|
50
|
+
updatedAt: record.updatedAt ? record.updatedAt.toISOString() : null,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function GET(req: Request) {
|
|
55
|
+
const auth = await getAuthFromRequest(req)
|
|
56
|
+
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
57
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
58
|
+
if (!effectiveUserId) return NextResponse.json({ error: 'No user context' }, { status: 403 })
|
|
59
|
+
|
|
60
|
+
const { locale } = await resolveTranslations()
|
|
61
|
+
const { resolve } = await createRequestContainer()
|
|
62
|
+
const em = resolve('em') as EntityManager
|
|
63
|
+
|
|
64
|
+
const variants = await listSidebarVariants(em, {
|
|
65
|
+
userId: effectiveUserId,
|
|
66
|
+
tenantId: auth.tenantId ?? null,
|
|
67
|
+
organizationId: auth.orgId ?? null,
|
|
68
|
+
locale,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{
|
|
73
|
+
locale,
|
|
74
|
+
variants: variants.map(serializeVariant),
|
|
75
|
+
},
|
|
76
|
+
{ headers: { 'cache-control': 'no-store, no-cache, must-revalidate' } },
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function POST(req: Request) {
|
|
81
|
+
const auth = await getAuthFromRequest(req)
|
|
82
|
+
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
83
|
+
const effectiveUserId = auth.isApiKey ? auth.userId : auth.sub
|
|
84
|
+
if (!effectiveUserId) return NextResponse.json({ error: 'No user context' }, { status: 403 })
|
|
85
|
+
|
|
86
|
+
let parsedBody: unknown
|
|
87
|
+
try {
|
|
88
|
+
parsedBody = await req.json()
|
|
89
|
+
} catch {
|
|
90
|
+
parsedBody = {}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const parsed = createSidebarVariantInputSchema.safeParse(parsedBody)
|
|
94
|
+
if (!parsed.success) {
|
|
95
|
+
return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const { locale } = await resolveTranslations()
|
|
100
|
+
const { resolve } = await createRequestContainer()
|
|
101
|
+
const em = resolve('em') as EntityManager
|
|
102
|
+
|
|
103
|
+
const variant = await createSidebarVariant(em, {
|
|
104
|
+
userId: effectiveUserId,
|
|
105
|
+
tenantId: auth.tenantId ?? null,
|
|
106
|
+
organizationId: auth.orgId ?? null,
|
|
107
|
+
locale,
|
|
108
|
+
}, {
|
|
109
|
+
name: parsed.data.name ?? null,
|
|
110
|
+
settings: parsed.data.settings ?? null,
|
|
111
|
+
isActive: parsed.data.isActive,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return NextResponse.json({
|
|
115
|
+
locale,
|
|
116
|
+
variant: serializeVariant(variant),
|
|
117
|
+
})
|
|
118
|
+
} catch (err) {
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
120
|
+
// MikroORM throws UniqueConstraintViolationException for unique conflicts.
|
|
121
|
+
// The constraint name embeds the columns: (user_id, tenant_id, locale, name).
|
|
122
|
+
if (err instanceof Error && err.constructor?.name === 'UniqueConstraintViolationException') {
|
|
123
|
+
return NextResponse.json(
|
|
124
|
+
{ error: 'A variant with this name already exists. Choose a different name.', code: 'duplicate_name' },
|
|
125
|
+
{ status: 409 },
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.error('[sidebar-variants POST] failed', err)
|
|
130
|
+
return NextResponse.json({ error: message }, { status: 500 })
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const openApi: OpenApiRouteDoc = {
|
|
135
|
+
tag: 'Authentication & Accounts',
|
|
136
|
+
summary: 'Sidebar variants',
|
|
137
|
+
methods: {
|
|
138
|
+
GET: {
|
|
139
|
+
summary: 'List sidebar variants',
|
|
140
|
+
description: 'Returns the named sidebar variants saved by the current user for the current tenant + locale.',
|
|
141
|
+
responses: [
|
|
142
|
+
{ status: 200, description: 'Variant list', schema: variantListResponseSchema },
|
|
143
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
POST: {
|
|
147
|
+
summary: 'Create a sidebar variant',
|
|
148
|
+
description: 'Creates a new variant. If `name` is omitted or blank, an auto-name like "My preferences", "My preferences 2", … is assigned.',
|
|
149
|
+
requestBody: { contentType: 'application/json', schema: createSidebarVariantInputSchema },
|
|
150
|
+
responses: [
|
|
151
|
+
{ status: 200, description: 'Variant created', schema: variantCreateResponseSchema },
|
|
152
|
+
{ status: 400, description: 'Invalid payload', schema: errorSchema },
|
|
153
|
+
{ status: 401, description: 'Unauthorized', schema: errorSchema },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const sidebarCustomizeIcon = React.createElement(
|
|
4
|
+
'svg',
|
|
5
|
+
{ width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
6
|
+
React.createElement('rect', { x: 3, y: 3, width: 7, height: 18, rx: 1 }),
|
|
7
|
+
React.createElement('rect', { x: 14, y: 3, width: 7, height: 11, rx: 1 }),
|
|
8
|
+
React.createElement('path', { d: 'M14 17h7' }),
|
|
9
|
+
React.createElement('path', { d: 'M17.5 14v7' }),
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// Page is reachable by any authenticated user — every staff user has
|
|
13
|
+
// always been able to customize their PERSONAL sidebar (the variants /
|
|
14
|
+
// preferences APIs gate only role-application via `auth.sidebar.manage`).
|
|
15
|
+
// Inside the editor, the "Apply to roles" card and role variants picker are
|
|
16
|
+
// already conditionally hidden via `canApplyToRoles` (server-checked against
|
|
17
|
+
// `auth.sidebar.manage`), so non-admins see only the personal-scope flow,
|
|
18
|
+
// matching the pre-PR inline-editor behavior. Restricting the whole page
|
|
19
|
+
// to `auth.sidebar.manage` would be a stealth regression for non-admins.
|
|
20
|
+
export const metadata = {
|
|
21
|
+
requireAuth: true,
|
|
22
|
+
pageTitle: 'Customize sidebar',
|
|
23
|
+
pageTitleKey: 'appShell.customizeSidebar',
|
|
24
|
+
pageGroup: 'Customization',
|
|
25
|
+
pageGroupKey: 'appShell.sidebarCustomizationGroup',
|
|
26
|
+
pageOrder: 1,
|
|
27
|
+
icon: sidebarCustomizeIcon,
|
|
28
|
+
pageContext: 'settings' as const,
|
|
29
|
+
breadcrumb: [
|
|
30
|
+
{ label: 'Customize sidebar', labelKey: 'appShell.customizeSidebar' },
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default metadata
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { useRouter } from 'next/navigation'
|
|
4
|
+
import { SidebarCustomizationEditor } from '@open-mercato/ui/backend/sidebar/SidebarCustomizationEditor'
|
|
5
|
+
|
|
6
|
+
export default function SidebarCustomizationPage() {
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
const goBack = React.useCallback(() => {
|
|
9
|
+
router.push('/backend/settings')
|
|
10
|
+
}, [router])
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="space-y-4">
|
|
14
|
+
<SidebarCustomizationEditor onCanceled={goBack} />
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -57,7 +57,10 @@ export class Role {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
@Entity({ tableName: 'user_sidebar_preferences' })
|
|
60
|
-
|
|
60
|
+
// Uniqueness is enforced by a partial unique index (`user_sidebar_preferences_active_unique_idx`)
|
|
61
|
+
// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in
|
|
62
|
+
// Migration20260427143311. A `@Unique` decorator can't express a partial index,
|
|
63
|
+
// so the entity intentionally omits it — the migration is the source of truth.
|
|
61
64
|
export class UserSidebarPreference {
|
|
62
65
|
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
63
66
|
id!: string
|
|
@@ -88,7 +91,10 @@ export class UserSidebarPreference {
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
@Entity({ tableName: 'role_sidebar_preferences' })
|
|
91
|
-
|
|
94
|
+
// Uniqueness is enforced by a partial unique index (`role_sidebar_preferences_active_unique_idx`)
|
|
95
|
+
// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in
|
|
96
|
+
// Migration20260427143311. A `@Unique` decorator can't express a partial index,
|
|
97
|
+
// so the entity intentionally omits it — the migration is the source of truth.
|
|
92
98
|
export class RoleSidebarPreference {
|
|
93
99
|
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
94
100
|
id!: string
|
|
@@ -115,6 +121,46 @@ export class RoleSidebarPreference {
|
|
|
115
121
|
deletedAt?: Date | null
|
|
116
122
|
}
|
|
117
123
|
|
|
124
|
+
@Entity({ tableName: 'sidebar_variants' })
|
|
125
|
+
// Uniqueness is enforced by a partial unique index (`sidebar_variants_active_name_unique_idx`)
|
|
126
|
+
// scoped to live rows (`WHERE deleted_at IS NULL`) and owned by raw SQL in
|
|
127
|
+
// Migration20260427143311. A `@Unique` decorator can't express a partial index,
|
|
128
|
+
// so the entity intentionally omits it — the migration is the source of truth.
|
|
129
|
+
export class SidebarVariant {
|
|
130
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
131
|
+
id!: string
|
|
132
|
+
|
|
133
|
+
@ManyToOne(() => User)
|
|
134
|
+
user!: User
|
|
135
|
+
|
|
136
|
+
@Property({ name: 'tenant_id', type: 'uuid', nullable: true })
|
|
137
|
+
tenantId?: string | null
|
|
138
|
+
|
|
139
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
140
|
+
organizationId?: string | null
|
|
141
|
+
|
|
142
|
+
@Property({ type: 'text' })
|
|
143
|
+
locale!: string
|
|
144
|
+
|
|
145
|
+
@Property({ type: 'text' })
|
|
146
|
+
name!: string
|
|
147
|
+
|
|
148
|
+
@Property({ name: 'settings_json', type: 'json', nullable: true })
|
|
149
|
+
settingsJson?: unknown
|
|
150
|
+
|
|
151
|
+
@Property({ name: 'is_active', type: 'boolean', default: false })
|
|
152
|
+
isActive: boolean = false
|
|
153
|
+
|
|
154
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
155
|
+
createdAt: Date = new Date()
|
|
156
|
+
|
|
157
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date(), nullable: true })
|
|
158
|
+
updatedAt?: Date
|
|
159
|
+
|
|
160
|
+
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
161
|
+
deletedAt?: Date | null
|
|
162
|
+
}
|
|
163
|
+
|
|
118
164
|
@Entity({ tableName: 'user_roles' })
|
|
119
165
|
export class UserRole {
|
|
120
166
|
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
@@ -24,14 +24,80 @@ export const refreshSessionRequestSchema = z.object({
|
|
|
24
24
|
refreshToken: z.string().min(1),
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
+
export const sidebarPreferencesScopeSchema = z.union([
|
|
28
|
+
z.object({ type: z.literal('user') }),
|
|
29
|
+
z.object({ type: z.literal('role'), roleId: z.string().uuid() }),
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
// Sidebar settings shape shared by both `sidebarPreferencesInputSchema` (which
|
|
33
|
+
// adds role-targeting fields) and the variants API (which uses just the
|
|
34
|
+
// settings + name + isActive). Keeping the field constraints in one place
|
|
35
|
+
// prevents drift between the preferences and variants surfaces.
|
|
36
|
+
export const sidebarVariantSettingsSchema = z.object({
|
|
37
|
+
version: z.number().int().positive().optional(),
|
|
38
|
+
groupOrder: z.array(z.string().min(1)).max(200).optional(),
|
|
39
|
+
groupLabels: z.record(z.string().min(1), z.string().min(1).max(120)).optional(),
|
|
40
|
+
itemLabels: z.record(z.string().min(1), z.string().min(1).max(120)).optional(),
|
|
41
|
+
hiddenItems: z.array(z.string().min(1)).max(500).optional(),
|
|
42
|
+
itemOrder: z.record(z.string().min(1), z.array(z.string().min(1)).max(500)).optional(),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const createSidebarVariantInputSchema = z.object({
|
|
46
|
+
name: z.string().trim().min(1).max(120).optional(),
|
|
47
|
+
settings: sidebarVariantSettingsSchema.optional(),
|
|
48
|
+
isActive: z.boolean().optional(),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export const updateSidebarVariantInputSchema = z.object({
|
|
52
|
+
name: z.string().trim().min(1).max(120).optional(),
|
|
53
|
+
settings: sidebarVariantSettingsSchema.optional(),
|
|
54
|
+
isActive: z.boolean().optional(),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const sidebarVariantRecordSchema = z.object({
|
|
58
|
+
id: z.string().uuid(),
|
|
59
|
+
name: z.string(),
|
|
60
|
+
isActive: z.boolean(),
|
|
61
|
+
settings: z.object({
|
|
62
|
+
version: z.number().int().positive(),
|
|
63
|
+
groupOrder: z.array(z.string()),
|
|
64
|
+
groupLabels: z.record(z.string(), z.string()),
|
|
65
|
+
itemLabels: z.record(z.string(), z.string()),
|
|
66
|
+
hiddenItems: z.array(z.string()),
|
|
67
|
+
itemOrder: z.record(z.string(), z.array(z.string())),
|
|
68
|
+
}),
|
|
69
|
+
createdAt: z.string(),
|
|
70
|
+
updatedAt: z.string().nullable(),
|
|
71
|
+
})
|
|
72
|
+
|
|
27
73
|
export const sidebarPreferencesInputSchema = z.object({
|
|
28
74
|
version: z.number().int().positive().optional(),
|
|
29
75
|
groupOrder: z.array(z.string().min(1)).max(200).optional(),
|
|
30
76
|
groupLabels: z.record(z.string().min(1), z.string().min(1).max(120)).optional(),
|
|
31
77
|
itemLabels: z.record(z.string().min(1), z.string().min(1).max(120)).optional(),
|
|
32
78
|
hiddenItems: z.array(z.string().min(1)).max(500).optional(),
|
|
79
|
+
itemOrder: z.record(z.string().min(1), z.array(z.string().min(1)).max(500)).optional(),
|
|
33
80
|
applyToRoles: z.array(z.string().uuid()).optional(),
|
|
34
81
|
clearRoleIds: z.array(z.string().uuid()).optional(),
|
|
82
|
+
scope: sidebarPreferencesScopeSchema.optional(),
|
|
83
|
+
}).superRefine((value, ctx) => {
|
|
84
|
+
const scopeType = value.scope?.type ?? 'user'
|
|
85
|
+
if (scopeType === 'role') {
|
|
86
|
+
if ((value.applyToRoles?.length ?? 0) > 0) {
|
|
87
|
+
ctx.addIssue({
|
|
88
|
+
code: z.ZodIssueCode.custom,
|
|
89
|
+
path: ['applyToRoles'],
|
|
90
|
+
message: 'applyToRoles is only valid when scope.type === "user"',
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
if ((value.clearRoleIds?.length ?? 0) > 0) {
|
|
94
|
+
ctx.addIssue({
|
|
95
|
+
code: z.ZodIssueCode.custom,
|
|
96
|
+
path: ['clearRoleIds'],
|
|
97
|
+
message: 'clearRoleIds is only valid when scope.type === "user"',
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
}
|
|
35
101
|
})
|
|
36
102
|
|
|
37
103
|
// Optional helpers for CLI or admin forms
|
|
@@ -56,5 +122,9 @@ export type RequestPasswordResetInput = z.infer<typeof requestPasswordResetSchem
|
|
|
56
122
|
export type ConfirmPasswordResetInput = z.infer<typeof confirmPasswordResetSchema>
|
|
57
123
|
export type RefreshSessionRequestInput = z.infer<typeof refreshSessionRequestSchema>
|
|
58
124
|
export type SidebarPreferencesInput = z.infer<typeof sidebarPreferencesInputSchema>
|
|
125
|
+
export type SidebarVariantSettingsInput = z.infer<typeof sidebarVariantSettingsSchema>
|
|
126
|
+
export type CreateSidebarVariantInput = z.infer<typeof createSidebarVariantInputSchema>
|
|
127
|
+
export type UpdateSidebarVariantInput = z.infer<typeof updateSidebarVariantInputSchema>
|
|
128
|
+
export type SidebarVariantRecordResponse = z.infer<typeof sidebarVariantRecordSchema>
|
|
59
129
|
export type UserCreateInput = z.infer<typeof userCreateSchema>
|
|
60
130
|
export type FeatureCheckRequestInput = z.infer<typeof featureCheckRequestSchema>
|