spine-framework-portal 0.1.5 → 0.1.7
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/README.md +43 -11
- package/components/PortalHeader.tsx +12 -7
- package/index.tsx +21 -10
- package/package.json +5 -4
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
|
package/README.md
CHANGED
|
@@ -1,26 +1,58 @@
|
|
|
1
1
|
# spine-framework-portal
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**We're building Spine in the open.** The source is visible, the roadmap is public, and we share what we learn along the way. That said, Spine is not open source — commercial use requires a license. See [LICENSE.md](./LICENSE.md) for details.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Customer Portal is a self-service app for Spine Framework. It gives your customers a branded portal to submit and track support tickets, browse the knowledge base, access courses, and participate in community discussions.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- [Spine Framework](https://www.npmjs.com/package/spine-framework) `>=0.1.0`
|
|
12
|
+
- A Spine project initialized with `npx spine-framework init`
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
7
16
|
```bash
|
|
8
|
-
|
|
17
|
+
npx spine-framework install-app spine-framework-portal@latest
|
|
9
18
|
```
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
This seeds the required roles, types, and configuration into your database and registers the app with Spine's navigation system.
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- **Support tickets** — customers submit and track their own tickets
|
|
25
|
+
- **Knowledge base** — self-service article browsing and search
|
|
26
|
+
- **Courses** — lesson and course content delivery
|
|
27
|
+
- **Community** — discussion threads and Q&A
|
|
28
|
+
- **Marketplace** — app and integration discovery
|
|
29
|
+
|
|
30
|
+
## Routes
|
|
31
|
+
|
|
32
|
+
| Path | Description |
|
|
33
|
+
|------|-------------|
|
|
34
|
+
| `/portal` | Portal home |
|
|
35
|
+
| `/portal/tickets` | Ticket list |
|
|
36
|
+
| `/portal/tickets/:id` | Ticket detail |
|
|
37
|
+
| `/portal/kb` | Knowledge base |
|
|
38
|
+
| `/portal/courses` | Courses |
|
|
39
|
+
| `/portal/community` | Community |
|
|
40
|
+
| `/portal/marketplace` | Marketplace |
|
|
41
|
+
|
|
42
|
+
## Uninstalling
|
|
12
43
|
|
|
13
44
|
```bash
|
|
14
|
-
npx spine-framework
|
|
45
|
+
npx spine-framework uninstall-app spine-framework-portal
|
|
15
46
|
```
|
|
16
47
|
|
|
17
|
-
##
|
|
48
|
+
## License
|
|
18
49
|
|
|
19
|
-
-
|
|
20
|
-
- Course and lesson content delivery
|
|
21
|
-
- Support ticket submission and tracking
|
|
22
|
-
- Knowledge base article access
|
|
50
|
+
Spine Framework Portal is **source-available, free for internal use**. Commercial use requires a separate license.
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
See [LICENSE.md](./LICENSE.md) for full terms.
|
|
53
|
+
|
|
54
|
+
For commercial licensing: [spine-framework.com](https://spine-framework.com) · webmaster@spine-framework.com
|
|
55
|
+
|
|
56
|
+
---
|
|
25
57
|
|
|
26
|
-
|
|
58
|
+
Copyright © 2026 Dahl Ventures Inc. All rights reserved.
|
|
@@ -2,6 +2,7 @@ import { useState } from 'react'
|
|
|
2
2
|
import { NavLink, useNavigate } from 'react-router-dom'
|
|
3
3
|
import { Ticket, Users, BookOpen, GraduationCap, Store, LayoutGrid, User, LogOut, Save } from 'lucide-react'
|
|
4
4
|
import { useAuth } from '@core/contexts/AuthContext'
|
|
5
|
+
import { useCurrentApp } from '@core/contexts/AppContext'
|
|
5
6
|
import { Button } from '@core/components/ui/button'
|
|
6
7
|
import { Avatar, AvatarFallback } from '@core/components/ui/avatar'
|
|
7
8
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@core/components/ui/dialog'
|
|
@@ -10,19 +11,23 @@ import { Label } from '@core/components/ui/label'
|
|
|
10
11
|
import { Separator } from '@core/components/ui/separator'
|
|
11
12
|
|
|
12
13
|
const NAV_ITEMS = [
|
|
13
|
-
{ label: 'Tickets', path: '/
|
|
14
|
-
{ label: 'Knowledge Base', path: '/
|
|
15
|
-
{ label: 'Courses', path: '/
|
|
16
|
-
{ label: 'Community', path: '/
|
|
17
|
-
{ label: 'Marketplace', path: '/
|
|
14
|
+
{ label: 'Tickets', path: '/tickets', icon: Ticket },
|
|
15
|
+
{ label: 'Knowledge Base', path: '/kb', icon: BookOpen },
|
|
16
|
+
{ label: 'Courses', path: '/courses', icon: GraduationCap },
|
|
17
|
+
{ label: 'Community', path: '/community', icon: Users },
|
|
18
|
+
{ label: 'Marketplace', path: '/marketplace', icon: Store },
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
export function PortalHeader() {
|
|
21
22
|
const { user, logout } = useAuth()
|
|
23
|
+
const app = useCurrentApp()
|
|
22
24
|
const navigate = useNavigate()
|
|
23
25
|
const [accountOpen, setAccountOpen] = useState(false)
|
|
24
26
|
const [displayName, setDisplayName] = useState(user?.full_name || user?.email?.split('@')[0] || '')
|
|
25
27
|
|
|
28
|
+
// Normalize route_prefix: '/' → '' so paths are /, /tickets, /kb, etc.
|
|
29
|
+
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
30
|
+
|
|
26
31
|
const initials = (displayName || user?.email || 'U')
|
|
27
32
|
.split(' ')
|
|
28
33
|
.map((w: string) => w[0])
|
|
@@ -43,7 +48,7 @@ export function PortalHeader() {
|
|
|
43
48
|
<>
|
|
44
49
|
<header className="sticky top-0 z-50 bg-background border-b border-border shadow-sm">
|
|
45
50
|
<div className="flex items-center h-14 px-6 gap-6">
|
|
46
|
-
<NavLink to=
|
|
51
|
+
<NavLink to={`${base}/`} className="flex items-center gap-2 shrink-0 text-foreground hover:text-primary transition-colors">
|
|
47
52
|
<LayoutGrid size={18} className="text-primary" />
|
|
48
53
|
<span className="font-semibold tracking-tight text-sm">Customer Portal</span>
|
|
49
54
|
</NavLink>
|
|
@@ -52,7 +57,7 @@ export function PortalHeader() {
|
|
|
52
57
|
{NAV_ITEMS.map(({ label, path, icon: Icon }) => (
|
|
53
58
|
<NavLink
|
|
54
59
|
key={path}
|
|
55
|
-
to={path}
|
|
60
|
+
to={`${base}${path}`}
|
|
56
61
|
className={({ isActive }) =>
|
|
57
62
|
[
|
|
58
63
|
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-colors',
|
package/index.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { lazy, Suspense } from 'react'
|
|
2
|
-
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
2
|
+
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
|
|
3
3
|
import { LoadingSpinner } from '@core/components/ui/LoadingSpinner'
|
|
4
4
|
import { TooltipProvider } from '@core/components/ui/tooltip'
|
|
5
5
|
import { PortalHeader } from './components/PortalHeader'
|
|
6
6
|
import { PortalFooter } from './components/PortalFooter'
|
|
7
|
+
import { useCurrentApp } from '@core/contexts/AppContext'
|
|
7
8
|
|
|
8
9
|
const HomePage = lazy(() => import('./pages/HomePage').then(m => ({ default: m.HomePage })))
|
|
9
10
|
const TicketsPage = lazy(() => import('./pages/TicketsPage').then(m => ({ default: m.TicketsPage })))
|
|
@@ -13,6 +14,16 @@ const KnowledgePage = lazy(() => import('./pages/KnowledgePage').then(m => ({ de
|
|
|
13
14
|
const MarketplacePage = lazy(() => import('./pages/MarketplacePage').then(m => ({ default: m.MarketplacePage })))
|
|
14
15
|
|
|
15
16
|
function PortalLayout() {
|
|
17
|
+
const location = useLocation()
|
|
18
|
+
const app = useCurrentApp()
|
|
19
|
+
|
|
20
|
+
// Normalize route_prefix: '/' → '' so paths are /, /tickets, /kb, etc.
|
|
21
|
+
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
22
|
+
const prefixDepth = base.split('/').filter(Boolean).length
|
|
23
|
+
|
|
24
|
+
const segments = location.pathname.split('/').filter(Boolean)
|
|
25
|
+
const appSegments = segments.slice(prefixDepth) // strips the prefix segments
|
|
26
|
+
|
|
16
27
|
return (
|
|
17
28
|
<div className="h-full flex flex-col bg-background overflow-hidden">
|
|
18
29
|
<PortalHeader />
|
|
@@ -20,15 +31,15 @@ function PortalLayout() {
|
|
|
20
31
|
<Suspense fallback={<div className="flex-1 flex items-center justify-center"><LoadingSpinner /></div>}>
|
|
21
32
|
<div className="flex-1 flex flex-col min-h-0">
|
|
22
33
|
<Routes>
|
|
23
|
-
<Route
|
|
24
|
-
<Route path="
|
|
25
|
-
<Route path="
|
|
26
|
-
<Route path="
|
|
27
|
-
<Route path="
|
|
28
|
-
<Route path="
|
|
29
|
-
<Route path="
|
|
30
|
-
<Route path="
|
|
31
|
-
<Route path="*" element={<Navigate to="/
|
|
34
|
+
<Route index element={<HomePage />} />
|
|
35
|
+
<Route path="tickets" element={<TicketsPage />} />
|
|
36
|
+
<Route path="tickets/:id" element={<TicketsPage />} />
|
|
37
|
+
<Route path="kb" element={<KnowledgePage />} />
|
|
38
|
+
<Route path="knowledge" element={<Navigate to="kb" replace />} />
|
|
39
|
+
<Route path="courses" element={<CoursesPage />} />
|
|
40
|
+
<Route path="community" element={<CommunityPage />} />
|
|
41
|
+
<Route path="marketplace" element={<MarketplacePage />} />
|
|
42
|
+
<Route path="*" element={<Navigate to="/" replace />} />
|
|
32
43
|
</Routes>
|
|
33
44
|
</div>
|
|
34
45
|
</Suspense>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework-portal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Customer Portal — self-service portal 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/portal",
|
|
10
10
|
"directory": "custom/apps/customer-portal"
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"pages/",
|
|
20
20
|
"components/",
|
|
21
21
|
"functions/",
|
|
22
|
-
"README.md"
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE.md"
|
|
23
24
|
],
|
|
24
25
|
"spine": {
|
|
25
26
|
"type": "app",
|