@escalated-dev/escalated 0.7.1 → 0.9.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 (117) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +380 -357
  3. package/package.json +85 -81
  4. package/src/components/ActiveChatsPanel.vue +246 -246
  5. package/src/components/AdminDashboard.stories.js +1423 -1423
  6. package/src/components/AttachmentList.vue +65 -65
  7. package/src/components/ChatActionBar.vue +226 -226
  8. package/src/components/ChatBubble.vue +162 -162
  9. package/src/components/ChatComposer.vue +308 -308
  10. package/src/components/ChatQueue.vue +222 -222
  11. package/src/components/ChatThread.vue +197 -197
  12. package/src/components/CustomTicketActions.stories.js +71 -0
  13. package/src/components/CustomTicketActions.vue +101 -0
  14. package/src/components/EscalatedLayout.vue +619 -598
  15. package/src/components/FollowButton.vue +20 -1
  16. package/src/components/MentionDropdown.vue +154 -154
  17. package/src/components/MetricComparison.vue +89 -89
  18. package/src/components/PercentileChart.vue +67 -67
  19. package/src/components/PeriodSelector.vue +98 -98
  20. package/src/components/PresenceIndicator.vue +228 -213
  21. package/src/components/ReplyComposer.vue +336 -336
  22. package/src/components/ReplyThread.vue +153 -153
  23. package/src/components/ReportChart.vue +395 -395
  24. package/src/components/ReportExportButton.vue +108 -108
  25. package/src/components/SaveViewDialog.vue +172 -172
  26. package/src/components/SavedViewSidebar.vue +208 -208
  27. package/src/components/SkillTagManager.vue +29 -22
  28. package/src/components/SnoozeButton.vue +247 -247
  29. package/src/components/TicketFilters.vue +270 -270
  30. package/src/components/TicketList.vue +655 -655
  31. package/src/components/TicketSidebar.vue +4 -0
  32. package/src/components/TicketSplitDialog.vue +98 -98
  33. package/src/components/TicketSubjects.stories.js +63 -0
  34. package/src/components/TicketSubjects.vue +107 -0
  35. package/src/components/TwoFactorSetup.vue +1 -37
  36. package/src/components/VariableTokenInput.vue +111 -111
  37. package/src/components/WorkflowActionConfig.vue +287 -287
  38. package/src/components/WorkflowActionList.vue +350 -350
  39. package/src/components/WorkflowConditionBuilder.vue +265 -265
  40. package/src/components/WorkflowTriggerSelector.vue +125 -125
  41. package/src/components/admin/newsletters/AnalyticsTiles.stories.js +14 -0
  42. package/src/components/admin/newsletters/AnalyticsTiles.vue +55 -0
  43. package/src/components/admin/newsletters/DeliveriesTable.stories.js +28 -0
  44. package/src/components/admin/newsletters/DeliveriesTable.vue +104 -0
  45. package/src/components/admin/newsletters/DynamicFilterBuilder.stories.js +11 -0
  46. package/src/components/admin/newsletters/DynamicFilterBuilder.vue +53 -0
  47. package/src/components/admin/newsletters/ListMemberTable.stories.js +12 -0
  48. package/src/components/admin/newsletters/ListMemberTable.vue +53 -0
  49. package/src/components/admin/newsletters/MarkdownEditor.stories.js +6 -0
  50. package/src/components/admin/newsletters/MarkdownEditor.vue +53 -0
  51. package/src/components/admin/newsletters/MergeFieldDropdown.stories.js +6 -0
  52. package/src/components/admin/newsletters/MergeFieldDropdown.vue +84 -0
  53. package/src/components/admin/newsletters/PreviewIframe.stories.js +7 -0
  54. package/src/components/admin/newsletters/PreviewIframe.vue +40 -0
  55. package/src/components/admin/newsletters/SummaryTiles.stories.js +8 -0
  56. package/src/components/admin/newsletters/SummaryTiles.vue +63 -0
  57. package/src/composables/useChat.js +187 -187
  58. package/src/composables/useI18n.js +30 -8
  59. package/src/composables/useMentions.js +134 -134
  60. package/src/composables/usePermissions.js +31 -0
  61. package/src/composables/useRealtime.js +182 -182
  62. package/src/index.js +85 -84
  63. package/src/locales/README.md +13 -0
  64. package/src/locales/ar.json +727 -727
  65. package/src/locales/de.json +690 -690
  66. package/src/locales/en.json +889 -729
  67. package/src/locales/es.json +691 -691
  68. package/src/locales/fr.json +691 -691
  69. package/src/locales/index.js +16 -16
  70. package/src/locales/it.json +727 -727
  71. package/src/locales/ja.json +727 -727
  72. package/src/locales/ko.json +727 -727
  73. package/src/locales/nl.json +727 -727
  74. package/src/locales/pl.json +727 -727
  75. package/src/locales/pt-BR.json +727 -727
  76. package/src/locales/ru.json +727 -727
  77. package/src/locales/tr.json +727 -727
  78. package/src/locales/zh-CN.json +727 -727
  79. package/src/pages/Admin/Newsletters/Compose.vue +192 -0
  80. package/src/pages/Admin/Newsletters/Edit.vue +24 -0
  81. package/src/pages/Admin/Newsletters/Index.vue +141 -0
  82. package/src/pages/Admin/Newsletters/Lists/Create.vue +64 -0
  83. package/src/pages/Admin/Newsletters/Lists/Index.vue +59 -0
  84. package/src/pages/Admin/Newsletters/Lists/Show.vue +53 -0
  85. package/src/pages/Admin/Newsletters/Settings.vue +85 -0
  86. package/src/pages/Admin/Newsletters/Show.vue +140 -0
  87. package/src/pages/Admin/Newsletters/Templates/Create.vue +11 -0
  88. package/src/pages/Admin/Newsletters/Templates/Index.vue +54 -0
  89. package/src/pages/Admin/Newsletters/Templates/Show.vue +65 -0
  90. package/src/pages/Admin/Newsletters/usePreview.js +44 -0
  91. package/src/pages/Admin/Reports/AgentDetail.vue +132 -132
  92. package/src/pages/Admin/Reports/AgentRanking.vue +246 -246
  93. package/src/pages/Admin/Reports/Cohorts.vue +180 -180
  94. package/src/pages/Admin/Reports/Comparison.vue +141 -141
  95. package/src/pages/Admin/Reports/Dashboard.vue +226 -226
  96. package/src/pages/Admin/Reports/ResolutionTimes.vue +164 -164
  97. package/src/pages/Admin/Reports/ResponseTimes.vue +164 -164
  98. package/src/pages/Admin/Reports/SlaTrends.vue +183 -183
  99. package/src/pages/Admin/Settings/SsoSettings.vue +157 -4
  100. package/src/pages/Admin/Settings/TwoFactor.vue +32 -6
  101. package/src/pages/Admin/Settings.vue +1008 -1008
  102. package/src/pages/Admin/Skills/Form.vue +164 -4
  103. package/src/pages/Admin/Skills/Index.vue +14 -3
  104. package/src/pages/Admin/Tickets/Index.vue +126 -126
  105. package/src/pages/Admin/Tickets/Show.vue +254 -249
  106. package/src/pages/Admin/Users/Index.vue +145 -0
  107. package/src/pages/Admin/Workflows/Builder.vue +207 -207
  108. package/src/pages/Admin/Workflows/Index.vue +235 -235
  109. package/src/pages/Admin/Workflows/Logs.vue +286 -286
  110. package/src/pages/Agent/TicketIndex.vue +112 -112
  111. package/src/pages/Agent/TicketShow.vue +547 -494
  112. package/src/pages/Customer/Index.vue +26 -1
  113. package/src/pages/Customer/KnowledgeBase/Article.vue +129 -129
  114. package/src/plugin.js +175 -175
  115. package/src/utils/mentionHighlight.js +24 -24
  116. package/src/widget/EscalatedWidget.vue +804 -804
  117. package/src/widget/index.js +254 -254
@@ -1,598 +1,619 @@
1
- <script setup>
2
- import { computed, inject, provide } from 'vue';
3
- import { usePage, Link } from '@inertiajs/vue3';
4
- import { usePluginExtensions } from '../composables/usePluginExtensions';
5
- import ActiveChatsPanel from './ActiveChatsPanel.vue';
6
-
7
- defineProps({
8
- title: { type: String, default: 'Support' },
9
- });
10
-
11
- const page = usePage();
12
- const hostLayout = inject('escalated-layout', null);
13
- const panelConfig = inject('escalated-panel', { appName: 'Escalated', logo: null, mode: 'dark' });
14
-
15
- const isAgent = computed(() => page.props.escalated?.is_agent);
16
- const isAdmin = computed(() => page.props.escalated?.is_admin);
17
- const prefix = computed(() => {
18
- const p = page.props.escalated?.prefix || 'support';
19
- return p.startsWith('/') ? p : `/${p}`;
20
- });
21
-
22
- const currentUrl = computed(() => page.url);
23
- const isAdminSection = computed(() => currentUrl.value?.includes('/admin'));
24
- const isAgentSection = computed(() => currentUrl.value?.includes('/agent'));
25
- const isPanel = computed(() => isAdminSection.value || isAgentSection.value);
26
- const isDark = computed(() => isPanel.value && panelConfig.mode !== 'light');
27
- const showPoweredBy = computed(() => page.props.escalated?.show_powered_by !== false);
28
- const kbEnabled = computed(() => page.props.escalated?.knowledge_base_enabled !== false);
29
- const kbPublic = computed(() => page.props.escalated?.knowledge_base_public !== false);
30
- const chatEnabled = computed(() => page.props.escalated?.chat_enabled === true);
31
- const activeChats = computed(() => page.props.escalated?.active_chats || []);
32
- const agentId = computed(() => page.props.auth?.user?.id);
33
-
34
- provide('esc-dark', isDark);
35
-
36
- const { menuItems: pluginMenuItems } = usePluginExtensions();
37
-
38
- const adminLinks = computed(() => {
39
- const p = prefix.value;
40
- const coreLinks = [
41
- {
42
- href: `${p}/admin/reports`,
43
- label: 'Reports',
44
- icon: 'M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z',
45
- position: 10,
46
- },
47
- {
48
- href: `${p}/admin/tickets`,
49
- label: 'Tickets',
50
- icon: 'M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z',
51
- position: 20,
52
- },
53
- {
54
- href: `${p}/admin/departments`,
55
- label: 'Departments',
56
- icon: 'M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z',
57
- position: 30,
58
- },
59
- {
60
- href: `${p}/admin/sla-policies`,
61
- label: 'SLA Policies',
62
- icon: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z',
63
- position: 40,
64
- },
65
- {
66
- href: `${p}/admin/kb-articles`,
67
- label: 'Knowledge Base',
68
- icon: 'M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25',
69
- position: 45,
70
- },
71
- {
72
- href: `${p}/admin/escalation-rules`,
73
- label: 'Escalation Rules',
74
- icon: 'M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12',
75
- position: 50,
76
- },
77
- {
78
- href: `${p}/admin/workflows`,
79
- label: 'Workflows',
80
- icon: 'M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z M15 12a3 3 0 11-6 0 3 3 0 016 0z',
81
- position: 53,
82
- },
83
- {
84
- href: `${p}/admin/webhooks`,
85
- label: 'Webhooks',
86
- icon: 'M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244',
87
- position: 54,
88
- },
89
- {
90
- href: `${p}/admin/tags`,
91
- label: 'Tags',
92
- icon: 'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z M6 6h.008v.008H6V6z',
93
- position: 60,
94
- },
95
- {
96
- href: `${p}/admin/custom-fields`,
97
- label: 'Custom Fields',
98
- icon: 'M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z',
99
- position: 62,
100
- },
101
- {
102
- href: `${p}/admin/statuses`,
103
- label: 'Statuses',
104
- icon: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
105
- position: 64,
106
- },
107
- {
108
- href: `${p}/admin/canned-responses`,
109
- label: 'Canned Responses',
110
- icon: 'M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z',
111
- position: 70,
112
- },
113
- {
114
- href: `${p}/admin/macros`,
115
- label: 'Macros',
116
- icon: 'M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z',
117
- position: 80,
118
- },
119
- {
120
- href: `${p}/admin/skills`,
121
- label: 'Skills',
122
- icon: 'M4.26 10.147a60.436 60.436 0 00-.491 6.347A48.627 48.627 0 0112 20.904a48.627 48.627 0 018.232-4.41 60.46 60.46 0 00-.491-6.347m-15.482 0a50.57 50.57 0 00-2.658-.813A59.905 59.905 0 0112 3.493a59.902 59.902 0 0110.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.697 50.697 0 0112 13.489a50.702 50.702 0 017.74-3.342',
123
- position: 82,
124
- },
125
- {
126
- href: `${p}/admin/roles`,
127
- label: 'Roles',
128
- icon: 'M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z',
129
- position: 84,
130
- },
131
- {
132
- href: `${p}/admin/capacity`,
133
- label: 'Capacity',
134
- icon: 'M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5',
135
- position: 85,
136
- },
137
- {
138
- href: `${p}/admin/business-hours`,
139
- label: 'Business Hours',
140
- icon: 'M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5',
141
- position: 86,
142
- },
143
- {
144
- href: `${p}/admin/audit-log`,
145
- label: 'Audit Log',
146
- icon: 'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z',
147
- position: 88,
148
- },
149
- {
150
- href: `${p}/admin/api-tokens`,
151
- label: 'API Tokens',
152
- icon: 'M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z',
153
- position: 90,
154
- },
155
- {
156
- href: `${p}/admin/custom-objects`,
157
- label: 'Custom Objects',
158
- icon: 'M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125',
159
- position: 92,
160
- },
161
- {
162
- href: `${p}/admin/plugins`,
163
- label: 'Plugins',
164
- icon: 'M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 01-.657.643 48.39 48.39 0 01-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 01-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 00-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 01-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 00.657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 005.427-.63 48.05 48.05 0 00.582-4.717.532.532 0 00-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 00.658-.663 48.422 48.422 0 00-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 01-.61-.58v0z',
165
- position: 95,
166
- },
167
- {
168
- href: `${p}/admin/import`,
169
- label: 'Import',
170
- icon: 'M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5',
171
- position: 97,
172
- },
173
- {
174
- href: `${p}/admin/settings`,
175
- label: 'Settings',
176
- icon: 'M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.204-.107-.397.165-.71.505-.781.93l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894zM15 12a3 3 0 11-6 0 3 3 0 016 0z',
177
- position: 100,
178
- },
179
- ];
180
-
181
- // Merge core admin links with plugin-provided menu items
182
- const pluginLinks = pluginMenuItems.value.map((item) => ({
183
- href: item.route,
184
- label: item.label,
185
- icon: item.icon,
186
- position: item.position || 100,
187
- badge: item.badge || null,
188
- submenu: item.submenu || [],
189
- }));
190
-
191
- return [...coreLinks, ...pluginLinks].sort((a, b) => (a.position || 100) - (b.position || 100));
192
- });
193
-
194
- const agentLinks = computed(() => [
195
- {
196
- href: `${prefix.value}/agent`,
197
- label: 'Dashboard',
198
- icon: 'M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25',
199
- },
200
- {
201
- href: `${prefix.value}/agent/tickets`,
202
- label: 'Tickets',
203
- icon: 'M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z',
204
- },
205
- ]);
206
-
207
- const userName = computed(() => page.props.auth?.user?.name || 'User');
208
- const userInitial = computed(() => userName.value.charAt(0).toUpperCase());
209
-
210
- function isActive(href) {
211
- if (!currentUrl.value) return false;
212
- return currentUrl.value === href || currentUrl.value.startsWith(href + '/');
213
- }
214
- </script>
215
-
216
- <template>
217
- <!-- MODE 1: Admin — dark sidebar layout -->
218
- <div
219
- v-if="isAdminSection"
220
- class="flex min-h-screen bg-[var(--esc-panel-bg)]"
221
- :style="{ colorScheme: panelConfig.mode === 'light' ? 'light' : 'dark' }"
222
- >
223
- <!-- Sidebar -->
224
- <aside
225
- class="fixed inset-y-0 left-0 z-30 flex w-64 flex-col border-r border-[var(--esc-panel-border)] bg-[var(--esc-panel-sidebar-bg)]"
226
- >
227
- <!-- Logo -->
228
- <div class="flex h-16 items-center gap-3 px-5">
229
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-[var(--esc-panel-border-input)]">
230
- <img
231
- v-if="typeof panelConfig.logo === 'string'"
232
- :src="panelConfig.logo"
233
- class="h-5 w-5"
234
- alt="Escalated logo"
235
- />
236
- <component :is="panelConfig.logo" v-else-if="panelConfig.logo" class="h-5 w-5" />
237
- <svg
238
- v-else
239
- class="h-5 w-5"
240
- viewBox="0 0 24 24"
241
- fill="none"
242
- stroke-width="2.5"
243
- stroke-linecap="round"
244
- stroke-linejoin="round"
245
- >
246
- <defs>
247
- <linearGradient id="esc-rainbow-admin" x1="0%" y1="0%" x2="100%" y2="100%">
248
- <stop offset="0%" stop-color="#f97316" />
249
- <stop offset="30%" stop-color="#eab308" />
250
- <stop offset="50%" stop-color="#22c55e" />
251
- <stop offset="70%" stop-color="#3b82f6" />
252
- <stop offset="100%" stop-color="#8b5cf6" />
253
- </linearGradient>
254
- </defs>
255
- <g transform="translate(12,12) scale(1.35) translate(-12,-12)">
256
- <polyline points="17 11 12 6 7 11" stroke="url(#esc-rainbow-admin)" />
257
- <polyline points="17 18 12 13 7 18" stroke="url(#esc-rainbow-admin)" />
258
- </g>
259
- </svg>
260
- </div>
261
- <div>
262
- <span class="text-sm font-bold text-[var(--esc-panel-text)] tracking-wide">{{
263
- panelConfig.appName
264
- }}</span>
265
- <span
266
- class="ml-1.5 rounded bg-[var(--esc-panel-active)] px-1.5 py-0.5 text-[10px] font-semibold text-[var(--esc-panel-text-muted)]"
267
- >ADMIN</span
268
- >
269
- </div>
270
- </div>
271
-
272
- <!-- Nav -->
273
- <nav role="navigation" aria-label="Admin navigation" class="mt-1 flex-1 space-y-0.5 overflow-y-auto px-3">
274
- <Link
275
- v-for="link in adminLinks"
276
- :key="link.href"
277
- :href="link.href"
278
- :aria-current="isActive(link.href) ? 'page' : undefined"
279
- :class="[
280
- 'group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] font-medium transition-all',
281
- isActive(link.href)
282
- ? 'bg-[var(--esc-panel-active)] text-[var(--esc-panel-text)]'
283
- : 'text-[var(--esc-panel-text-muted)] hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]',
284
- ]"
285
- >
286
- <svg
287
- :class="[
288
- 'h-[18px] w-[18px] shrink-0',
289
- isActive(link.href)
290
- ? 'text-[var(--esc-panel-text)]'
291
- : 'text-[var(--esc-panel-text-muted)] group-hover:text-[var(--esc-panel-text-tertiary)]',
292
- ]"
293
- fill="none"
294
- stroke="currentColor"
295
- stroke-width="1.5"
296
- viewBox="0 0 24 24"
297
- >
298
- <path stroke-linecap="round" stroke-linejoin="round" :d="link.icon" />
299
- </svg>
300
- {{ link.label }}
301
- <span
302
- v-if="link.badge"
303
- class="ml-auto inline-flex h-5 min-w-[20px] items-center justify-center rounded-full bg-[var(--esc-panel-accent)]/20 px-1.5 text-[10px] font-semibold text-[var(--esc-panel-accent)]"
304
- >
305
- {{ link.badge }}
306
- </span>
307
- </Link>
308
- </nav>
309
-
310
- <!-- Bottom section -->
311
- <div class="border-t border-[var(--esc-panel-border)] p-3">
312
- <Link
313
- v-if="isAgent"
314
- :href="`${prefix}/agent`"
315
- class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]"
316
- >
317
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
318
- <path
319
- stroke-linecap="round"
320
- stroke-linejoin="round"
321
- d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
322
- />
323
- </svg>
324
- Agent Panel
325
- </Link>
326
- <Link
327
- :href="prefix"
328
- class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]"
329
- >
330
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
331
- <path
332
- stroke-linecap="round"
333
- stroke-linejoin="round"
334
- d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
335
- />
336
- </svg>
337
- Back to App
338
- </Link>
339
-
340
- <!-- User -->
341
- <div class="mt-2 flex items-center gap-3 rounded-lg bg-[var(--esc-panel-hover)] px-3 py-2.5">
342
- <div
343
- class="flex h-7 w-7 items-center justify-center rounded-md bg-[var(--esc-panel-active)] text-xs font-semibold text-[var(--esc-panel-text-tertiary)]"
344
- >
345
- {{ userInitial }}
346
- </div>
347
- <span class="text-sm text-[var(--esc-panel-text-tertiary)]">{{ userName }}</span>
348
- </div>
349
- </div>
350
-
351
- <!-- Powered by -->
352
- <div v-if="showPoweredBy" class="px-4 pb-3">
353
- <a
354
- href="https://escalated.dev"
355
- target="_blank"
356
- rel="noopener noreferrer"
357
- class="flex items-center justify-center gap-1.5 rounded-lg px-2 py-1.5 text-[11px] text-[var(--esc-panel-text-muted)] opacity-60 transition-opacity hover:opacity-100"
358
- >
359
- <img
360
- :src="
361
- isDark
362
- ? 'https://escalated.dev/brand/logo-icon-dark.svg'
363
- : 'https://escalated.dev/brand/logo-icon-white.svg'
364
- "
365
- alt="Escalated logo"
366
- class="h-3.5 w-3.5"
367
- />
368
- Powered by Escalated
369
- </a>
370
- </div>
371
- </aside>
372
-
373
- <!-- Main content -->
374
- <div class="flex flex-1 flex-col pl-64">
375
- <!-- Top bar -->
376
- <header
377
- role="banner"
378
- class="sticky top-0 z-20 flex h-14 items-center border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-topbar-bg)] px-6 backdrop-blur-xl"
379
- >
380
- <h1 class="text-sm font-semibold text-[var(--esc-panel-text)]">{{ title }}</h1>
381
- </header>
382
-
383
- <!-- Page content -->
384
- <main role="main" class="flex-1 p-6">
385
- <slot />
386
- </main>
387
- </div>
388
- </div>
389
-
390
- <!-- MODE 2: Agent — dark top-nav layout -->
391
- <div
392
- v-else-if="isAgentSection"
393
- class="min-h-screen bg-[var(--esc-panel-bg)]"
394
- :style="{ colorScheme: panelConfig.mode === 'light' ? 'light' : 'dark' }"
395
- >
396
- <!-- Top nav -->
397
- <nav
398
- class="sticky top-0 z-30 border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-sidebar-bg)] backdrop-blur-xl"
399
- >
400
- <div class="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
401
- <!-- Left: branding -->
402
- <div class="flex items-center gap-3">
403
- <div class="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--esc-panel-border-input)]">
404
- <img
405
- v-if="typeof panelConfig.logo === 'string'"
406
- :src="panelConfig.logo"
407
- class="h-4 w-4"
408
- alt="Escalated logo"
409
- />
410
- <component :is="panelConfig.logo" v-else-if="panelConfig.logo" class="h-4 w-4" />
411
- <svg
412
- v-else
413
- class="h-4 w-4"
414
- viewBox="0 0 24 24"
415
- fill="none"
416
- stroke-width="2.5"
417
- stroke-linecap="round"
418
- stroke-linejoin="round"
419
- >
420
- <defs>
421
- <linearGradient id="esc-rainbow-agent" x1="0%" y1="0%" x2="100%" y2="100%">
422
- <stop offset="0%" stop-color="#f97316" />
423
- <stop offset="30%" stop-color="#eab308" />
424
- <stop offset="50%" stop-color="#22c55e" />
425
- <stop offset="70%" stop-color="#3b82f6" />
426
- <stop offset="100%" stop-color="#8b5cf6" />
427
- </linearGradient>
428
- </defs>
429
- <g transform="translate(12,12) scale(1.35) translate(-12,-12)">
430
- <polyline points="17 11 12 6 7 11" stroke="url(#esc-rainbow-agent)" />
431
- <polyline points="17 18 12 13 7 18" stroke="url(#esc-rainbow-agent)" />
432
- </g>
433
- </svg>
434
- </div>
435
- <span class="text-sm font-bold text-[var(--esc-panel-text)] tracking-wide">{{
436
- panelConfig.appName
437
- }}</span>
438
- </div>
439
-
440
- <!-- Center: nav links -->
441
- <div class="flex items-center gap-1">
442
- <Link
443
- v-for="link in agentLinks"
444
- :key="link.href"
445
- :href="link.href"
446
- :aria-current="isActive(link.href) ? 'page' : undefined"
447
- :class="[
448
- 'flex items-center gap-2 rounded-lg px-3.5 py-1.5 text-[13px] font-medium transition-all',
449
- isActive(link.href)
450
- ? 'bg-[var(--esc-panel-active)] text-[var(--esc-panel-text)]'
451
- : 'text-[var(--esc-panel-text-muted)] hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]',
452
- ]"
453
- >
454
- <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
455
- <path stroke-linecap="round" stroke-linejoin="round" :d="link.icon" />
456
- </svg>
457
- {{ link.label }}
458
- </Link>
459
- </div>
460
-
461
- <!-- Right: user + links -->
462
- <div class="flex items-center gap-3">
463
- <Link
464
- v-if="isAdmin"
465
- :href="`${prefix}/admin/reports`"
466
- class="text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:text-[var(--esc-panel-text)]"
467
- >
468
- Admin
469
- </Link>
470
- <Link
471
- :href="prefix"
472
- class="text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:text-[var(--esc-panel-text)]"
473
- >
474
- Back to App
475
- </Link>
476
- <div class="ml-1 h-5 w-px bg-[var(--esc-panel-active)]"></div>
477
- <div class="flex items-center gap-2">
478
- <div
479
- class="flex h-7 w-7 items-center justify-center rounded-md bg-[var(--esc-panel-active)] text-[10px] font-semibold text-[var(--esc-panel-text-tertiary)]"
480
- >
481
- {{ userInitial }}
482
- </div>
483
- <span class="text-[13px] text-[var(--esc-panel-text-tertiary)]">{{ userName }}</span>
484
- </div>
485
- </div>
486
- </div>
487
- </nav>
488
-
489
- <!-- Page content -->
490
- <main class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
491
- <slot />
492
- </main>
493
-
494
- <!-- Powered by -->
495
- <footer v-if="showPoweredBy" class="border-t border-[var(--esc-panel-border)] py-3 text-center">
496
- <a
497
- href="https://escalated.dev"
498
- target="_blank"
499
- rel="noopener noreferrer"
500
- class="inline-flex items-center gap-1.5 text-[11px] text-[var(--esc-panel-text-muted)] opacity-60 transition-opacity hover:opacity-100"
501
- >
502
- <img
503
- :src="
504
- isDark
505
- ? 'https://escalated.dev/brand/logo-icon-dark.svg'
506
- : 'https://escalated.dev/brand/logo-icon-white.svg'
507
- "
508
- alt=""
509
- class="h-3.5 w-3.5"
510
- />
511
- Powered by Escalated
512
- </a>
513
- </footer>
514
-
515
- <!-- Active Chats Panel (agent layout only, when chat enabled) -->
516
- <ActiveChatsPanel v-if="chatEnabled && agentId" :chats="activeChats" :agent-id="agentId" />
517
- </div>
518
-
519
- <!-- MODE 3: Customer pages — use host app layout if provided -->
520
- <component :is="hostLayout" v-else-if="hostLayout">
521
- <template #header>
522
- <div class="flex items-center justify-between">
523
- <h2 class="text-xl font-semibold leading-tight text-gray-800">{{ title }}</h2>
524
- <nav class="flex items-center gap-4 text-sm">
525
- <Link :href="prefix" class="text-gray-600 hover:text-gray-900">My Tickets</Link>
526
- <Link
527
- v-if="kbEnabled && (kbPublic || page.props.auth?.user)"
528
- :href="`${prefix}/kb`"
529
- class="text-gray-600 hover:text-gray-900"
530
- >
531
- Knowledge Base
532
- </Link>
533
- <Link v-if="isAgent" :href="`${prefix}/agent`" class="text-gray-600 hover:text-gray-900">
534
- Agent Panel
535
- </Link>
536
- <Link v-if="isAdmin" :href="`${prefix}/admin/reports`" class="text-gray-600 hover:text-gray-900">
537
- Admin
538
- </Link>
539
- </nav>
540
- </div>
541
- </template>
542
-
543
- <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
544
- <slot />
545
- </div>
546
- </component>
547
-
548
- <!-- MODE 4: Customer standalone fallback (no host layout) -->
549
- <div v-else class="min-h-screen bg-gray-50">
550
- <nav class="border-b border-gray-200 bg-white">
551
- <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
552
- <div class="flex h-14 items-center justify-between">
553
- <div class="flex items-center gap-6">
554
- <span class="text-lg font-bold text-gray-900">{{ title }}</span>
555
- <div class="flex items-center gap-4 text-sm">
556
- <Link :href="prefix" class="text-gray-600 hover:text-gray-900">My Tickets</Link>
557
- <Link
558
- v-if="kbEnabled && (kbPublic || page.props.auth?.user)"
559
- :href="`${prefix}/kb`"
560
- class="text-gray-600 hover:text-gray-900"
561
- >
562
- Knowledge Base
563
- </Link>
564
- <Link v-if="isAgent" :href="`${prefix}/agent`" class="text-gray-600 hover:text-gray-900">
565
- Agent Panel
566
- </Link>
567
- <Link
568
- v-if="isAdmin"
569
- :href="`${prefix}/admin/reports`"
570
- class="text-gray-600 hover:text-gray-900"
571
- >
572
- Admin
573
- </Link>
574
- </div>
575
- </div>
576
- </div>
577
- </div>
578
- </nav>
579
- <main class="flex-1">
580
- <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
581
- <slot />
582
- </div>
583
- </main>
584
-
585
- <!-- Powered by -->
586
- <footer v-if="showPoweredBy" class="border-t border-gray-200 py-3 text-center">
587
- <a
588
- href="https://escalated.dev"
589
- target="_blank"
590
- rel="noopener noreferrer"
591
- class="inline-flex items-center gap-1.5 text-[11px] text-gray-400 transition-colors hover:text-gray-600"
592
- >
593
- <img src="https://escalated.dev/brand/logo-icon-white.svg" alt="Escalated logo" class="h-3.5 w-3.5" />
594
- Powered by Escalated
595
- </a>
596
- </footer>
597
- </div>
598
- </template>
1
+ <script setup>
2
+ import { computed, inject, provide } from 'vue';
3
+ import { usePage, Link } from '@inertiajs/vue3';
4
+ import { usePluginExtensions } from '../composables/usePluginExtensions';
5
+ import { usePermissions } from '../composables/usePermissions';
6
+ import { useI18n } from '../composables/useI18n';
7
+ import ActiveChatsPanel from './ActiveChatsPanel.vue';
8
+
9
+ defineProps({
10
+ title: { type: String, default: 'Support' },
11
+ });
12
+
13
+ const page = usePage();
14
+ const hostLayout = inject('escalated-layout', null);
15
+ const panelConfig = inject('escalated-panel', { appName: 'Escalated', logo: null, mode: 'dark' });
16
+
17
+ const isAgent = computed(() => page.props.escalated?.is_agent);
18
+ const isAdmin = computed(() => page.props.escalated?.is_admin);
19
+ const prefix = computed(() => {
20
+ const p = page.props.escalated?.prefix || 'support';
21
+ return p.startsWith('/') ? p : `/${p}`;
22
+ });
23
+
24
+ const currentUrl = computed(() => page.url);
25
+ const isAdminSection = computed(() => currentUrl.value?.includes('/admin'));
26
+ const isAgentSection = computed(() => currentUrl.value?.includes('/agent'));
27
+ const isPanel = computed(() => isAdminSection.value || isAgentSection.value);
28
+ const isDark = computed(() => isPanel.value && panelConfig.mode !== 'light');
29
+ const showPoweredBy = computed(() => page.props.escalated?.show_powered_by !== false);
30
+ const kbEnabled = computed(() => page.props.escalated?.knowledge_base_enabled !== false);
31
+ const newslettersEnabled = computed(() => page.props.escalated?.features?.newsletters === true);
32
+ const kbPublic = computed(() => page.props.escalated?.knowledge_base_public !== false);
33
+ const chatEnabled = computed(() => page.props.escalated?.chat_enabled === true);
34
+ const activeChats = computed(() => page.props.escalated?.active_chats || []);
35
+ const agentId = computed(() => page.props.auth?.user?.id);
36
+
37
+ provide('esc-dark', isDark);
38
+
39
+ const { menuItems: pluginMenuItems } = usePluginExtensions();
40
+ const { hasPermission } = usePermissions();
41
+ const { t } = useI18n();
42
+
43
+ const adminLinks = computed(() => {
44
+ const p = prefix.value;
45
+ const coreLinks = [
46
+ {
47
+ href: `${p}/admin/reports`,
48
+ label: 'Reports',
49
+ icon: 'M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z',
50
+ position: 10,
51
+ },
52
+ {
53
+ href: `${p}/admin/tickets`,
54
+ label: 'Tickets',
55
+ icon: 'M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z',
56
+ position: 20,
57
+ },
58
+ {
59
+ href: `${p}/admin/departments`,
60
+ label: 'Departments',
61
+ icon: 'M2.25 7.125C2.25 6.504 2.754 6 3.375 6h6c.621 0 1.125.504 1.125 1.125v3.75c0 .621-.504 1.125-1.125 1.125h-6a1.125 1.125 0 01-1.125-1.125v-3.75zM14.25 8.625c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v8.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-8.25zM3.75 16.125c0-.621.504-1.125 1.125-1.125h5.25c.621 0 1.125.504 1.125 1.125v2.25c0 .621-.504 1.125-1.125 1.125h-5.25a1.125 1.125 0 01-1.125-1.125v-2.25z',
62
+ position: 30,
63
+ },
64
+ {
65
+ href: `${p}/admin/sla-policies`,
66
+ label: 'SLA Policies',
67
+ icon: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z',
68
+ position: 40,
69
+ },
70
+ {
71
+ href: `${p}/admin/kb-articles`,
72
+ label: 'Knowledge Base',
73
+ icon: 'M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25',
74
+ position: 45,
75
+ },
76
+ {
77
+ href: `${p}/admin/escalation-rules`,
78
+ label: 'Escalation Rules',
79
+ icon: 'M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12',
80
+ position: 50,
81
+ },
82
+ {
83
+ href: `${p}/admin/workflows`,
84
+ label: 'Workflows',
85
+ icon: 'M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z M15 12a3 3 0 11-6 0 3 3 0 016 0z',
86
+ position: 53,
87
+ },
88
+ {
89
+ href: `${p}/admin/webhooks`,
90
+ label: 'Webhooks',
91
+ icon: 'M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244',
92
+ position: 54,
93
+ },
94
+ {
95
+ href: `${p}/admin/tags`,
96
+ label: 'Tags',
97
+ icon: 'M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z M6 6h.008v.008H6V6z',
98
+ position: 60,
99
+ },
100
+ {
101
+ href: `${p}/admin/custom-fields`,
102
+ label: 'Custom Fields',
103
+ icon: 'M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z',
104
+ position: 62,
105
+ },
106
+ {
107
+ href: `${p}/admin/statuses`,
108
+ label: 'Statuses',
109
+ icon: 'M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
110
+ position: 64,
111
+ },
112
+ {
113
+ href: `${p}/admin/canned-responses`,
114
+ label: 'Canned Responses',
115
+ icon: 'M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z',
116
+ position: 70,
117
+ },
118
+ {
119
+ href: `${p}/admin/macros`,
120
+ label: 'Macros',
121
+ icon: 'M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z',
122
+ position: 80,
123
+ },
124
+ ...(newslettersEnabled.value && isAdmin.value && hasPermission('newsletters.manage')
125
+ ? [
126
+ {
127
+ href: `${p}/admin/newsletters`,
128
+ label: t('newsletters.nav.newsletters'),
129
+ icon: 'M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75',
130
+ position: 81,
131
+ },
132
+ ]
133
+ : []),
134
+ {
135
+ href: `${p}/admin/skills`,
136
+ label: 'Skills',
137
+ icon: 'M4.26 10.147a60.436 60.436 0 00-.491 6.347A48.627 48.627 0 0112 20.904a48.627 48.627 0 018.232-4.41 60.46 60.46 0 00-.491-6.347m-15.482 0a50.57 50.57 0 00-2.658-.813A59.905 59.905 0 0112 3.493a59.902 59.902 0 0110.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.697 50.697 0 0112 13.489a50.702 50.702 0 017.74-3.342',
138
+ position: 82,
139
+ },
140
+ {
141
+ href: `${p}/admin/roles`,
142
+ label: 'Roles',
143
+ icon: 'M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z',
144
+ position: 84,
145
+ },
146
+ {
147
+ href: `${p}/admin/users`,
148
+ label: 'Users',
149
+ icon: 'M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z',
150
+ position: 83,
151
+ },
152
+ {
153
+ href: `${p}/admin/capacity`,
154
+ label: 'Capacity',
155
+ icon: 'M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5',
156
+ position: 85,
157
+ },
158
+ {
159
+ href: `${p}/admin/business-hours`,
160
+ label: 'Business Hours',
161
+ icon: 'M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5',
162
+ position: 86,
163
+ },
164
+ {
165
+ href: `${p}/admin/audit-log`,
166
+ label: 'Audit Log',
167
+ icon: 'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z',
168
+ position: 88,
169
+ },
170
+ {
171
+ href: `${p}/admin/api-tokens`,
172
+ label: 'API Tokens',
173
+ icon: 'M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z',
174
+ position: 90,
175
+ },
176
+ {
177
+ href: `${p}/admin/custom-objects`,
178
+ label: 'Custom Objects',
179
+ icon: 'M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125',
180
+ position: 92,
181
+ },
182
+ {
183
+ href: `${p}/admin/plugins`,
184
+ label: 'Plugins',
185
+ icon: 'M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 01-.657.643 48.39 48.39 0 01-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 01-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 00-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 01-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 00.657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 005.427-.63 48.05 48.05 0 00.582-4.717.532.532 0 00-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 00.658-.663 48.422 48.422 0 00-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 01-.61-.58v0z',
186
+ position: 95,
187
+ },
188
+ {
189
+ href: `${p}/admin/import`,
190
+ label: 'Import',
191
+ icon: 'M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5',
192
+ position: 97,
193
+ },
194
+ {
195
+ href: `${p}/admin/settings`,
196
+ label: 'Settings',
197
+ icon: 'M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.204-.107-.397.165-.71.505-.781.93l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894zM15 12a3 3 0 11-6 0 3 3 0 016 0z',
198
+ position: 100,
199
+ },
200
+ ];
201
+
202
+ // Merge core admin links with plugin-provided menu items
203
+ const pluginLinks = pluginMenuItems.value.map((item) => ({
204
+ href: item.route,
205
+ label: item.label,
206
+ icon: item.icon,
207
+ position: item.position || 100,
208
+ badge: item.badge || null,
209
+ submenu: item.submenu || [],
210
+ }));
211
+
212
+ return [...coreLinks, ...pluginLinks].sort((a, b) => (a.position || 100) - (b.position || 100));
213
+ });
214
+
215
+ const agentLinks = computed(() => [
216
+ {
217
+ href: `${prefix.value}/agent`,
218
+ label: 'Dashboard',
219
+ icon: 'M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25',
220
+ },
221
+ {
222
+ href: `${prefix.value}/agent/tickets`,
223
+ label: 'Tickets',
224
+ icon: 'M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 010 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 010-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375z',
225
+ },
226
+ ]);
227
+
228
+ const userName = computed(() => page.props.auth?.user?.name || 'User');
229
+ const userInitial = computed(() => userName.value.charAt(0).toUpperCase());
230
+
231
+ function isActive(href) {
232
+ if (!currentUrl.value) return false;
233
+ return currentUrl.value === href || currentUrl.value.startsWith(href + '/');
234
+ }
235
+ </script>
236
+
237
+ <template>
238
+ <!-- MODE 1: Admin — dark sidebar layout -->
239
+ <div
240
+ v-if="isAdminSection"
241
+ class="flex min-h-screen bg-[var(--esc-panel-bg)]"
242
+ :style="{ colorScheme: panelConfig.mode === 'light' ? 'light' : 'dark' }"
243
+ >
244
+ <!-- Sidebar -->
245
+ <aside
246
+ class="fixed inset-y-0 left-0 z-30 flex w-64 flex-col border-r border-[var(--esc-panel-border)] bg-[var(--esc-panel-sidebar-bg)]"
247
+ >
248
+ <!-- Logo -->
249
+ <div class="flex h-16 items-center gap-3 px-5">
250
+ <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-[var(--esc-panel-border-input)]">
251
+ <img
252
+ v-if="typeof panelConfig.logo === 'string'"
253
+ :src="panelConfig.logo"
254
+ class="h-5 w-5"
255
+ alt="Escalated logo"
256
+ />
257
+ <component :is="panelConfig.logo" v-else-if="panelConfig.logo" class="h-5 w-5" />
258
+ <svg
259
+ v-else
260
+ class="h-5 w-5"
261
+ viewBox="0 0 24 24"
262
+ fill="none"
263
+ stroke-width="2.5"
264
+ stroke-linecap="round"
265
+ stroke-linejoin="round"
266
+ >
267
+ <defs>
268
+ <linearGradient id="esc-rainbow-admin" x1="0%" y1="0%" x2="100%" y2="100%">
269
+ <stop offset="0%" stop-color="#f97316" />
270
+ <stop offset="30%" stop-color="#eab308" />
271
+ <stop offset="50%" stop-color="#22c55e" />
272
+ <stop offset="70%" stop-color="#3b82f6" />
273
+ <stop offset="100%" stop-color="#8b5cf6" />
274
+ </linearGradient>
275
+ </defs>
276
+ <g transform="translate(12,12) scale(1.35) translate(-12,-12)">
277
+ <polyline points="17 11 12 6 7 11" stroke="url(#esc-rainbow-admin)" />
278
+ <polyline points="17 18 12 13 7 18" stroke="url(#esc-rainbow-admin)" />
279
+ </g>
280
+ </svg>
281
+ </div>
282
+ <div>
283
+ <span class="text-sm font-bold text-[var(--esc-panel-text)] tracking-wide">{{
284
+ panelConfig.appName
285
+ }}</span>
286
+ <span
287
+ class="ml-1.5 rounded bg-[var(--esc-panel-active)] px-1.5 py-0.5 text-[10px] font-semibold text-[var(--esc-panel-text-muted)]"
288
+ >ADMIN</span
289
+ >
290
+ </div>
291
+ </div>
292
+
293
+ <!-- Nav -->
294
+ <nav role="navigation" aria-label="Admin navigation" class="mt-1 flex-1 space-y-0.5 overflow-y-auto px-3">
295
+ <Link
296
+ v-for="link in adminLinks"
297
+ :key="link.href"
298
+ :href="link.href"
299
+ :aria-current="isActive(link.href) ? 'page' : undefined"
300
+ :class="[
301
+ 'group flex items-center gap-3 rounded-lg px-3 py-2 text-[13px] font-medium transition-all',
302
+ isActive(link.href)
303
+ ? 'bg-[var(--esc-panel-active)] text-[var(--esc-panel-text)]'
304
+ : 'text-[var(--esc-panel-text-muted)] hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]',
305
+ ]"
306
+ >
307
+ <svg
308
+ :class="[
309
+ 'h-[18px] w-[18px] shrink-0',
310
+ isActive(link.href)
311
+ ? 'text-[var(--esc-panel-text)]'
312
+ : 'text-[var(--esc-panel-text-muted)] group-hover:text-[var(--esc-panel-text-tertiary)]',
313
+ ]"
314
+ fill="none"
315
+ stroke="currentColor"
316
+ stroke-width="1.5"
317
+ viewBox="0 0 24 24"
318
+ >
319
+ <path stroke-linecap="round" stroke-linejoin="round" :d="link.icon" />
320
+ </svg>
321
+ {{ link.label }}
322
+ <span
323
+ v-if="link.badge"
324
+ class="ml-auto inline-flex h-5 min-w-[20px] items-center justify-center rounded-full bg-[var(--esc-panel-accent)]/20 px-1.5 text-[10px] font-semibold text-[var(--esc-panel-accent)]"
325
+ >
326
+ {{ link.badge }}
327
+ </span>
328
+ </Link>
329
+ </nav>
330
+
331
+ <!-- Bottom section -->
332
+ <div class="border-t border-[var(--esc-panel-border)] p-3">
333
+ <Link
334
+ v-if="isAgent"
335
+ :href="`${prefix}/agent`"
336
+ class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]"
337
+ >
338
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
339
+ <path
340
+ stroke-linecap="round"
341
+ stroke-linejoin="round"
342
+ d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
343
+ />
344
+ </svg>
345
+ Agent Panel
346
+ </Link>
347
+ <Link
348
+ :href="prefix"
349
+ class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]"
350
+ >
351
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
352
+ <path
353
+ stroke-linecap="round"
354
+ stroke-linejoin="round"
355
+ d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
356
+ />
357
+ </svg>
358
+ Back to App
359
+ </Link>
360
+
361
+ <!-- User -->
362
+ <div class="mt-2 flex items-center gap-3 rounded-lg bg-[var(--esc-panel-hover)] px-3 py-2.5">
363
+ <div
364
+ class="flex h-7 w-7 items-center justify-center rounded-md bg-[var(--esc-panel-active)] text-xs font-semibold text-[var(--esc-panel-text-tertiary)]"
365
+ >
366
+ {{ userInitial }}
367
+ </div>
368
+ <span class="text-sm text-[var(--esc-panel-text-tertiary)]">{{ userName }}</span>
369
+ </div>
370
+ </div>
371
+
372
+ <!-- Powered by -->
373
+ <div v-if="showPoweredBy" class="px-4 pb-3">
374
+ <a
375
+ href="https://escalated.dev"
376
+ target="_blank"
377
+ rel="noopener noreferrer"
378
+ class="flex items-center justify-center gap-1.5 rounded-lg px-2 py-1.5 text-[11px] text-[var(--esc-panel-text-muted)] opacity-60 transition-opacity hover:opacity-100"
379
+ >
380
+ <img
381
+ :src="
382
+ isDark
383
+ ? 'https://escalated.dev/brand/logo-icon-dark.svg'
384
+ : 'https://escalated.dev/brand/logo-icon-white.svg'
385
+ "
386
+ alt="Escalated logo"
387
+ class="h-3.5 w-3.5"
388
+ />
389
+ Powered by Escalated
390
+ </a>
391
+ </div>
392
+ </aside>
393
+
394
+ <!-- Main content -->
395
+ <div class="flex flex-1 flex-col pl-64">
396
+ <!-- Top bar -->
397
+ <header
398
+ role="banner"
399
+ class="sticky top-0 z-20 flex h-14 items-center border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-topbar-bg)] px-6 backdrop-blur-xl"
400
+ >
401
+ <h1 class="text-sm font-semibold text-[var(--esc-panel-text)]">{{ title }}</h1>
402
+ </header>
403
+
404
+ <!-- Page content -->
405
+ <main role="main" class="flex-1 p-6">
406
+ <slot />
407
+ </main>
408
+ </div>
409
+ </div>
410
+
411
+ <!-- MODE 2: Agent — dark top-nav layout -->
412
+ <div
413
+ v-else-if="isAgentSection"
414
+ class="min-h-screen bg-[var(--esc-panel-bg)]"
415
+ :style="{ colorScheme: panelConfig.mode === 'light' ? 'light' : 'dark' }"
416
+ >
417
+ <!-- Top nav -->
418
+ <nav
419
+ class="sticky top-0 z-30 border-b border-[var(--esc-panel-border)] bg-[var(--esc-panel-sidebar-bg)] backdrop-blur-xl"
420
+ >
421
+ <div class="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
422
+ <!-- Left: branding -->
423
+ <div class="flex items-center gap-3">
424
+ <div class="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--esc-panel-border-input)]">
425
+ <img
426
+ v-if="typeof panelConfig.logo === 'string'"
427
+ :src="panelConfig.logo"
428
+ class="h-4 w-4"
429
+ alt="Escalated logo"
430
+ />
431
+ <component :is="panelConfig.logo" v-else-if="panelConfig.logo" class="h-4 w-4" />
432
+ <svg
433
+ v-else
434
+ class="h-4 w-4"
435
+ viewBox="0 0 24 24"
436
+ fill="none"
437
+ stroke-width="2.5"
438
+ stroke-linecap="round"
439
+ stroke-linejoin="round"
440
+ >
441
+ <defs>
442
+ <linearGradient id="esc-rainbow-agent" x1="0%" y1="0%" x2="100%" y2="100%">
443
+ <stop offset="0%" stop-color="#f97316" />
444
+ <stop offset="30%" stop-color="#eab308" />
445
+ <stop offset="50%" stop-color="#22c55e" />
446
+ <stop offset="70%" stop-color="#3b82f6" />
447
+ <stop offset="100%" stop-color="#8b5cf6" />
448
+ </linearGradient>
449
+ </defs>
450
+ <g transform="translate(12,12) scale(1.35) translate(-12,-12)">
451
+ <polyline points="17 11 12 6 7 11" stroke="url(#esc-rainbow-agent)" />
452
+ <polyline points="17 18 12 13 7 18" stroke="url(#esc-rainbow-agent)" />
453
+ </g>
454
+ </svg>
455
+ </div>
456
+ <span class="text-sm font-bold text-[var(--esc-panel-text)] tracking-wide">{{
457
+ panelConfig.appName
458
+ }}</span>
459
+ </div>
460
+
461
+ <!-- Center: nav links -->
462
+ <div class="flex items-center gap-1">
463
+ <Link
464
+ v-for="link in agentLinks"
465
+ :key="link.href"
466
+ :href="link.href"
467
+ :aria-current="isActive(link.href) ? 'page' : undefined"
468
+ :class="[
469
+ 'flex items-center gap-2 rounded-lg px-3.5 py-1.5 text-[13px] font-medium transition-all',
470
+ isActive(link.href)
471
+ ? 'bg-[var(--esc-panel-active)] text-[var(--esc-panel-text)]'
472
+ : 'text-[var(--esc-panel-text-muted)] hover:bg-[var(--esc-panel-hover)] hover:text-[var(--esc-panel-text-secondary)]',
473
+ ]"
474
+ >
475
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
476
+ <path stroke-linecap="round" stroke-linejoin="round" :d="link.icon" />
477
+ </svg>
478
+ {{ link.label }}
479
+ </Link>
480
+ </div>
481
+
482
+ <!-- Right: user + links -->
483
+ <div class="flex items-center gap-3">
484
+ <Link
485
+ v-if="isAdmin"
486
+ :href="`${prefix}/admin/reports`"
487
+ class="text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:text-[var(--esc-panel-text)]"
488
+ >
489
+ Admin
490
+ </Link>
491
+ <Link
492
+ :href="prefix"
493
+ class="text-[13px] text-[var(--esc-panel-text-muted)] transition-colors hover:text-[var(--esc-panel-text)]"
494
+ >
495
+ Back to App
496
+ </Link>
497
+ <div class="ml-1 h-5 w-px bg-[var(--esc-panel-active)]"></div>
498
+ <div class="flex items-center gap-2">
499
+ <div
500
+ class="flex h-7 w-7 items-center justify-center rounded-md bg-[var(--esc-panel-active)] text-[10px] font-semibold text-[var(--esc-panel-text-tertiary)]"
501
+ >
502
+ {{ userInitial }}
503
+ </div>
504
+ <span class="text-[13px] text-[var(--esc-panel-text-tertiary)]">{{ userName }}</span>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ </nav>
509
+
510
+ <!-- Page content -->
511
+ <main class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
512
+ <slot />
513
+ </main>
514
+
515
+ <!-- Powered by -->
516
+ <footer v-if="showPoweredBy" class="border-t border-[var(--esc-panel-border)] py-3 text-center">
517
+ <a
518
+ href="https://escalated.dev"
519
+ target="_blank"
520
+ rel="noopener noreferrer"
521
+ class="inline-flex items-center gap-1.5 text-[11px] text-[var(--esc-panel-text-muted)] opacity-60 transition-opacity hover:opacity-100"
522
+ >
523
+ <img
524
+ :src="
525
+ isDark
526
+ ? 'https://escalated.dev/brand/logo-icon-dark.svg'
527
+ : 'https://escalated.dev/brand/logo-icon-white.svg'
528
+ "
529
+ alt=""
530
+ class="h-3.5 w-3.5"
531
+ />
532
+ Powered by Escalated
533
+ </a>
534
+ </footer>
535
+
536
+ <!-- Active Chats Panel (agent layout only, when chat enabled) -->
537
+ <ActiveChatsPanel v-if="chatEnabled && agentId" :chats="activeChats" :agent-id="agentId" />
538
+ </div>
539
+
540
+ <!-- MODE 3: Customer pages — use host app layout if provided -->
541
+ <component :is="hostLayout" v-else-if="hostLayout">
542
+ <template #header>
543
+ <div class="flex items-center justify-between">
544
+ <h2 class="text-xl font-semibold leading-tight text-gray-800">{{ title }}</h2>
545
+ <nav class="flex items-center gap-4 text-sm">
546
+ <Link :href="prefix" class="text-gray-600 hover:text-gray-900">My Tickets</Link>
547
+ <Link
548
+ v-if="kbEnabled && (kbPublic || page.props.auth?.user)"
549
+ :href="`${prefix}/kb`"
550
+ class="text-gray-600 hover:text-gray-900"
551
+ >
552
+ Knowledge Base
553
+ </Link>
554
+ <Link v-if="isAgent" :href="`${prefix}/agent`" class="text-gray-600 hover:text-gray-900">
555
+ Agent Panel
556
+ </Link>
557
+ <Link v-if="isAdmin" :href="`${prefix}/admin/reports`" class="text-gray-600 hover:text-gray-900">
558
+ Admin
559
+ </Link>
560
+ </nav>
561
+ </div>
562
+ </template>
563
+
564
+ <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
565
+ <slot />
566
+ </div>
567
+ </component>
568
+
569
+ <!-- MODE 4: Customer standalone fallback (no host layout) -->
570
+ <div v-else class="min-h-screen bg-gray-50">
571
+ <nav class="border-b border-gray-200 bg-white">
572
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
573
+ <div class="flex h-14 items-center justify-between">
574
+ <div class="flex items-center gap-6">
575
+ <span class="text-lg font-bold text-gray-900">{{ title }}</span>
576
+ <div class="flex items-center gap-4 text-sm">
577
+ <Link :href="prefix" class="text-gray-600 hover:text-gray-900">My Tickets</Link>
578
+ <Link
579
+ v-if="kbEnabled && (kbPublic || page.props.auth?.user)"
580
+ :href="`${prefix}/kb`"
581
+ class="text-gray-600 hover:text-gray-900"
582
+ >
583
+ Knowledge Base
584
+ </Link>
585
+ <Link v-if="isAgent" :href="`${prefix}/agent`" class="text-gray-600 hover:text-gray-900">
586
+ Agent Panel
587
+ </Link>
588
+ <Link
589
+ v-if="isAdmin"
590
+ :href="`${prefix}/admin/reports`"
591
+ class="text-gray-600 hover:text-gray-900"
592
+ >
593
+ Admin
594
+ </Link>
595
+ </div>
596
+ </div>
597
+ </div>
598
+ </div>
599
+ </nav>
600
+ <main class="flex-1">
601
+ <div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8">
602
+ <slot />
603
+ </div>
604
+ </main>
605
+
606
+ <!-- Powered by -->
607
+ <footer v-if="showPoweredBy" class="border-t border-gray-200 py-3 text-center">
608
+ <a
609
+ href="https://escalated.dev"
610
+ target="_blank"
611
+ rel="noopener noreferrer"
612
+ class="inline-flex items-center gap-1.5 text-[11px] text-gray-400 transition-colors hover:text-gray-600"
613
+ >
614
+ <img src="https://escalated.dev/brand/logo-icon-white.svg" alt="Escalated logo" class="h-3.5 w-3.5" />
615
+ Powered by Escalated
616
+ </a>
617
+ </footer>
618
+ </div>
619
+ </template>