@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.
Files changed (82) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/sidebar_variant/index.js +25 -0
  3. package/dist/generated/entities/sidebar_variant/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +13 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/authUi.js +1 -1
  9. package/dist/helpers/integration/authUi.js.map +2 -2
  10. package/dist/modules/audit_logs/services/actionLogService.js +4 -5
  11. package/dist/modules/audit_logs/services/actionLogService.js.map +2 -2
  12. package/dist/modules/auth/api/sidebar/preferences/route.js +224 -35
  13. package/dist/modules/auth/api/sidebar/preferences/route.js.map +3 -3
  14. package/dist/modules/auth/api/sidebar/variants/[id]/route.js +161 -0
  15. package/dist/modules/auth/api/sidebar/variants/[id]/route.js.map +7 -0
  16. package/dist/modules/auth/api/sidebar/variants/route.js +142 -0
  17. package/dist/modules/auth/api/sidebar/variants/route.js.map +7 -0
  18. package/dist/modules/auth/backend/sidebar-customization/page.js +16 -0
  19. package/dist/modules/auth/backend/sidebar-customization/page.js.map +7 -0
  20. package/dist/modules/auth/backend/sidebar-customization/page.meta.js +28 -0
  21. package/dist/modules/auth/backend/sidebar-customization/page.meta.js.map +7 -0
  22. package/dist/modules/auth/data/entities.js +45 -4
  23. package/dist/modules/auth/data/entities.js.map +2 -2
  24. package/dist/modules/auth/data/validators.js +63 -1
  25. package/dist/modules/auth/data/validators.js.map +2 -2
  26. package/dist/modules/auth/migrations/Migration20260427081815.js +15 -0
  27. package/dist/modules/auth/migrations/Migration20260427081815.js.map +7 -0
  28. package/dist/modules/auth/migrations/Migration20260427124900.js +15 -0
  29. package/dist/modules/auth/migrations/Migration20260427124900.js.map +7 -0
  30. package/dist/modules/auth/migrations/Migration20260427143311.js +72 -0
  31. package/dist/modules/auth/migrations/Migration20260427143311.js.map +7 -0
  32. package/dist/modules/auth/services/sidebarPreferencesService.js +176 -16
  33. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  34. package/dist/modules/customers/backend/customers/companies/[id]/page.js +3 -1
  35. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  36. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +4 -2
  37. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  38. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +8 -3
  39. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  40. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +3 -2
  41. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  42. package/dist/modules/customers/components/formConfig.js +3 -3
  43. package/dist/modules/customers/components/formConfig.js.map +2 -2
  44. package/dist/modules/customers/lib/displayName.js +12 -0
  45. package/dist/modules/customers/lib/displayName.js.map +2 -2
  46. package/dist/modules/entities/cli.js +5 -6
  47. package/dist/modules/entities/cli.js.map +2 -2
  48. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +124 -0
  49. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +7 -0
  50. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js +11 -0
  51. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.js.map +7 -0
  52. package/generated/entities/sidebar_variant/index.ts +11 -0
  53. package/generated/entities.ids.generated.ts +1 -0
  54. package/generated/entity-fields-registry.ts +13 -0
  55. package/package.json +6 -6
  56. package/src/helpers/integration/authUi.ts +1 -1
  57. package/src/modules/audit_logs/services/actionLogService.ts +5 -6
  58. package/src/modules/auth/api/sidebar/preferences/route.ts +266 -34
  59. package/src/modules/auth/api/sidebar/variants/[id]/route.ts +183 -0
  60. package/src/modules/auth/api/sidebar/variants/route.ts +157 -0
  61. package/src/modules/auth/backend/sidebar-customization/page.meta.ts +34 -0
  62. package/src/modules/auth/backend/sidebar-customization/page.tsx +17 -0
  63. package/src/modules/auth/data/entities.ts +48 -2
  64. package/src/modules/auth/data/validators.ts +70 -0
  65. package/src/modules/auth/migrations/.snapshot-open-mercato.json +790 -71
  66. package/src/modules/auth/migrations/Migration20260427081815.ts +16 -0
  67. package/src/modules/auth/migrations/Migration20260427124900.ts +19 -0
  68. package/src/modules/auth/migrations/Migration20260427143311.ts +83 -0
  69. package/src/modules/auth/services/sidebarPreferencesService.ts +243 -18
  70. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +5 -4
  71. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +6 -5
  72. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +13 -9
  73. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +3 -2
  74. package/src/modules/customers/components/formConfig.tsx +3 -3
  75. package/src/modules/customers/lib/displayName.ts +21 -0
  76. package/src/modules/entities/cli.ts +5 -6
  77. package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.meta.ts +9 -0
  78. package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +168 -0
  79. package/src/modules/portal/i18n/de.json +20 -0
  80. package/src/modules/portal/i18n/en.json +20 -0
  81. package/src/modules/portal/i18n/es.json +20 -0
  82. 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
- @Unique({ properties: ['user', 'tenantId', 'organizationId', 'locale'] })
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
- @Unique({ properties: ['role', 'tenantId', 'locale'] })
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>