spine-framework-cortex 0.1.7 → 0.1.9
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/LICENSE.md +13 -0
- package/components/CortexSidebar.tsx +27 -22
- package/index.tsx +14 -5
- package/package.json +5 -4
- package/pages/crm/AccountDetailPage.tsx +6 -6
package/LICENSE.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Spine Framework Internal Use License 1.0.0
|
|
2
|
+
|
|
3
|
+
**Source-available. Free for internal use. Commercial rights reserved.**
|
|
4
|
+
|
|
5
|
+
Copyright © 2026 Dahl Ventures Inc. All rights reserved.
|
|
6
|
+
|
|
7
|
+
This software is licensed under the Spine Framework Internal Use License 1.0.0.
|
|
8
|
+
|
|
9
|
+
For full license terms, see: https://github.com/spine-framework/spine-framework/blob/main/LICENSE.md
|
|
10
|
+
|
|
11
|
+
**Summary:** You may use this software for internal business use only. Commercial use, resale, SaaS offering, white-labeling, and distribution require a separate Commercial License.
|
|
12
|
+
|
|
13
|
+
Contact: webmaster@spine-framework.com
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import * as React from "react"
|
|
11
|
-
import { useNavigate, useLocation } from "react-router-dom"
|
|
11
|
+
import { Link, useNavigate, useLocation } from "react-router-dom"
|
|
12
12
|
import { useAuth } from "@core/contexts/AuthContext"
|
|
13
|
+
import { useCurrentApp } from "@core/contexts/AppContext"
|
|
13
14
|
import {
|
|
14
15
|
LayoutDashboard,
|
|
15
16
|
Building2,
|
|
@@ -35,26 +36,30 @@ import {
|
|
|
35
36
|
SidebarRail,
|
|
36
37
|
} from "@core/components/ui/sidebar"
|
|
37
38
|
|
|
38
|
-
const crmItems = [
|
|
39
|
-
{ title: "Dashboard", url: "/cortex/dashboard", icon: LayoutDashboard },
|
|
40
|
-
{ title: "Accounts", url: "/cortex/crm/accounts", icon: Building2 },
|
|
41
|
-
{ title: "Contacts", url: "/cortex/crm/contacts", icon: Users },
|
|
42
|
-
{ title: "Deals", url: "/cortex/crm/deals", icon: Handshake },
|
|
43
|
-
{ title: "Health", url: "/cortex/crm/health", icon: Heart },
|
|
44
|
-
{ title: "Activity", url: "/cortex/crm/activity", icon: Activity },
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
const opsItems = [
|
|
48
|
-
{ title: "Support", url: "/cortex/support", icon: Headphones },
|
|
49
|
-
{ title: "Community", url: "/cortex/community", icon: Users },
|
|
50
|
-
{ title: "Knowledge Base", url: "/cortex/kb", icon: BookOpen },
|
|
51
|
-
{ title: "Courses", url: "/cortex/courses", icon: GraduationCap },
|
|
52
|
-
]
|
|
53
|
-
|
|
54
39
|
export function CortexSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
55
40
|
const navigate = useNavigate()
|
|
56
41
|
const location = useLocation()
|
|
57
42
|
const { user } = useAuth()
|
|
43
|
+
const app = useCurrentApp()
|
|
44
|
+
|
|
45
|
+
// Normalize route_prefix: '/' becomes '' so all paths are /dashboard, /crm/accounts, etc.
|
|
46
|
+
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
47
|
+
|
|
48
|
+
const crmItems = [
|
|
49
|
+
{ title: "Dashboard", url: `${base}/dashboard`, icon: LayoutDashboard },
|
|
50
|
+
{ title: "Accounts", url: `${base}/crm/accounts`, icon: Building2 },
|
|
51
|
+
{ title: "Contacts", url: `${base}/crm/contacts`, icon: Users },
|
|
52
|
+
{ title: "Deals", url: `${base}/crm/deals`, icon: Handshake },
|
|
53
|
+
{ title: "Health", url: `${base}/crm/health`, icon: Heart },
|
|
54
|
+
{ title: "Activity", url: `${base}/crm/activity`, icon: Activity },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
const opsItems = [
|
|
58
|
+
{ title: "Support", url: `${base}/support`, icon: Headphones },
|
|
59
|
+
{ title: "Community", url: `${base}/community`, icon: Users },
|
|
60
|
+
{ title: "Knowledge Base", url: `${base}/kb`, icon: BookOpen },
|
|
61
|
+
{ title: "Courses", url: `${base}/courses`, icon: GraduationCap },
|
|
62
|
+
]
|
|
58
63
|
|
|
59
64
|
const isActive = (url: string) => location.pathname.startsWith(url)
|
|
60
65
|
|
|
@@ -63,7 +68,7 @@ export function CortexSidebar({ ...props }: React.ComponentProps<typeof Sidebar>
|
|
|
63
68
|
<SidebarHeader>
|
|
64
69
|
<SidebarMenu>
|
|
65
70
|
<SidebarMenuItem>
|
|
66
|
-
<SidebarMenuButton size="lg" onClick={() => navigate(
|
|
71
|
+
<SidebarMenuButton size="lg" onClick={() => navigate(`${base}/dashboard`)}>
|
|
67
72
|
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground">
|
|
68
73
|
<span className="text-sm font-bold">Cx</span>
|
|
69
74
|
</div>
|
|
@@ -87,10 +92,10 @@ export function CortexSidebar({ ...props }: React.ComponentProps<typeof Sidebar>
|
|
|
87
92
|
asChild
|
|
88
93
|
isActive={isActive(item.url)}
|
|
89
94
|
>
|
|
90
|
-
<
|
|
95
|
+
<Link to={item.url}>
|
|
91
96
|
<item.icon className="h-4 w-4" />
|
|
92
97
|
<span>{item.title}</span>
|
|
93
|
-
</
|
|
98
|
+
</Link>
|
|
94
99
|
</SidebarMenuButton>
|
|
95
100
|
</SidebarMenuItem>
|
|
96
101
|
))}
|
|
@@ -108,10 +113,10 @@ export function CortexSidebar({ ...props }: React.ComponentProps<typeof Sidebar>
|
|
|
108
113
|
asChild
|
|
109
114
|
isActive={isActive(item.url)}
|
|
110
115
|
>
|
|
111
|
-
<
|
|
116
|
+
<Link to={item.url}>
|
|
112
117
|
<item.icon className="h-4 w-4" />
|
|
113
118
|
<span>{item.title}</span>
|
|
114
|
-
</
|
|
119
|
+
</Link>
|
|
115
120
|
</SidebarMenuButton>
|
|
116
121
|
</SidebarMenuItem>
|
|
117
122
|
))}
|
package/index.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { LoadingSpinner } from '@core/components/ui/LoadingSpinner'
|
|
|
4
4
|
import { AppShell } from '@core/components/layout/AppShell'
|
|
5
5
|
import { CortexSidebar } from './components/CortexSidebar'
|
|
6
6
|
import { TooltipProvider } from '@core/components/ui/tooltip'
|
|
7
|
+
import { useCurrentApp } from '@core/contexts/AppContext'
|
|
7
8
|
|
|
8
9
|
const CortexDashboard = lazy(() => import('./pages/CortexDashboard'))
|
|
9
10
|
|
|
@@ -40,13 +41,21 @@ const Fallback = <div className="min-h-[400px] flex items-center justify-center"
|
|
|
40
41
|
|
|
41
42
|
function CortexLayout() {
|
|
42
43
|
const location = useLocation()
|
|
44
|
+
const app = useCurrentApp()
|
|
45
|
+
|
|
46
|
+
// Normalize route_prefix: '/' → '' so paths are /dashboard, /crm/accounts, etc.
|
|
47
|
+
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
48
|
+
const prefixDepth = base.split('/').filter(Boolean).length
|
|
49
|
+
|
|
43
50
|
const segments = location.pathname.split('/').filter(Boolean)
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
const appSegments = segments.slice(prefixDepth) // strips the prefix segments
|
|
52
|
+
|
|
53
|
+
const breadcrumbs: { title: string; url?: string }[] = [{ title: 'Cortex', url: `${base}/dashboard` }]
|
|
54
|
+
if (appSegments[0] && appSegments[0] !== 'dashboard') {
|
|
55
|
+
breadcrumbs.push({ title: appSegments[0].charAt(0).toUpperCase() + appSegments[0].slice(1) })
|
|
47
56
|
}
|
|
48
|
-
if (
|
|
49
|
-
breadcrumbs.push({ title:
|
|
57
|
+
if (appSegments[1]) {
|
|
58
|
+
breadcrumbs.push({ title: appSegments[1].charAt(0).toUpperCase() + appSegments[1].slice(1) })
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
return (
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework-cortex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Cortex — AI-powered support, CRM, and knowledge base app for Spine Framework",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/spine-framework/cortex",
|
|
10
10
|
"directory": "custom/apps/cortex"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"components/",
|
|
21
21
|
"functions/",
|
|
22
22
|
"lib/",
|
|
23
|
-
"README.md"
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE.md"
|
|
24
25
|
],
|
|
25
26
|
"spine": {
|
|
26
27
|
"type": "app",
|
|
@@ -7,7 +7,7 @@ import { Skeleton } from '@core/components/ui/skeleton'
|
|
|
7
7
|
import { Button } from '@core/components/ui/button'
|
|
8
8
|
import { ScrollArea } from '@core/components/ui/scroll-area'
|
|
9
9
|
import { Separator } from '@core/components/ui/separator'
|
|
10
|
-
import { ArrowLeft, User, Ticket, Handshake, Activity, Heart,
|
|
10
|
+
import { ArrowLeft, User, Ticket, Handshake, Activity, Heart, Filter, TrendingUp, Target, Clock } from 'lucide-react'
|
|
11
11
|
|
|
12
12
|
interface Account {
|
|
13
13
|
id: string
|
|
@@ -140,7 +140,7 @@ function ActivityTab({ accountId }: { accountId: string }) {
|
|
|
140
140
|
)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
function
|
|
143
|
+
function FilterTab({ account }: { account: Account }) {
|
|
144
144
|
const stage = account.data?.lifecycle_stage
|
|
145
145
|
const score = account.data?.lead_score ?? 0
|
|
146
146
|
const temp = account.data?.temperature
|
|
@@ -217,7 +217,7 @@ function FunnelTab({ account }: { account: Account }) {
|
|
|
217
217
|
{queue?.pending_opportunity_id && (
|
|
218
218
|
<div className="border rounded-lg p-4 bg-yellow-50 border-yellow-200">
|
|
219
219
|
<div className="flex items-center gap-2">
|
|
220
|
-
<
|
|
220
|
+
<Filter className="h-4 w-4 text-yellow-600" />
|
|
221
221
|
<span className="font-medium text-sm">Opportunity in Queue</span>
|
|
222
222
|
</div>
|
|
223
223
|
<p className="text-xs text-muted-foreground mt-1">
|
|
@@ -317,7 +317,7 @@ function FunnelTab({ account }: { account: Account }) {
|
|
|
317
317
|
{/* Empty State */}
|
|
318
318
|
{!stage && !score && !temp && !ratings && !queue?.pending_opportunity_id && (
|
|
319
319
|
<div className="text-center py-12 text-muted-foreground">
|
|
320
|
-
<
|
|
320
|
+
<Filter className="h-10 w-10 mx-auto mb-3 opacity-30" />
|
|
321
321
|
<p className="text-sm">No funnel data yet.</p>
|
|
322
322
|
<p className="text-xs mt-1">Signals will appear here as they are processed.</p>
|
|
323
323
|
</div>
|
|
@@ -364,7 +364,7 @@ export default function AccountDetailPage() {
|
|
|
364
364
|
<TabsList className="h-9 bg-transparent p-0 gap-4">
|
|
365
365
|
{[
|
|
366
366
|
{ value: 'people', label: 'People', icon: User },
|
|
367
|
-
{ value: 'funnel', label: 'Funnel', icon:
|
|
367
|
+
{ value: 'funnel', label: 'Funnel', icon: Filter },
|
|
368
368
|
{ value: 'tickets', label: 'Tickets', icon: Ticket },
|
|
369
369
|
{ value: 'deals', label: 'Deals', icon: Handshake },
|
|
370
370
|
{ value: 'health', label: 'Health', icon: Heart },
|
|
@@ -381,7 +381,7 @@ export default function AccountDetailPage() {
|
|
|
381
381
|
|
|
382
382
|
<ScrollArea className="flex-1">
|
|
383
383
|
<TabsContent value="people" className="mt-0"><PeopleTab accountId={id!} /></TabsContent>
|
|
384
|
-
<TabsContent value="funnel" className="mt-0"><
|
|
384
|
+
<TabsContent value="funnel" className="mt-0"><FilterTab account={account} /></TabsContent>
|
|
385
385
|
<TabsContent value="tickets" className="mt-0"><ItemsTab accountId={id!} typeSlug="support_ticket" emptyText="No tickets." /></TabsContent>
|
|
386
386
|
<TabsContent value="deals" className="mt-0"><ItemsTab accountId={id!} typeSlug="deal" emptyText="No deals." /></TabsContent>
|
|
387
387
|
<TabsContent value="health" className="mt-0"><ItemsTab accountId={id!} typeSlug="csm_health" emptyText="No health records." /></TabsContent>
|