@murumets-ee/create 0.1.14 → 0.1.15

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/dist/cli.mjs CHANGED
@@ -1,15 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import*as e from"@clack/prompts";import t from"picocolors";import{dirname as n,join as r}from"node:path";import{execSync as i}from"node:child_process";import{appendFile as a,mkdir as o,readFile as s,rm as c,writeFile as l}from"node:fs/promises";var u=Object.defineProperty,d=(e,t)=>{let n={};for(var r in e)u(n,r,{get:e[r],enumerable:!0});return t||u(n,Symbol.toStringTag,{value:`Module`}),n};function f(e){let t={},n=e.slice(2);for(let e=0;e<n.length;e++){let r=n[e];if(r===`--mode`&&n[e+1]){let r=n[++e];(r===`single`||r===`multi`)&&(t.mode=r)}else r===`--install`?t.install=!0:r===`--no-install`?t.install=!1:!r.startsWith(`-`)&&!t.name&&(t.name=r)}return t}async function p(t){let n=f(process.argv),r=n.name??t;if(r&&n.mode&&n.install!==void 0)return{name:r,mode:n.mode,installDeps:n.install};e.intro(`@murumets-ee/create`);let i=r??await e.text({message:`Project name:`,placeholder:`my-project`,validate:e=>{if(!e)return`Project name is required`;if(!/^[a-z0-9-]+$/.test(e))return`Use lowercase letters, numbers, and hyphens only`}});if(e.isCancel(i))return e.cancel(`Cancelled.`),null;let a=n.mode??await e.select({message:`App setup:`,options:[{value:`single`,label:`Single app`,hint:`everything in one Next.js project`},{value:`multi`,label:`Admin + Frontend`,hint:`two apps in a pnpm workspace`}]});if(e.isCancel(a))return e.cancel(`Cancelled.`),null;let o=n.install??await e.confirm({message:`Install dependencies?`,initialValue:!0});return e.isCancel(o)?(e.cancel(`Cancelled.`),null):{name:i,mode:a,installDeps:o}}var m=d({appendToFile:()=>y,deleteFiles:()=>_,mergePackageJson:()=>g,runCommand:()=>v,writeFile:()=>h});async function h(e,t){await o(n(e),{recursive:!0}),await l(e,t,`utf-8`)}async function g(e,t){let n=await s(e,`utf-8`),r=JSON.parse(n);t.type&&(r.type=t.type),t.dependencies&&(r.dependencies={...r.dependencies,...t.dependencies}),t.devDependencies&&(r.devDependencies={...r.devDependencies,...t.devDependencies}),t.scripts&&(r.scripts={...r.scripts,...t.scripts}),await l(e,`${JSON.stringify(r,null,2)}\n`,`utf-8`)}async function _(e,t){for(let n of t)await c(r(e,n),{force:!0,recursive:!0})}function v(e,t){return i(e,{cwd:t?.cwd,stdio:`pipe`,encoding:`utf-8`})}async function y(e,t){await a(e,t,`utf-8`)}async function b(e,t){await h(r(e,`lib/admin-config.ts`),`import { Media } from '@murumets-ee/media'
3
+ import {
4
+ Ticket,
5
+ TicketMessage,
6
+ TicketAttachment,
7
+ Department,
8
+ TicketTag,
9
+ } from '@murumets-ee/ticketing'
3
10
  import { Article, Category } from '@/entities'
4
11
 
5
12
  /** All entities available in the admin (drives sidebar nav + CRUD) */
6
- export const allEntities = [Article, Media, Category]
13
+ export const allEntities = [
14
+ Article, Media, Category,
15
+ Ticket, TicketMessage, TicketAttachment, Department, TicketTag,
16
+ ]
7
17
 
8
18
  /** Taxonomy entities keyed by name */
9
- export const taxonomyVocabularies = { category: Category }
19
+ export const taxonomyVocabularies = {
20
+ category: Category,
21
+ department: Department,
22
+ ticket_tag: TicketTag,
23
+ }
10
24
 
11
25
  /** Entities exposed via generic CRUD API handler */
12
- export const crudEntities = [Article, Media]
26
+ export const crudEntities = [Article, Media, Ticket, TicketMessage, TicketAttachment]
13
27
 
14
28
  /** Plugin resources for the permission catalog */
15
29
  export const pluginResources = [
@@ -17,6 +31,7 @@ export const pluginResources = [
17
31
  { resource: 'settings', actions: ['view', 'update'] },
18
32
  { resource: 'audit-logs', actions: ['view'] },
19
33
  { resource: 'permissions', actions: ['view', 'create', 'update', 'delete'] },
34
+ { resource: 'ticketing', actions: ['view', 'create', 'update', 'delete'] },
20
35
  ]
21
36
  `),await h(r(e,`lib/content-locale.ts`),`import { cookies } from 'next/headers'
22
37
  import { hasLocale } from 'next-intl'
@@ -107,15 +122,20 @@ import { AdminShell } from '@murumets-ee/admin-ui'
107
122
  import type { AdminNavGroup } from '@murumets-ee/admin-ui/server'
108
123
  import {
109
124
  Activity,
125
+ Building2,
110
126
  FileText,
111
127
  FolderTree,
112
128
  Home,
113
129
  Image,
114
130
  ImageDown,
131
+ Inbox,
132
+ Kanban,
115
133
  Lock,
116
134
  PenTool,
117
135
  ShieldCheck,
136
+ Tag,
118
137
  Tags,
138
+ Ticket,
119
139
  Users,
120
140
  type LucideIcon,
121
141
  } from 'lucide-react'
@@ -127,6 +147,7 @@ const ICON_MAP: Record<string, LucideIcon> = {
127
147
  'file-text': FileText,
128
148
  'folder-tree': FolderTree,
129
149
  tags: Tags,
150
+ ticket: Ticket,
130
151
  }
131
152
 
132
153
  interface AdminLayoutProps {
@@ -162,6 +183,16 @@ export function AdminLayout({
162
183
  ],
163
184
  }
164
185
 
186
+ const supportGroup: SidebarNavGroup = {
187
+ label: 'Support',
188
+ items: [
189
+ { label: 'Tickets', href: '/admin/tickets', icon: Inbox },
190
+ { label: 'Board', href: '/admin/tickets/board', icon: Kanban },
191
+ { label: 'Departments', href: '/admin/departments', icon: Building2 },
192
+ { label: 'Ticket Tags', href: '/admin/ticket-tags', icon: Tag },
193
+ ],
194
+ }
195
+
165
196
  const systemGroup: SidebarNavGroup = {
166
197
  label: 'System',
167
198
  items: [
@@ -177,6 +208,7 @@ export function AdminLayout({
177
208
  const navGroups: SidebarNavGroup[] = [
178
209
  staticBefore,
179
210
  ...dynamicGroups,
211
+ supportGroup,
180
212
  systemGroup,
181
213
  ]
182
214
 
@@ -1017,6 +1049,207 @@ export default async function ActivityPage({ params }: { params: Promise<{ local
1017
1049
  )
1018
1050
  })
1019
1051
  }
1052
+ `),await h(r(e,`app/[locale]/(shell)/admin/tickets/page.tsx`),`import { Ticket, Department, TicketTag } from '@murumets-ee/ticketing'
1053
+ import { headers } from 'next/headers'
1054
+ import { setRequestLocale } from 'next-intl/server'
1055
+ import { withAdminContext } from '@/lib/with-admin-context'
1056
+ import { auth } from '@/lib/auth'
1057
+ import { TicketsInboxClient } from './tickets-inbox-client'
1058
+ import { fetchEntityList } from '@murumets-ee/admin-ui/server'
1059
+ import type {
1060
+ TicketData,
1061
+ DepartmentData,
1062
+ TagData,
1063
+ } from '@murumets-ee/ticketing-ui'
1064
+
1065
+ interface TicketsPageProps {
1066
+ params: Promise<{ locale: string }>
1067
+ }
1068
+
1069
+ export default async function TicketsPage({ params }: TicketsPageProps) {
1070
+ const { locale } = await params
1071
+ setRequestLocale(locale)
1072
+
1073
+ const h = await headers()
1074
+ const session = await auth.api.getSession({ headers: h })
1075
+ const currentUserId = session?.user?.id ?? ''
1076
+
1077
+ return withAdminContext(locale, async () => {
1078
+ const ticketData = await fetchEntityList(Ticket, {
1079
+ sortField: 'lastReplyAt',
1080
+ sortDirection: 'desc',
1081
+ limit: 50,
1082
+ })
1083
+
1084
+ const departmentData = await fetchEntityList(Department, {
1085
+ sortField: 'name',
1086
+ sortDirection: 'asc',
1087
+ limit: 100,
1088
+ })
1089
+
1090
+ const tagData = await fetchEntityList(TicketTag, {
1091
+ sortField: 'name',
1092
+ sortDirection: 'asc',
1093
+ limit: 100,
1094
+ })
1095
+
1096
+ return (
1097
+ <TicketsInboxClient
1098
+ initialTickets={ticketData.items as unknown as TicketData[]}
1099
+ initialTotal={ticketData.total}
1100
+ initialDepartments={departmentData.items as unknown as DepartmentData[]}
1101
+ initialTags={tagData.items as unknown as TagData[]}
1102
+ currentUserId={currentUserId}
1103
+ locale={locale}
1104
+ />
1105
+ )
1106
+ })
1107
+ }
1108
+ `),await h(r(e,`app/[locale]/(shell)/admin/tickets/tickets-inbox-client.tsx`),`'use client'
1109
+
1110
+ import { QueryProvider } from '@murumets-ee/admin-ui'
1111
+ import { InboxProvider, TicketInbox } from '@murumets-ee/ticketing-ui/inbox'
1112
+ import type {
1113
+ TicketData,
1114
+ DepartmentData,
1115
+ TagData,
1116
+ } from '@murumets-ee/ticketing-ui'
1117
+
1118
+ interface TicketsInboxClientProps {
1119
+ initialTickets: TicketData[]
1120
+ initialTotal: number
1121
+ initialDepartments: DepartmentData[]
1122
+ initialTags: TagData[]
1123
+ currentUserId: string
1124
+ locale: string
1125
+ }
1126
+
1127
+ export function TicketsInboxClient({
1128
+ initialTickets,
1129
+ initialTotal,
1130
+ initialDepartments,
1131
+ initialTags,
1132
+ currentUserId,
1133
+ }: TicketsInboxClientProps) {
1134
+ return (
1135
+ <QueryProvider>
1136
+ <InboxProvider
1137
+ apiBasePath="/api/admin"
1138
+ initialTickets={initialTickets}
1139
+ initialTotal={initialTotal}
1140
+ initialDepartments={initialDepartments}
1141
+ initialTags={initialTags}
1142
+ currentUserId={currentUserId}
1143
+ >
1144
+ <div className="h-[calc(100vh-3.5rem)]">
1145
+ <TicketInbox currentUserId={currentUserId} />
1146
+ </div>
1147
+ </InboxProvider>
1148
+ </QueryProvider>
1149
+ )
1150
+ }
1151
+ `),await h(r(e,`app/[locale]/(shell)/admin/tickets/board/page.tsx`),`import { Ticket, Department } from '@murumets-ee/ticketing'
1152
+ import { setRequestLocale } from 'next-intl/server'
1153
+ import { withAdminContext } from '@/lib/with-admin-context'
1154
+ import { fetchEntityList } from '@murumets-ee/admin-ui/server'
1155
+ import { TicketBoardClient } from './ticket-board-client'
1156
+ import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
1157
+
1158
+ interface TicketBoardPageProps {
1159
+ params: Promise<{ locale: string }>
1160
+ }
1161
+
1162
+ export default async function TicketBoardPage({ params }: TicketBoardPageProps) {
1163
+ const { locale } = await params
1164
+ setRequestLocale(locale)
1165
+
1166
+ return withAdminContext(locale, async () => {
1167
+ const ticketData = await fetchEntityList(Ticket, {
1168
+ sortField: 'lastReplyAt',
1169
+ sortDirection: 'desc',
1170
+ limit: 200,
1171
+ })
1172
+
1173
+ const departmentData = await fetchEntityList(Department, {
1174
+ sortField: 'name',
1175
+ sortDirection: 'asc',
1176
+ limit: 100,
1177
+ })
1178
+
1179
+ return (
1180
+ <div className="p-6 h-[calc(100vh-3.5rem)]">
1181
+ <div className="mb-4">
1182
+ <h1 className="text-2xl font-bold">Ticket Board</h1>
1183
+ <p className="text-sm text-muted-foreground">
1184
+ Kanban view of tickets by status
1185
+ </p>
1186
+ </div>
1187
+ <TicketBoardClient
1188
+ initialTickets={ticketData.items as unknown as TicketData[]}
1189
+ initialDepartments={departmentData.items as unknown as DepartmentData[]}
1190
+ locale={locale}
1191
+ />
1192
+ </div>
1193
+ )
1194
+ })
1195
+ }
1196
+ `),await h(r(e,`app/[locale]/(shell)/admin/tickets/board/ticket-board-client.tsx`),`'use client'
1197
+
1198
+ import { useMemo } from 'react'
1199
+ import { TicketBoard } from '@murumets-ee/ticketing-ui/board'
1200
+ import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
1201
+ import { useRouter } from 'next/navigation'
1202
+
1203
+ interface TicketBoardClientProps {
1204
+ initialTickets: TicketData[]
1205
+ initialDepartments: DepartmentData[]
1206
+ locale: string
1207
+ }
1208
+
1209
+ export function TicketBoardClient({
1210
+ initialTickets,
1211
+ initialDepartments,
1212
+ locale,
1213
+ }: TicketBoardClientProps) {
1214
+ const router = useRouter()
1215
+ const departmentMap = useMemo(
1216
+ () => new Map(initialDepartments.map((d) => [d.id, d])),
1217
+ [initialDepartments],
1218
+ )
1219
+
1220
+ return (
1221
+ <TicketBoard
1222
+ tickets={initialTickets}
1223
+ departments={departmentMap}
1224
+ onTicketSelect={(ticket) => {
1225
+ router.push(\`/\${locale}/admin/tickets?selected=\${ticket.id}\`)
1226
+ }}
1227
+ className="h-[calc(100%-4rem)]"
1228
+ />
1229
+ )
1230
+ }
1231
+ `),await h(r(e,`app/api/ticketing/inbound/route.ts`),`/**
1232
+ * Resend inbound email webhook endpoint.
1233
+ *
1234
+ * Public endpoint (no session auth) — protected by webhook signature
1235
+ * verification (HMAC-SHA256). Lives outside the admin API handler
1236
+ * because the admin handler requires authentication.
1237
+ */
1238
+
1239
+ import { getToolkitApp } from '@/lib/app'
1240
+
1241
+ await getToolkitApp()
1242
+
1243
+ export async function POST(req: Request): Promise<Response> {
1244
+ const { handleInboundWebhook } = await import('@murumets-ee/ticketing')
1245
+ const { QueueClient } = await import('@murumets-ee/queue/client')
1246
+ const { getApp } = await import('@murumets-ee/core')
1247
+
1248
+ const db = getApp().db.readWrite
1249
+ const queueClient = new QueueClient({ db })
1250
+
1251
+ return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
1252
+ }
1020
1253
  `),await h(r(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
1021
1254
  import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
1022
1255
  import { permissionRoutes } from '@murumets-ee/auth/admin'
@@ -1036,6 +1269,7 @@ import { createSettingsClient } from '@murumets-ee/settings'
1036
1269
  import { settingsRoutes } from '@murumets-ee/settings/admin'
1037
1270
  import { storageRoutes } from '@murumets-ee/storage/admin'
1038
1271
  import { taxonomyRoutes } from '@murumets-ee/taxonomy/admin'
1272
+ import { ticketingRoutes } from '@murumets-ee/ticketing/admin'
1039
1273
  import {
1040
1274
  allEntities,
1041
1275
  crudEntities,
@@ -1058,6 +1292,7 @@ const routes = [
1058
1292
  storageRoutes(),
1059
1293
  settingsRoutes(siteSettings),
1060
1294
  taxonomyRoutes(taxonomyVocabularies),
1295
+ ticketingRoutes(),
1061
1296
  logRoutes(() => new AuditLogClient(getApp().db.readWrite)),
1062
1297
  permissionRoutes({
1063
1298
  getStatements: () => buildResourceCatalog(allEntities, pluginResources),
package/dist/index.mjs CHANGED
@@ -1,14 +1,28 @@
1
1
  import{i as e,n as t,o as n,r,t as i}from"./utils-Bmw75lEa.mjs";import{join as a}from"node:path";import{mkdir as o}from"node:fs/promises";async function s(e,t){await n(a(e,`lib/admin-config.ts`),`import { Media } from '@murumets-ee/media'
2
+ import {
3
+ Ticket,
4
+ TicketMessage,
5
+ TicketAttachment,
6
+ Department,
7
+ TicketTag,
8
+ } from '@murumets-ee/ticketing'
2
9
  import { Article, Category } from '@/entities'
3
10
 
4
11
  /** All entities available in the admin (drives sidebar nav + CRUD) */
5
- export const allEntities = [Article, Media, Category]
12
+ export const allEntities = [
13
+ Article, Media, Category,
14
+ Ticket, TicketMessage, TicketAttachment, Department, TicketTag,
15
+ ]
6
16
 
7
17
  /** Taxonomy entities keyed by name */
8
- export const taxonomyVocabularies = { category: Category }
18
+ export const taxonomyVocabularies = {
19
+ category: Category,
20
+ department: Department,
21
+ ticket_tag: TicketTag,
22
+ }
9
23
 
10
24
  /** Entities exposed via generic CRUD API handler */
11
- export const crudEntities = [Article, Media]
25
+ export const crudEntities = [Article, Media, Ticket, TicketMessage, TicketAttachment]
12
26
 
13
27
  /** Plugin resources for the permission catalog */
14
28
  export const pluginResources = [
@@ -16,6 +30,7 @@ export const pluginResources = [
16
30
  { resource: 'settings', actions: ['view', 'update'] },
17
31
  { resource: 'audit-logs', actions: ['view'] },
18
32
  { resource: 'permissions', actions: ['view', 'create', 'update', 'delete'] },
33
+ { resource: 'ticketing', actions: ['view', 'create', 'update', 'delete'] },
19
34
  ]
20
35
  `),await n(a(e,`lib/content-locale.ts`),`import { cookies } from 'next/headers'
21
36
  import { hasLocale } from 'next-intl'
@@ -106,15 +121,20 @@ import { AdminShell } from '@murumets-ee/admin-ui'
106
121
  import type { AdminNavGroup } from '@murumets-ee/admin-ui/server'
107
122
  import {
108
123
  Activity,
124
+ Building2,
109
125
  FileText,
110
126
  FolderTree,
111
127
  Home,
112
128
  Image,
113
129
  ImageDown,
130
+ Inbox,
131
+ Kanban,
114
132
  Lock,
115
133
  PenTool,
116
134
  ShieldCheck,
135
+ Tag,
117
136
  Tags,
137
+ Ticket,
118
138
  Users,
119
139
  type LucideIcon,
120
140
  } from 'lucide-react'
@@ -126,6 +146,7 @@ const ICON_MAP: Record<string, LucideIcon> = {
126
146
  'file-text': FileText,
127
147
  'folder-tree': FolderTree,
128
148
  tags: Tags,
149
+ ticket: Ticket,
129
150
  }
130
151
 
131
152
  interface AdminLayoutProps {
@@ -161,6 +182,16 @@ export function AdminLayout({
161
182
  ],
162
183
  }
163
184
 
185
+ const supportGroup: SidebarNavGroup = {
186
+ label: 'Support',
187
+ items: [
188
+ { label: 'Tickets', href: '/admin/tickets', icon: Inbox },
189
+ { label: 'Board', href: '/admin/tickets/board', icon: Kanban },
190
+ { label: 'Departments', href: '/admin/departments', icon: Building2 },
191
+ { label: 'Ticket Tags', href: '/admin/ticket-tags', icon: Tag },
192
+ ],
193
+ }
194
+
164
195
  const systemGroup: SidebarNavGroup = {
165
196
  label: 'System',
166
197
  items: [
@@ -176,6 +207,7 @@ export function AdminLayout({
176
207
  const navGroups: SidebarNavGroup[] = [
177
208
  staticBefore,
178
209
  ...dynamicGroups,
210
+ supportGroup,
179
211
  systemGroup,
180
212
  ]
181
213
 
@@ -1016,6 +1048,207 @@ export default async function ActivityPage({ params }: { params: Promise<{ local
1016
1048
  )
1017
1049
  })
1018
1050
  }
1051
+ `),await n(a(e,`app/[locale]/(shell)/admin/tickets/page.tsx`),`import { Ticket, Department, TicketTag } from '@murumets-ee/ticketing'
1052
+ import { headers } from 'next/headers'
1053
+ import { setRequestLocale } from 'next-intl/server'
1054
+ import { withAdminContext } from '@/lib/with-admin-context'
1055
+ import { auth } from '@/lib/auth'
1056
+ import { TicketsInboxClient } from './tickets-inbox-client'
1057
+ import { fetchEntityList } from '@murumets-ee/admin-ui/server'
1058
+ import type {
1059
+ TicketData,
1060
+ DepartmentData,
1061
+ TagData,
1062
+ } from '@murumets-ee/ticketing-ui'
1063
+
1064
+ interface TicketsPageProps {
1065
+ params: Promise<{ locale: string }>
1066
+ }
1067
+
1068
+ export default async function TicketsPage({ params }: TicketsPageProps) {
1069
+ const { locale } = await params
1070
+ setRequestLocale(locale)
1071
+
1072
+ const h = await headers()
1073
+ const session = await auth.api.getSession({ headers: h })
1074
+ const currentUserId = session?.user?.id ?? ''
1075
+
1076
+ return withAdminContext(locale, async () => {
1077
+ const ticketData = await fetchEntityList(Ticket, {
1078
+ sortField: 'lastReplyAt',
1079
+ sortDirection: 'desc',
1080
+ limit: 50,
1081
+ })
1082
+
1083
+ const departmentData = await fetchEntityList(Department, {
1084
+ sortField: 'name',
1085
+ sortDirection: 'asc',
1086
+ limit: 100,
1087
+ })
1088
+
1089
+ const tagData = await fetchEntityList(TicketTag, {
1090
+ sortField: 'name',
1091
+ sortDirection: 'asc',
1092
+ limit: 100,
1093
+ })
1094
+
1095
+ return (
1096
+ <TicketsInboxClient
1097
+ initialTickets={ticketData.items as unknown as TicketData[]}
1098
+ initialTotal={ticketData.total}
1099
+ initialDepartments={departmentData.items as unknown as DepartmentData[]}
1100
+ initialTags={tagData.items as unknown as TagData[]}
1101
+ currentUserId={currentUserId}
1102
+ locale={locale}
1103
+ />
1104
+ )
1105
+ })
1106
+ }
1107
+ `),await n(a(e,`app/[locale]/(shell)/admin/tickets/tickets-inbox-client.tsx`),`'use client'
1108
+
1109
+ import { QueryProvider } from '@murumets-ee/admin-ui'
1110
+ import { InboxProvider, TicketInbox } from '@murumets-ee/ticketing-ui/inbox'
1111
+ import type {
1112
+ TicketData,
1113
+ DepartmentData,
1114
+ TagData,
1115
+ } from '@murumets-ee/ticketing-ui'
1116
+
1117
+ interface TicketsInboxClientProps {
1118
+ initialTickets: TicketData[]
1119
+ initialTotal: number
1120
+ initialDepartments: DepartmentData[]
1121
+ initialTags: TagData[]
1122
+ currentUserId: string
1123
+ locale: string
1124
+ }
1125
+
1126
+ export function TicketsInboxClient({
1127
+ initialTickets,
1128
+ initialTotal,
1129
+ initialDepartments,
1130
+ initialTags,
1131
+ currentUserId,
1132
+ }: TicketsInboxClientProps) {
1133
+ return (
1134
+ <QueryProvider>
1135
+ <InboxProvider
1136
+ apiBasePath="/api/admin"
1137
+ initialTickets={initialTickets}
1138
+ initialTotal={initialTotal}
1139
+ initialDepartments={initialDepartments}
1140
+ initialTags={initialTags}
1141
+ currentUserId={currentUserId}
1142
+ >
1143
+ <div className="h-[calc(100vh-3.5rem)]">
1144
+ <TicketInbox currentUserId={currentUserId} />
1145
+ </div>
1146
+ </InboxProvider>
1147
+ </QueryProvider>
1148
+ )
1149
+ }
1150
+ `),await n(a(e,`app/[locale]/(shell)/admin/tickets/board/page.tsx`),`import { Ticket, Department } from '@murumets-ee/ticketing'
1151
+ import { setRequestLocale } from 'next-intl/server'
1152
+ import { withAdminContext } from '@/lib/with-admin-context'
1153
+ import { fetchEntityList } from '@murumets-ee/admin-ui/server'
1154
+ import { TicketBoardClient } from './ticket-board-client'
1155
+ import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
1156
+
1157
+ interface TicketBoardPageProps {
1158
+ params: Promise<{ locale: string }>
1159
+ }
1160
+
1161
+ export default async function TicketBoardPage({ params }: TicketBoardPageProps) {
1162
+ const { locale } = await params
1163
+ setRequestLocale(locale)
1164
+
1165
+ return withAdminContext(locale, async () => {
1166
+ const ticketData = await fetchEntityList(Ticket, {
1167
+ sortField: 'lastReplyAt',
1168
+ sortDirection: 'desc',
1169
+ limit: 200,
1170
+ })
1171
+
1172
+ const departmentData = await fetchEntityList(Department, {
1173
+ sortField: 'name',
1174
+ sortDirection: 'asc',
1175
+ limit: 100,
1176
+ })
1177
+
1178
+ return (
1179
+ <div className="p-6 h-[calc(100vh-3.5rem)]">
1180
+ <div className="mb-4">
1181
+ <h1 className="text-2xl font-bold">Ticket Board</h1>
1182
+ <p className="text-sm text-muted-foreground">
1183
+ Kanban view of tickets by status
1184
+ </p>
1185
+ </div>
1186
+ <TicketBoardClient
1187
+ initialTickets={ticketData.items as unknown as TicketData[]}
1188
+ initialDepartments={departmentData.items as unknown as DepartmentData[]}
1189
+ locale={locale}
1190
+ />
1191
+ </div>
1192
+ )
1193
+ })
1194
+ }
1195
+ `),await n(a(e,`app/[locale]/(shell)/admin/tickets/board/ticket-board-client.tsx`),`'use client'
1196
+
1197
+ import { useMemo } from 'react'
1198
+ import { TicketBoard } from '@murumets-ee/ticketing-ui/board'
1199
+ import type { TicketData, DepartmentData } from '@murumets-ee/ticketing-ui'
1200
+ import { useRouter } from 'next/navigation'
1201
+
1202
+ interface TicketBoardClientProps {
1203
+ initialTickets: TicketData[]
1204
+ initialDepartments: DepartmentData[]
1205
+ locale: string
1206
+ }
1207
+
1208
+ export function TicketBoardClient({
1209
+ initialTickets,
1210
+ initialDepartments,
1211
+ locale,
1212
+ }: TicketBoardClientProps) {
1213
+ const router = useRouter()
1214
+ const departmentMap = useMemo(
1215
+ () => new Map(initialDepartments.map((d) => [d.id, d])),
1216
+ [initialDepartments],
1217
+ )
1218
+
1219
+ return (
1220
+ <TicketBoard
1221
+ tickets={initialTickets}
1222
+ departments={departmentMap}
1223
+ onTicketSelect={(ticket) => {
1224
+ router.push(\`/\${locale}/admin/tickets?selected=\${ticket.id}\`)
1225
+ }}
1226
+ className="h-[calc(100%-4rem)]"
1227
+ />
1228
+ )
1229
+ }
1230
+ `),await n(a(e,`app/api/ticketing/inbound/route.ts`),`/**
1231
+ * Resend inbound email webhook endpoint.
1232
+ *
1233
+ * Public endpoint (no session auth) — protected by webhook signature
1234
+ * verification (HMAC-SHA256). Lives outside the admin API handler
1235
+ * because the admin handler requires authentication.
1236
+ */
1237
+
1238
+ import { getToolkitApp } from '@/lib/app'
1239
+
1240
+ await getToolkitApp()
1241
+
1242
+ export async function POST(req: Request): Promise<Response> {
1243
+ const { handleInboundWebhook } = await import('@murumets-ee/ticketing')
1244
+ const { QueueClient } = await import('@murumets-ee/queue/client')
1245
+ const { getApp } = await import('@murumets-ee/core')
1246
+
1247
+ const db = getApp().db.readWrite
1248
+ const queueClient = new QueueClient({ db })
1249
+
1250
+ return handleInboundWebhook(req, (type, payload) => queueClient.enqueue(type, payload))
1251
+ }
1019
1252
  `),await n(a(e,`app/api/admin/[...path]/route.ts`),`import { createAdminApiHandler } from '@murumets-ee/admin-ui/server'
1020
1253
  import { buildPermissionChecker, buildResourceCatalog } from '@murumets-ee/auth'
1021
1254
  import { permissionRoutes } from '@murumets-ee/auth/admin'
@@ -1035,6 +1268,7 @@ import { createSettingsClient } from '@murumets-ee/settings'
1035
1268
  import { settingsRoutes } from '@murumets-ee/settings/admin'
1036
1269
  import { storageRoutes } from '@murumets-ee/storage/admin'
1037
1270
  import { taxonomyRoutes } from '@murumets-ee/taxonomy/admin'
1271
+ import { ticketingRoutes } from '@murumets-ee/ticketing/admin'
1038
1272
  import {
1039
1273
  allEntities,
1040
1274
  crudEntities,
@@ -1057,6 +1291,7 @@ const routes = [
1057
1291
  storageRoutes(),
1058
1292
  settingsRoutes(siteSettings),
1059
1293
  taxonomyRoutes(taxonomyVocabularies),
1294
+ ticketingRoutes(),
1060
1295
  logRoutes(() => new AuditLogClient(getApp().db.readWrite)),
1061
1296
  permissionRoutes({
1062
1297
  getStatements: () => buildResourceCatalog(allEntities, pluginResources),