@xeplr/ui-account 1.0.0 → 1.0.1

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.
@@ -0,0 +1,34 @@
1
+ import { memo } from 'react';
2
+ import AccountMenu from './AccountMenu.jsx';
3
+ import NotificationsBell from './NotificationsBell.jsx';
4
+ import './nav.css';
5
+
6
+ // The (default, swappable via NavPage's `design` prop) top bar: 10% / 80% / 10%
7
+ // columns. Only ever rendered when there's NO drawer — NavPage renders the
8
+ // drawer instead of this, never both (see pages.jsx). Left holds the small
9
+ // `logo`, middle is `navMiddle` (a fully exposed slot, e.g. an app's
10
+ // company/workspace picker), right is always the settings trigger plus the
11
+ // notifications bell when `notifications` is set.
12
+ function NavTopSample({
13
+ logo, navMiddle,
14
+ user, accountItems, notifications, accountOpen, toggleAccount, closeAccount, accountRef, logout
15
+ }) {
16
+ return (
17
+ <header className="xeplr-nav xeplr-nav-top">
18
+ <div className="xeplr-nav-left">
19
+ {logo && <img src={logo} alt="" className="xeplr-nav-logo" />}
20
+ </div>
21
+ <div className="xeplr-nav-middle">{navMiddle}</div>
22
+ <div className="xeplr-nav-right">
23
+ <NotificationsBell notifications={notifications} />
24
+ <AccountMenu
25
+ user={user} accountItems={accountItems}
26
+ accountOpen={accountOpen} toggleAccount={toggleAccount} closeAccount={closeAccount}
27
+ accountRef={accountRef} logout={logout}
28
+ />
29
+ </div>
30
+ </header>
31
+ );
32
+ }
33
+
34
+ export default memo(NavTopSample);
@@ -0,0 +1,31 @@
1
+ import { memo } from 'react';
2
+
3
+ // Its own control, separate from AccountMenu on purpose — memoized independently
4
+ // so a notification count changing doesn't re-render the account dropdown (and
5
+ // vice versa). `notifications` is optional; omit it to hide the bell entirely.
6
+ // Wire a real feed later (open a popover, etc.) without touching the other parts.
7
+ // `label` is optional text shown next to the icon — the drawer passes
8
+ // "Notifications" when expanded (matching how its other items show a label),
9
+ // omits it when collapsed; the top bar never passes it (icon-only, no room).
10
+ function NotificationsBell({ notifications, label }) {
11
+ if (!notifications) return null;
12
+ var count = notifications.count;
13
+
14
+ return (
15
+ <button
16
+ type="button"
17
+ className={'xeplr-nav-bell' + (label ? ' xeplr-nav-bell-labeled' : '')}
18
+ onClick={notifications.onClick}
19
+ aria-label={count > 0 ? count + ' unread notifications' : 'Notifications'}
20
+ >
21
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
22
+ <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
23
+ <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
24
+ </svg>
25
+ {count > 0 && <span className="xeplr-nav-badge">{count > 9 ? '9+' : count}</span>}
26
+ {label && <span className="xeplr-nav-bell-label">{label}</span>}
27
+ </button>
28
+ );
29
+ }
30
+
31
+ export default memo(NotificationsBell);
@@ -1,7 +1,9 @@
1
1
  import './auth.css';
2
2
 
3
+ // error/success feedback fires as a snackbar (see useProfileController.js) —
4
+ // this design doesn't render it inline.
3
5
  export default function ProfileSample({
4
- form, error, success, loading, fetching, handleChange, handleSubmit
6
+ form, loading, fetching, handleChange, handleSubmit
5
7
  }) {
6
8
  if (fetching) {
7
9
  return (
@@ -14,8 +16,6 @@ export default function ProfileSample({
14
16
  return (
15
17
  <div className="xeplr-auth-container">
16
18
  <h1>Profile</h1>
17
- {error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
18
- {success && <div className="xeplr-auth-alert xeplr-auth-alert-success">{success}</div>}
19
19
  <form onSubmit={handleSubmit}>
20
20
  <div className="xeplr-auth-form-group">
21
21
  <label htmlFor="xeplr-profile-name">Name</label>
@@ -1,12 +1,12 @@
1
1
  import { Link } from 'react-router-dom';
2
2
  import './auth.css';
3
3
 
4
- export default function RegisterSample({ form, error, success, loading, handleChange, handleSubmit }) {
4
+ // error/success feedback fires as a snackbar (see useRegisterController.js) —
5
+ // this design doesn't render it inline.
6
+ export default function RegisterSample({ form, loading, handleChange, handleSubmit }) {
5
7
  return (
6
8
  <div className="xeplr-auth-container">
7
9
  <h1>Register</h1>
8
- {error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
9
- {success && <div className="xeplr-auth-alert xeplr-auth-alert-success">{success}</div>}
10
10
  <form onSubmit={handleSubmit}>
11
11
  <div className="xeplr-auth-form-group">
12
12
  <label htmlFor="xeplr-name">Name</label>
@@ -1,11 +1,13 @@
1
1
  import { Link } from 'react-router-dom';
2
2
  import './auth.css';
3
3
 
4
- export default function ResetPasswordSample({ token, password, setPassword, error, success, loading, handleSubmit }) {
4
+ // error/success feedback fires as a snackbar (see useResetPasswordController.js,
5
+ // including the "invalid link" case) — this design doesn't render it inline,
6
+ // it just switches which block is visible.
7
+ export default function ResetPasswordSample({ token, password, setPassword, success, loading, handleSubmit }) {
5
8
  if (!token && !success) {
6
9
  return (
7
10
  <div className="xeplr-auth-container">
8
- <div className="xeplr-auth-alert xeplr-auth-alert-error">Invalid reset link</div>
9
11
  <div className="xeplr-auth-links">
10
12
  <p><Link to="/auth/forgot-password">Request a new reset link</Link></p>
11
13
  </div>
@@ -16,14 +18,10 @@ export default function ResetPasswordSample({ token, password, setPassword, erro
16
18
  return (
17
19
  <div className="xeplr-auth-container">
18
20
  <h1>Reset Password</h1>
19
- {error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
20
21
  {success && (
21
- <>
22
- <div className="xeplr-auth-alert xeplr-auth-alert-success">{success}</div>
23
- <div className="xeplr-auth-links">
24
- <p><Link to="/auth/login">Go to Login</Link></p>
25
- </div>
26
- </>
22
+ <div className="xeplr-auth-links">
23
+ <p><Link to="/auth/login">Go to Login</Link></p>
24
+ </div>
27
25
  )}
28
26
  {!success && (
29
27
  <form onSubmit={handleSubmit}>
@@ -3,8 +3,14 @@
3
3
  /* ─── xeplr Admin Matrix ─── */
4
4
 
5
5
  .xeplr-admin-container {
6
- max-width: 1200px;
7
- margin: 20px auto;
6
+ /* Fill the available height of whatever container the host app uses
7
+ (e.g. the architects app's `.content` area) so the table area scrolls
8
+ internally instead of leaving empty whitespace below. */
9
+ width: 100%;
10
+ height: 100%;
11
+ min-height: 0;
12
+ display: flex;
13
+ flex-direction: column;
8
14
  padding: 20px;
9
15
  border: 1px solid var(--xeplr-border-primary);
10
16
  border-radius: 8px;
@@ -165,7 +171,11 @@
165
171
  /* ─── Matrix Table ─── */
166
172
 
167
173
  .xeplr-admin-matrix-wrapper {
168
- overflow-x: auto;
174
+ /* Fills remaining height inside the flex column container so the table
175
+ scrolls internally and the header/tabs/toolbar stay pinned at top. */
176
+ flex: 1;
177
+ min-height: 0;
178
+ overflow: auto;
169
179
  border: 1px solid var(--xeplr-border-primary);
170
180
  border-radius: 6px;
171
181
  }
@@ -82,22 +82,16 @@
82
82
  text-decoration: underline;
83
83
  }
84
84
 
85
+ /* In-progress status text (e.g. "Activating your account...") — error/success
86
+ feedback fires as a snackbar instead (see the use*Controller.js hooks), so
87
+ there's no -error/-success modifier here anymore. */
85
88
  .xeplr-auth-alert {
86
89
  padding: 10px 15px;
87
90
  border-radius: 4px;
88
91
  margin-bottom: 15px;
89
- }
90
-
91
- .xeplr-auth-alert-error {
92
- background: var(--xeplr-danger-bg);
93
- color: var(--xeplr-danger);
94
- border: 1px solid var(--xeplr-danger-border);
95
- }
96
-
97
- .xeplr-auth-alert-success {
98
- background: var(--xeplr-success-bg);
99
- color: var(--xeplr-success);
100
- border: 1px solid var(--xeplr-success-border);
92
+ background: var(--xeplr-info-bg);
93
+ color: var(--xeplr-info);
94
+ border: 1px solid var(--xeplr-info-border);
101
95
  }
102
96
 
103
97
  .xeplr-auth-not-activated {
@@ -110,51 +104,3 @@
110
104
  color: var(--xeplr-warning);
111
105
  margin: 20px 0;
112
106
  }
113
-
114
- /* Tenant Picker */
115
- .xeplr-tenant-picker {
116
- display: flex;
117
- flex-direction: column;
118
- gap: 10px;
119
- margin-top: 16px;
120
- }
121
-
122
- .xeplr-tenant-option {
123
- display: flex;
124
- flex-direction: column;
125
- align-items: flex-start;
126
- gap: 4px;
127
- padding: 16px;
128
- border: 1px solid var(--xeplr-border-primary, #333);
129
- border-radius: 8px;
130
- background: var(--xeplr-bg-secondary, #222);
131
- cursor: pointer;
132
- transition: all 0.15s;
133
- text-align: left;
134
- width: 100%;
135
- }
136
-
137
- .xeplr-tenant-option:hover {
138
- border-color: var(--xeplr-accent, #646cff);
139
- background: var(--xeplr-bg-active, #1a1a2a);
140
- }
141
-
142
- .xeplr-tenant-name {
143
- font-size: 16px;
144
- font-weight: 600;
145
- color: var(--xeplr-text-primary, #e0e0e0);
146
- }
147
-
148
- .xeplr-tenant-code {
149
- font-size: 12px;
150
- font-family: monospace;
151
- color: var(--xeplr-tag-text, #9999cc);
152
- background: var(--xeplr-tag-bg, #2a2a3a);
153
- padding: 2px 6px;
154
- border-radius: 3px;
155
- }
156
-
157
- .xeplr-tenant-desc {
158
- font-size: 13px;
159
- color: var(--xeplr-text-muted, #777);
160
- }
@@ -9,5 +9,7 @@ export { default as ProfileSample } from './ProfileSample.jsx';
9
9
  export { default as UserRolesMatrixSample } from './UserRolesMatrixSample.jsx';
10
10
  export { default as AccessMatrixSample } from './AccessMatrixSample.jsx';
11
11
  export { default as MasterSettingsSample } from './MasterSettingsSample.jsx';
12
- export { default as TenantSample } from './TenantSample.jsx';
13
- export { default as TenantPickerSample } from './TenantPickerSample.jsx';
12
+ export { default as NavTopSample } from './NavTopSample.jsx';
13
+ export { default as AccountMenu } from './AccountMenu.jsx';
14
+ export { default as NavDrawer } from './NavDrawer.jsx';
15
+ export { default as NotificationsBell } from './NotificationsBell.jsx';
@@ -0,0 +1,311 @@
1
+ /* Uses the shared --xeplr-* theme tokens (see theme.css) — correct in both
2
+ light and dark automatically, as long as the app applies a .xeplr-theme-*
3
+ class (see ThemeContext.jsx / theme.css usage notes). */
4
+
5
+ /* ─── Top bar: 10% / 80% / 10% ───
6
+ flexbox, not grid: fixed percentage grid tracks don't account for `gap`
7
+ or box-sizing the way flex-basis does, and 10%+80%+10% already sums to
8
+ 100% before the gaps/padding are added — grid pushed the real content
9
+ width past the container and forced a horizontal scrollbar. flex-basis
10
+ percentages combined with box-sizing:border-box and the default
11
+ flex-shrink correctly negotiate the gaps back out of the 100%. */
12
+
13
+ .xeplr-nav-top {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 16px;
17
+ padding: 10px 20px;
18
+ box-sizing: border-box;
19
+ background: var(--xeplr-bg-tertiary);
20
+ border-bottom: 1px solid var(--xeplr-border-primary);
21
+ }
22
+
23
+ .xeplr-nav-left { flex: 0 1 10%; display: flex; align-items: center; gap: 8px; min-width: 0; }
24
+ .xeplr-nav-right { flex: 0 1 10%; display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
25
+
26
+ .xeplr-nav-logo { height: 32px; width: auto; display: block; }
27
+
28
+ .xeplr-nav-middle {
29
+ flex: 1 1 80%;
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 12px;
33
+ min-width: 0;
34
+ }
35
+
36
+ /* ─── Settings trigger + dropdown ─── */
37
+
38
+ .xeplr-nav-account { position: relative; }
39
+
40
+ .xeplr-nav-settings-trigger {
41
+ position: relative;
42
+ width: 34px;
43
+ height: 34px;
44
+ border: none;
45
+ background: none;
46
+ border-radius: 50%;
47
+ color: var(--xeplr-text-secondary);
48
+ display: grid;
49
+ place-items: center;
50
+ cursor: pointer;
51
+ align-self: center;
52
+ }
53
+ .xeplr-nav-settings-trigger:hover { background: var(--xeplr-bg-hover); color: var(--xeplr-text-primary); }
54
+ .xeplr-nav-settings-trigger svg { width: 19px; height: 19px; flex-shrink: 0; }
55
+
56
+ /* Drawer, expanded: a full-width labeled row instead of a circular icon
57
+ button, matching .xeplr-nav-drawer-link's look. */
58
+ .xeplr-nav-settings-trigger-labeled {
59
+ width: 100%;
60
+ height: auto;
61
+ border-radius: 6px;
62
+ display: flex;
63
+ justify-content: flex-start;
64
+ gap: 10px;
65
+ padding: 9px 10px;
66
+ }
67
+ .xeplr-nav-settings-trigger-label { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
68
+
69
+ .xeplr-nav-avatar {
70
+ width: 34px;
71
+ height: 34px;
72
+ border-radius: 50%;
73
+ background: var(--xeplr-accent);
74
+ color: var(--xeplr-accent-text);
75
+ display: grid;
76
+ place-items: center;
77
+ font-size: 14px;
78
+ font-weight: 700;
79
+ flex-shrink: 0;
80
+ }
81
+ .xeplr-nav-avatar-img { object-fit: cover; }
82
+
83
+ .xeplr-nav-bell {
84
+ position: relative;
85
+ width: 34px;
86
+ height: 34px;
87
+ border: none;
88
+ background: none;
89
+ border-radius: 50%;
90
+ color: var(--xeplr-text-secondary);
91
+ display: grid;
92
+ place-items: center;
93
+ cursor: pointer;
94
+ align-self: center;
95
+ }
96
+ .xeplr-nav-bell:hover { background: var(--xeplr-bg-hover); color: var(--xeplr-text-primary); }
97
+ .xeplr-nav-bell svg { width: 19px; height: 19px; flex-shrink: 0; }
98
+
99
+ /* Drawer, expanded: a full-width labeled row instead of a circular icon
100
+ button, matching .xeplr-nav-drawer-link's look. */
101
+ .xeplr-nav-bell-labeled {
102
+ position: relative;
103
+ width: 100%;
104
+ height: auto;
105
+ border-radius: 6px;
106
+ display: flex;
107
+ justify-content: flex-start;
108
+ gap: 10px;
109
+ padding: 9px 10px;
110
+ }
111
+ .xeplr-nav-bell-labeled .xeplr-nav-badge { position: static; margin-left: auto; }
112
+ .xeplr-nav-bell-label { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
113
+
114
+ .xeplr-nav-badge {
115
+ position: absolute;
116
+ top: -4px;
117
+ right: -4px;
118
+ min-width: 16px;
119
+ height: 16px;
120
+ padding: 0 4px;
121
+ border-radius: 999px;
122
+ background: var(--xeplr-danger);
123
+ color: #fff;
124
+ font-size: 10px;
125
+ font-weight: 700;
126
+ line-height: 1;
127
+ display: grid;
128
+ place-items: center;
129
+ }
130
+
131
+ .xeplr-nav-account-menu {
132
+ position: absolute;
133
+ top: calc(100% + 8px);
134
+ right: 0;
135
+ min-width: 220px;
136
+ background: var(--xeplr-bg-tertiary);
137
+ border: 1px solid var(--xeplr-border-primary);
138
+ border-radius: 8px;
139
+ box-shadow: var(--xeplr-shadow-md);
140
+ padding: 6px;
141
+ z-index: 60;
142
+ }
143
+
144
+ /* Drawer placement: trigger sits near the bottom-left of the viewport, so the
145
+ menu opens upward (not off the bottom edge) and rightward from the trigger
146
+ (not off the left edge — the default `right: 0` anchoring would run the
147
+ menu's width past x=0 given how narrow/close-to-the-edge the drawer is). */
148
+ .xeplr-nav-account-menu-top {
149
+ top: auto;
150
+ bottom: calc(100% + 8px);
151
+ left: 0;
152
+ right: auto;
153
+ }
154
+
155
+ .xeplr-nav-account-header {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 10px;
159
+ padding: 8px 10px 10px;
160
+ margin-bottom: 6px;
161
+ border-bottom: 1px solid var(--xeplr-border-secondary);
162
+ }
163
+
164
+ .xeplr-nav-account-header-text { min-width: 0; }
165
+ .xeplr-nav-account-name { font-size: 13px; font-weight: 600; color: var(--xeplr-text-primary); }
166
+ .xeplr-nav-account-email { margin-top: 2px; font-size: 12px; color: var(--xeplr-text-muted); }
167
+
168
+ .xeplr-nav-account-item {
169
+ display: block;
170
+ width: 100%;
171
+ text-align: left;
172
+ padding: 8px 10px;
173
+ border: none;
174
+ border-radius: 6px;
175
+ background: none;
176
+ color: var(--xeplr-text-primary);
177
+ font-size: 13px;
178
+ text-decoration: none;
179
+ cursor: pointer;
180
+ }
181
+
182
+ .xeplr-nav-account-item:hover { background: var(--xeplr-bg-hover); }
183
+ .xeplr-nav-account-logout { color: var(--xeplr-danger); }
184
+ .xeplr-nav-account-divider { height: 1px; margin: 6px 0; background: var(--xeplr-border-secondary); }
185
+
186
+ /* ─── Drawer rail — pinned to the viewport's top-left corner (0,0), full
187
+ height, `position: fixed` with a high z-index. A pure overlay: it never
188
+ pushes, offsets, or otherwise interacts with the top bar or the app's own
189
+ content — there IS no top bar when this renders (see pages.jsx, they're
190
+ mutually exclusive), and content underneath simply renders at its normal
191
+ position, with the drawer floating on top of whatever's in that corner.
192
+ No overflow set here (default: visible) so the account-menu popup in the
193
+ footer can escape this box — only the scrollable nav-links list below
194
+ clips/scrolls on its own. */
195
+
196
+ .xeplr-nav-drawer {
197
+ position: fixed;
198
+ top: 0;
199
+ left: 0;
200
+ z-index: 90;
201
+ display: flex;
202
+ flex-direction: column;
203
+ height: 100vh;
204
+ padding: 16px 10px;
205
+ background: var(--xeplr-bg-tertiary);
206
+ border-right: 1px solid var(--xeplr-border-primary);
207
+ box-shadow: var(--xeplr-shadow-lg);
208
+ transition: width 0.15s;
209
+ flex-shrink: 0;
210
+ }
211
+
212
+ .xeplr-nav-drawer-collapsed { width: 60px; }
213
+ .xeplr-nav-drawer-expanded { width: 240px; padding: 16px; }
214
+
215
+ .xeplr-nav-drawer-toggle {
216
+ display: block;
217
+ flex-shrink: 0;
218
+ border: none;
219
+ background: none;
220
+ padding: 0;
221
+ margin-bottom: 12px;
222
+ cursor: pointer;
223
+ align-self: flex-start;
224
+ }
225
+
226
+ /* Fixed, small, and never swapped for a different image (see NavDrawer.jsx) —
227
+ deliberately smaller than the collapsed rail's content width (60px rail -
228
+ 20px padding = 40px) so it can never force the rail wider than intended. */
229
+ .xeplr-nav-drawer-logo { width: 22px; height: 22px; object-fit: contain; display: block; }
230
+
231
+ /* Its own row, real height — typically a stacked icon+wordmark lockup
232
+ (near-square, not a wide banner), so 22px (the toggle icon's height) would
233
+ crush the wordmark to a few illegible pixels. */
234
+ .xeplr-nav-drawer-expanded-logo {
235
+ height: 40px;
236
+ width: auto;
237
+ object-fit: contain;
238
+ display: block;
239
+ margin-bottom: 16px;
240
+ align-self: flex-start;
241
+ }
242
+
243
+ .xeplr-nav-drawer-search {
244
+ width: 100%;
245
+ margin-bottom: 12px;
246
+ padding: 8px 10px;
247
+ border: 1px solid var(--xeplr-border-secondary);
248
+ border-radius: 6px;
249
+ background: var(--xeplr-bg-primary);
250
+ color: var(--xeplr-text-primary);
251
+ font-size: 13px;
252
+ }
253
+
254
+ .xeplr-nav-drawer-links {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 2px;
258
+ flex: 1;
259
+ overflow-y: auto;
260
+ overflow-x: hidden;
261
+ }
262
+
263
+ .xeplr-nav-drawer-group { display: flex; flex-direction: column; gap: 2px; margin-top: 10px; }
264
+ .xeplr-nav-drawer-group-label {
265
+ padding: 4px 10px;
266
+ font-size: 11px;
267
+ font-weight: 600;
268
+ letter-spacing: 0.04em;
269
+ text-transform: uppercase;
270
+ color: var(--xeplr-text-muted);
271
+ }
272
+
273
+ .xeplr-nav-drawer-link {
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 10px;
277
+ padding: 9px 10px;
278
+ border: none;
279
+ border-radius: 6px;
280
+ background: none;
281
+ color: var(--xeplr-text-primary);
282
+ font-size: 14px;
283
+ text-align: left;
284
+ cursor: pointer;
285
+ }
286
+ .xeplr-nav-drawer-link:hover { background: var(--xeplr-bg-hover); }
287
+ .xeplr-nav-drawer-collapsed .xeplr-nav-drawer-link { justify-content: center; }
288
+
289
+ .xeplr-nav-drawer-icon { display: grid; place-items: center; flex-shrink: 0; }
290
+ .xeplr-nav-drawer-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
291
+
292
+ .xeplr-nav-drawer-empty { padding: 10px 12px; color: var(--xeplr-text-muted); font-size: 13px; }
293
+
294
+ .xeplr-nav-drawer-promo { padding-top: 12px; }
295
+
296
+ /* Settings (AccountMenu, placement="top") + notifications — the drawer's
297
+ footer, since there's no top bar for them to live in when the drawer is
298
+ the whole nav. `.xeplr-nav-drawer-links` already has flex:1, so this and
299
+ any promo above it are naturally pushed to the bottom without needing
300
+ their own margin-top:auto. */
301
+ .xeplr-nav-drawer-footer {
302
+ display: flex;
303
+ flex-direction: column;
304
+ /* stretch so the expanded (labeled) rows can go full-width; the collapsed
305
+ circular icon buttons self-center via their own align-self: center. */
306
+ align-items: stretch;
307
+ gap: 6px;
308
+ padding-top: 12px;
309
+ margin-top: 12px;
310
+ border-top: 1px solid var(--xeplr-border-secondary);
311
+ }
package/src/index.js CHANGED
@@ -1,9 +1,19 @@
1
1
  // API client
2
- export { configure, registerUser, loginUser, activateAccount, forgotPassword, resetPassword, changePassword, getProfile, updateProfile, logoutUser, getMyTenants, authFetch } from './api.js';
2
+ export { configure, registerUser, loginUser, activateAccount, forgotPassword, resetPassword, changePassword, getProfile, updateProfile, uploadAvatar, logoutUser, authFetch } from './api.js';
3
3
 
4
4
  // Token helpers
5
5
  export { getToken, setToken, getRefreshToken, setRefreshToken, getUser, setUser, clearAuth, isAuthenticated } from './token.js';
6
6
 
7
+ // Multi-tenant levels — call registerMTs() once at app boot with the SAME
8
+ // shape passed to @xeplr/db's registerMTs() on the backend. Teaches authFetch
9
+ // which header to attach for each level, read from activeScope. See mt.js.
10
+ export { registerMTs, getMtConfig } from './mt.js';
11
+
12
+ // Active scope (e.g. company/workspace), keyed by MT level (l1, l2, ...) — a
13
+ // bare, app-interpreted value per level, attached to every authFetch call as
14
+ // that level's configured header. See activeScope.js.
15
+ export { getActiveScope, setActiveScope, clearActiveScope } from './activeScope.js';
16
+
7
17
  // Controller hooks
8
18
  export { useLoginController } from './useLoginController.js';
9
19
  export { useRegisterController } from './useRegisterController.js';
@@ -15,8 +25,7 @@ export { useProfileController } from './useProfileController.js';
15
25
  export { useUserRolesController } from './useUserRolesController.js';
16
26
  export { useAccessMatrixController } from './useAccessMatrixController.js';
17
27
  export { useMasterSettingsController } from './useMasterSettingsController.js';
18
- export { useTenantController } from './useTenantController.js';
19
- export { useTenantPickerController, getActiveTenant, setActiveTenant, clearActiveTenant, hasMultipleTenants } from './useTenantPickerController.js';
28
+ export { useNavController } from './useNavController.js';
20
29
 
21
30
  // Theme
22
31
  export { ThemeProvider, useTheme, useThemeStrict, BUILT_IN_THEMES, DEFAULT_THEME } from './ThemeContext.jsx';
@@ -29,13 +38,15 @@ export { AccessGuard } from './AccessGuard.jsx';
29
38
  // Admin API (model layer for RBAC management)
30
39
  export { getUsers, getRoles, getAccessItems, toggleUserRole, toggleAccessRole, toggleModuleRole } from './adminApi.js';
31
40
  export { getMasterItems, saveMasterItem, deleteMasterItem, MASTER_TYPES } from './masterApi.js';
32
- export { getTenants, saveTenant, deleteTenant, assignUserTenant } from './adminApi.js';
33
41
 
34
42
  // Design validation (use when building custom designs)
35
- export { useDesignValidator, LOGIN_RULES, REGISTER_RULES, FORGOT_PASSWORD_RULES, RESET_PASSWORD_RULES, CHANGE_PASSWORD_RULES, PROFILE_RULES, USER_ROLES_MATRIX_RULES, ACCESS_MATRIX_RULES, MASTER_SETTINGS_RULES } from './validateDesign.js';
43
+ export { useDesignValidator, LOGIN_RULES, REGISTER_RULES, FORGOT_PASSWORD_RULES, RESET_PASSWORD_RULES, CHANGE_PASSWORD_RULES, PROFILE_RULES, USER_ROLES_MATRIX_RULES, ACCESS_MATRIX_RULES, MASTER_SETTINGS_RULES, NAV_RULES } from './validateDesign.js';
36
44
 
37
45
  // Sample designs (use as reference or starting point)
38
- export { LoginSample, RegisterSample, ForgotPasswordSample, ResetPasswordSample, ActivateSample, NotActivatedSample, ChangePasswordSample, ProfileSample, UserRolesMatrixSample, AccessMatrixSample, MasterSettingsSample, TenantSample } from './designs/index.js';
46
+ export { LoginSample, RegisterSample, ForgotPasswordSample, ResetPasswordSample, ActivateSample, NotActivatedSample, ChangePasswordSample, ProfileSample, UserRolesMatrixSample, AccessMatrixSample, MasterSettingsSample, NavTopSample, AccountMenu, NavDrawer, NotificationsBell } from './designs/index.js';
47
+
48
+ // Ready-made pages (controller + sample design wired together; each takes an optional `design` prop)
49
+ export { LoginPage, RegisterPage, ForgotPasswordPage, ResetPasswordPage, ActivatePage, NotActivatedPage, ChangePasswordPage, ProfilePage, UserRolesPage, AccessMatrixPage, MasterSettingsPage, NavPage } from './pages.jsx';
39
50
 
40
- // Ready-made pages (controller + sample design wired together)
41
- export { LoginPage, RegisterPage, ForgotPasswordPage, ResetPasswordPage, ActivatePage, NotActivatedPage, ChangePasswordPage, ProfilePage, UserRolesPage, AccessMatrixPage, MasterSettingsPage, TenantPage, TenantPickerPage } from './pages.jsx';
51
+ // One-call auth routing — mount every auth page with no boilerplate; override just what you want
52
+ export { authRoutes, authPath } from './authRoutes.jsx';
package/src/mt.js ADDED
@@ -0,0 +1,31 @@
1
+ // Frontend counterpart to @xeplr/db's BaseModel.registerMTs() — call this once
2
+ // at app boot with the SAME config object (same level keys, names, headers).
3
+ // There's no shared runtime package between frontend and backend (by design —
4
+ // duplicated by convention); keep one literal config in your own app and
5
+ // import it into both boot entry points so it can't drift.
6
+ //
7
+ // registerMTs({
8
+ // l1: { name: 'companyId', header: 'x-company-id' },
9
+ // l2: { name: 'workspaceId', header: 'x-workspace-id' }
10
+ // });
11
+ //
12
+ // This only teaches api.js's authFetch which header to attach for each level
13
+ // (read from activeScope.js) — it doesn't validate anything itself.
14
+
15
+ var SLOT_KEYS = ['l1', 'l2', 'l3', 'l4'];
16
+ var _config = { slots: {} };
17
+
18
+ export function registerMTs(config) {
19
+ config = config || {};
20
+ var slots = {};
21
+ SLOT_KEYS.forEach(function(key) {
22
+ if (config[key]) {
23
+ slots[key] = { name: config[key].name, header: String(config[key].header).toLowerCase() };
24
+ }
25
+ });
26
+ _config = { slots: slots };
27
+ }
28
+
29
+ export function getMtConfig() {
30
+ return { slots: Object.assign({}, _config.slots) };
31
+ }