@forjio/portal-ui 0.1.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.
- package/README.md +116 -0
- package/dist/Sidebar.cjs +679 -0
- package/dist/Sidebar.cjs.map +1 -0
- package/dist/Sidebar.d.cts +60 -0
- package/dist/Sidebar.d.ts +60 -0
- package/dist/Sidebar.js +645 -0
- package/dist/Sidebar.js.map +1 -0
- package/dist/index.cjs +38 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/types.cjs +17 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +47 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.cjs +79 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +19 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +52 -0
- package/dist/utils.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @forjio/portal-ui
|
|
2
|
+
|
|
3
|
+
Shared portal chrome for the Forjio family of SaaS products. Sister
|
|
4
|
+
package to [`@forjio/website-ui`](https://github.com/hachimi-cat/forjio-website-ui)
|
|
5
|
+
— that handles the marketing site; this handles the authenticated
|
|
6
|
+
dashboard.
|
|
7
|
+
|
|
8
|
+
Single source of truth for:
|
|
9
|
+
|
|
10
|
+
- **`<Sidebar />`** — workspace switcher + nav sections + profile dropdown
|
|
11
|
+
- Workspace persistence helpers (cookie / localStorage / API)
|
|
12
|
+
|
|
13
|
+
Extracted from `saas-plugipay` on 2026-05-19 as the canonical reference
|
|
14
|
+
build (per [TEMPLATE.md](https://github.com/hachimi-cat/forjio-service-template/blob/master/TEMPLATE.md)).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm i @forjio/portal-ui lucide-react
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer deps: `react`, `react-dom`, `next` (App Router), `lucide-react`.
|
|
23
|
+
|
|
24
|
+
## Minimal usage
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
'use client';
|
|
28
|
+
import { Sidebar } from '@forjio/portal-ui';
|
|
29
|
+
import { LayoutDashboard, CreditCard, Settings } from 'lucide-react';
|
|
30
|
+
import { useState, useEffect } from 'react';
|
|
31
|
+
|
|
32
|
+
export function PortalLayout({ children }: { children: React.ReactNode }) {
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const [workspaces, setWorkspaces] = useState([]);
|
|
35
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch('/api/v1/workspaces', { credentials: 'include' })
|
|
39
|
+
.then((r) => r.json())
|
|
40
|
+
.then((b) => setWorkspaces(b?.data ?? []));
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div style={{ display: 'flex' }}>
|
|
45
|
+
<Sidebar
|
|
46
|
+
brandSlug="kalium"
|
|
47
|
+
brandName="Kalium"
|
|
48
|
+
brandColor="#7C3AED"
|
|
49
|
+
workspacePersist="cookie"
|
|
50
|
+
workspaces={workspaces}
|
|
51
|
+
activeWorkspaceId={activeId}
|
|
52
|
+
sections={[
|
|
53
|
+
{
|
|
54
|
+
label: 'Overview',
|
|
55
|
+
items: [
|
|
56
|
+
{ href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'Money',
|
|
61
|
+
items: [
|
|
62
|
+
{ href: '/dashboard/billing', label: 'Billing', icon: CreditCard },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'Settings',
|
|
67
|
+
items: [
|
|
68
|
+
{ href: '/dashboard/settings', label: 'Settings', icon: Settings },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
]}
|
|
72
|
+
user={{ name: 'Gojo Sensei', email: 'gojo@forjio.com' }}
|
|
73
|
+
onLogout={async () => {
|
|
74
|
+
await fetch('/api/v1/auth/logout', { method: 'POST', credentials: 'include' });
|
|
75
|
+
window.location.href = '/';
|
|
76
|
+
}}
|
|
77
|
+
open={open}
|
|
78
|
+
onClose={() => setOpen(false)}
|
|
79
|
+
/>
|
|
80
|
+
<main style={{ flex: 1 }}>{children}</main>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Workspace persistence
|
|
87
|
+
|
|
88
|
+
| Mode | Persistence | Backend reads from |
|
|
89
|
+
|-----------|---------------------------------------------------|--------------------------------------------------|
|
|
90
|
+
| `cookie` | `<brand>_active_workspace` cookie (recommended) | Cookie header on every request |
|
|
91
|
+
| `local` | `<brand>_active_workspace` localStorage | `X-Account-Id` header you thread by hand |
|
|
92
|
+
| `api` | POST to `apiSwitchPath` mutating server session | Backend session state |
|
|
93
|
+
|
|
94
|
+
**Use `cookie` for new products** — it's the only mode where the
|
|
95
|
+
backend's auth middleware can resolve the active workspace without
|
|
96
|
+
either a header round-trip or a separate localStorage→header thread.
|
|
97
|
+
saas-plugipay's pattern; the storlaunch/linksnap localStorage variant
|
|
98
|
+
causes a known frontend/backend desync that bit us during the
|
|
99
|
+
seeded-data screenshot session.
|
|
100
|
+
|
|
101
|
+
## Why not Tailwind?
|
|
102
|
+
|
|
103
|
+
Inline styles + CSS custom properties only. Consumers can keep their
|
|
104
|
+
own Tailwind config without theme collisions, and the component renders
|
|
105
|
+
identically regardless of the host app's CSS framework. brandColor +
|
|
106
|
+
brandSlug props drive everything.
|
|
107
|
+
|
|
108
|
+
## Anti-patterns from previous patterns
|
|
109
|
+
|
|
110
|
+
- ❌ Don't hardcode brand strings in the sidebar — pass via props
|
|
111
|
+
- ❌ Don't use localStorage workspace persistence on new products
|
|
112
|
+
- ❌ Don't ship `position: fixed` overlay without the lg:hidden gate
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
UNLICENSED — private Forjio family package.
|