spine-framework-portal 0.2.10 → 0.2.12
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.
|
@@ -2,7 +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 {
|
|
5
|
+
import { useAppPath } from '@core/hooks/useAppPath'
|
|
6
6
|
import { Button } from '@core/components/ui/button'
|
|
7
7
|
import { Avatar, AvatarFallback } from '@core/components/ui/avatar'
|
|
8
8
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@core/components/ui/dialog'
|
|
@@ -20,14 +20,11 @@ const NAV_ITEMS = [
|
|
|
20
20
|
|
|
21
21
|
export function PortalHeader() {
|
|
22
22
|
const { user, logout } = useAuth()
|
|
23
|
-
const
|
|
23
|
+
const appPath = useAppPath()
|
|
24
24
|
const navigate = useNavigate()
|
|
25
25
|
const [accountOpen, setAccountOpen] = useState(false)
|
|
26
26
|
const [displayName, setDisplayName] = useState(user?.full_name || user?.email?.split('@')[0] || '')
|
|
27
27
|
|
|
28
|
-
// Normalize route_prefix: '/' → '' so paths are /, /tickets, /kb, etc.
|
|
29
|
-
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
30
|
-
|
|
31
28
|
const initials = (displayName || user?.email || 'U')
|
|
32
29
|
.split(' ')
|
|
33
30
|
.map((w: string) => w[0])
|
|
@@ -48,7 +45,7 @@ export function PortalHeader() {
|
|
|
48
45
|
<>
|
|
49
46
|
<header className="sticky top-0 z-50 bg-background border-b border-border shadow-sm">
|
|
50
47
|
<div className="flex items-center h-14 px-6 gap-6">
|
|
51
|
-
<NavLink to={
|
|
48
|
+
<NavLink to={appPath('/')} className="flex items-center gap-2 shrink-0 text-foreground hover:text-primary transition-colors">
|
|
52
49
|
<LayoutGrid size={18} className="text-primary" />
|
|
53
50
|
<span className="font-semibold tracking-tight text-sm">Customer Portal</span>
|
|
54
51
|
</NavLink>
|
|
@@ -57,7 +54,7 @@ export function PortalHeader() {
|
|
|
57
54
|
{NAV_ITEMS.map(({ label, path, icon: Icon }) => (
|
|
58
55
|
<NavLink
|
|
59
56
|
key={path}
|
|
60
|
-
to={
|
|
57
|
+
to={appPath(path)}
|
|
61
58
|
className={({ isActive }) =>
|
|
62
59
|
[
|
|
63
60
|
'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-sm font-medium transition-colors',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHandler } from '
|
|
2
|
-
import { adminDb } from '
|
|
1
|
+
import { createHandler } from './_shared/middleware'
|
|
2
|
+
import { adminDb } from './_shared/db'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Portal Community Escalation Handler
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createHandler } from '
|
|
2
|
-
import {
|
|
1
|
+
import { createHandler } from './_shared/middleware'
|
|
2
|
+
import { adminDb } from './_shared/db'
|
|
3
|
+
import { resolveTypeId } from './_shared/resolve-ids'
|
|
3
4
|
|
|
4
5
|
export const handler = createHandler(async (ctx, body) => {
|
|
5
6
|
const {
|
|
@@ -26,7 +27,21 @@ export const handler = createHandler(async (ctx, body) => {
|
|
|
26
27
|
throw err
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// Get the funnel_signal type_id
|
|
31
|
+
const { data: typeId } = await adminDb
|
|
32
|
+
.from('types')
|
|
33
|
+
.select('id')
|
|
34
|
+
.eq('kind', 'item')
|
|
35
|
+
.eq('slug', 'funnel_signal')
|
|
36
|
+
.single()
|
|
37
|
+
|
|
38
|
+
if (!typeId) {
|
|
39
|
+
const err: any = new Error('funnel_signal type not found')
|
|
40
|
+
err.statusCode = 500
|
|
41
|
+
throw err
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const signalData = {
|
|
30
45
|
account_id: ctx.accountId,
|
|
31
46
|
person_id: ctx.principal.id,
|
|
32
47
|
session_id: session_id || `portal_${ctx.principal.id}_${Date.now()}`,
|
|
@@ -43,13 +58,23 @@ export const handler = createHandler(async (ctx, body) => {
|
|
|
43
58
|
occurred_at: new Date().toISOString()
|
|
44
59
|
}
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
// Create the signal item directly using admin-data pattern
|
|
62
|
+
const { data: signal, error } = await adminDb
|
|
63
|
+
.from('items')
|
|
64
|
+
.insert({
|
|
65
|
+
type_id: typeId.id,
|
|
66
|
+
title: `${action_type} - ${action_value}`,
|
|
67
|
+
account_id: ctx.accountId,
|
|
68
|
+
data: signalData
|
|
69
|
+
})
|
|
70
|
+
.select('id')
|
|
71
|
+
.single()
|
|
47
72
|
|
|
48
|
-
if (
|
|
49
|
-
const err: any = new Error(
|
|
50
|
-
err.statusCode =
|
|
73
|
+
if (error || !signal) {
|
|
74
|
+
const err: any = new Error(`Failed to create signal: ${error?.message || 'Unknown error'}`)
|
|
75
|
+
err.statusCode = 500
|
|
51
76
|
throw err
|
|
52
77
|
}
|
|
53
78
|
|
|
54
|
-
return { status: 'ok', signal_id:
|
|
79
|
+
return { status: 'ok', signal_id: signal.id }
|
|
55
80
|
})
|
package/index.tsx
CHANGED
|
@@ -16,14 +16,6 @@ const DeveloperSettingsPage = lazy(() => import('./pages/DeveloperSettingsPage')
|
|
|
16
16
|
|
|
17
17
|
function PortalLayout() {
|
|
18
18
|
const location = useLocation()
|
|
19
|
-
const app = useCurrentApp()
|
|
20
|
-
|
|
21
|
-
// Normalize route_prefix: '/' → '' so paths are /, /tickets, /kb, etc.
|
|
22
|
-
const base = app.route_prefix === '/' ? '' : (app.route_prefix || '')
|
|
23
|
-
const prefixDepth = base.split('/').filter(Boolean).length
|
|
24
|
-
|
|
25
|
-
const segments = location.pathname.split('/').filter(Boolean)
|
|
26
|
-
const appSegments = segments.slice(prefixDepth) // strips the prefix segments
|
|
27
19
|
|
|
28
20
|
return (
|
|
29
21
|
<div className="h-full flex flex-col bg-background overflow-hidden">
|