@xeplr/ui-account 1.0.0 → 1.0.2
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/package.json +25 -5
- package/src/AccessContext.jsx +86 -4
- package/src/ProtectedRoute.jsx +4 -1
- package/src/activeScope.js +71 -0
- package/src/adminApi.js +0 -29
- package/src/api.js +321 -22
- package/src/authRoutes.jsx +125 -0
- package/src/designs/AccountMenu.jsx +71 -0
- package/src/designs/ActivateSample.jsx +8 -16
- package/src/designs/ChangePasswordSample.jsx +3 -3
- package/src/designs/ForgotPasswordSample.jsx +3 -3
- package/src/designs/LoginSample.jsx +3 -2
- package/src/designs/NavDrawer.jsx +194 -0
- package/src/designs/NavFloatingSettings.jsx +25 -0
- package/src/designs/NavTopSample.jsx +34 -0
- package/src/designs/NotificationsBell.jsx +31 -0
- package/src/designs/ProfileSample.jsx +3 -3
- package/src/designs/RegisterSample.jsx +3 -3
- package/src/designs/ResetPasswordSample.jsx +7 -9
- package/src/designs/admin.css +13 -3
- package/src/designs/auth.css +6 -60
- package/src/designs/index.js +5 -2
- package/src/designs/nav.css +439 -0
- package/src/index.js +24 -8
- package/src/mt.js +31 -0
- package/src/pages.jsx +95 -38
- package/src/returnTo.js +24 -0
- package/src/useActivateController.js +19 -4
- package/src/useChangePasswordController.js +8 -2
- package/src/useForgotPasswordController.js +3 -0
- package/src/useLoginController.js +8 -1
- package/src/useNavController.js +224 -0
- package/src/useProfileController.js +6 -1
- package/src/useRegisterController.js +5 -1
- package/src/useResetPasswordController.js +12 -1
- package/src/validateDesign.js +20 -2
- package/src/designs/TenantPickerSample.jsx +0 -39
- package/src/designs/TenantSample.jsx +0 -182
- package/src/useTenantController.js +0 -146
- package/src/useTenantPickerController.js +0 -97
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import './auth.css';
|
|
2
2
|
|
|
3
|
+
// error/success feedback fires as a snackbar (see useChangePasswordController.js)
|
|
4
|
+
// — this design doesn't render it inline.
|
|
3
5
|
export default function ChangePasswordSample({
|
|
4
6
|
currentPassword, setCurrentPassword,
|
|
5
7
|
newPassword, setNewPassword,
|
|
6
8
|
confirmPassword, setConfirmPassword,
|
|
7
|
-
|
|
9
|
+
loading, handleSubmit
|
|
8
10
|
}) {
|
|
9
11
|
return (
|
|
10
12
|
<div className="xeplr-auth-container">
|
|
11
13
|
<h1>Change Password</h1>
|
|
12
|
-
{error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
|
|
13
|
-
{success && <div className="xeplr-auth-alert xeplr-auth-alert-success">{success}</div>}
|
|
14
14
|
<form onSubmit={handleSubmit}>
|
|
15
15
|
<div className="xeplr-auth-form-group">
|
|
16
16
|
<label htmlFor="xeplr-current-password">Current Password</label>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Link } from 'react-router-dom';
|
|
2
2
|
import './auth.css';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// error/success feedback fires as a snackbar (see useForgotPasswordController.js)
|
|
5
|
+
// — this design doesn't render it inline.
|
|
6
|
+
export default function ForgotPasswordSample({ email, setEmail, loading, handleSubmit }) {
|
|
5
7
|
return (
|
|
6
8
|
<div className="xeplr-auth-container">
|
|
7
9
|
<h1>Forgot Password</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-email">Email</label>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Link } from 'react-router-dom';
|
|
2
2
|
import './auth.css';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// error/success feedback fires as a snackbar (see useLoginController.js) —
|
|
5
|
+
// this design doesn't render it inline.
|
|
6
|
+
export default function LoginSample({ email, setEmail, password, setPassword, loading, handleSubmit }) {
|
|
5
7
|
return (
|
|
6
8
|
<div className="xeplr-auth-container">
|
|
7
9
|
<h1>Login</h1>
|
|
8
|
-
{error && <div className="xeplr-auth-alert xeplr-auth-alert-error">{error}</div>}
|
|
9
10
|
<form onSubmit={handleSubmit}>
|
|
10
11
|
<div className="xeplr-auth-form-group">
|
|
11
12
|
<label htmlFor="xeplr-email">Email</label>
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { memo, useState, useMemo } from 'react';
|
|
2
|
+
import AccountMenu from './AccountMenu.jsx';
|
|
3
|
+
import NotificationsBell from './NotificationsBell.jsx';
|
|
4
|
+
|
|
5
|
+
// The ENTIRE nav when drawerItems is non-empty — NavPage renders this instead
|
|
6
|
+
// of (never alongside) the top bar, see pages.jsx. A pure overlay: `position:
|
|
7
|
+
// fixed` (nav.css), pinned to (0,0), full height — it never pushes or resizes
|
|
8
|
+
// anything else on the page, completely decoupled from the app's own content.
|
|
9
|
+
// Two states: icon-only (collapsed, `drawerOpen=false`) and
|
|
10
|
+
// icon+label+groups+search+promo (expanded). Toggled by clicking its own
|
|
11
|
+
// logo. Settings + notifications live in its footer (bottom-pinned) instead
|
|
12
|
+
// of a top bar, since there isn't one — same accountItems/notifications the
|
|
13
|
+
// top bar would have gotten, just rendered here instead.
|
|
14
|
+
//
|
|
15
|
+
// Items are bucketed by their optional `group` — ungrouped items render first
|
|
16
|
+
// (no header), grouped ones under a section header, groups in first-seen order.
|
|
17
|
+
function bucketItems(items) {
|
|
18
|
+
var ungrouped = [];
|
|
19
|
+
var groupOrder = [];
|
|
20
|
+
var groups = {};
|
|
21
|
+
items.forEach(function(item) {
|
|
22
|
+
if (!item.group) {
|
|
23
|
+
ungrouped.push(item);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!groups[item.group]) {
|
|
27
|
+
groups[item.group] = [];
|
|
28
|
+
groupOrder.push(item.group);
|
|
29
|
+
}
|
|
30
|
+
groups[item.group].push(item);
|
|
31
|
+
});
|
|
32
|
+
return { ungrouped: ungrouped, groupOrder: groupOrder, groups: groups };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function NavDrawer({
|
|
36
|
+
drawerOpen, toggleDrawer, drawerItems, expandedLogo, logo, drawerPromo,
|
|
37
|
+
user, accountItems, notifications, accountOpen, toggleAccount, closeAccount, accountRef, logout,
|
|
38
|
+
hideFooterIcons,
|
|
39
|
+
drawerWidth, drawerResizing, startDrawerResize, resetDrawerWidth, nudgeDrawerWidth, drawerWidthBounds
|
|
40
|
+
}) {
|
|
41
|
+
var [query, setQuery] = useState('');
|
|
42
|
+
|
|
43
|
+
var bounds = drawerWidthBounds || { min: 180, max: 480, step: 16 };
|
|
44
|
+
|
|
45
|
+
// Arrow keys resize, Home/End jump to the stops — the handle is focusable,
|
|
46
|
+
// so everything the drag does has to be reachable from the keyboard too.
|
|
47
|
+
function onResizeKeyDown(e) {
|
|
48
|
+
if (e.key === 'ArrowLeft') { e.preventDefault(); nudgeDrawerWidth(-bounds.step); }
|
|
49
|
+
else if (e.key === 'ArrowRight') { e.preventDefault(); nudgeDrawerWidth(bounds.step); }
|
|
50
|
+
else if (e.key === 'Home') { e.preventDefault(); nudgeDrawerWidth(-Infinity); }
|
|
51
|
+
else if (e.key === 'End') { e.preventDefault(); nudgeDrawerWidth(Infinity); }
|
|
52
|
+
else if (e.key === 'Enter') { e.preventDefault(); resetDrawerWidth(); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var visibleItems = useMemo(function() {
|
|
56
|
+
if (!drawerOpen || !query.trim()) return drawerItems;
|
|
57
|
+
var q = query.trim().toLowerCase();
|
|
58
|
+
return drawerItems.filter(function(item) { return item.name.toLowerCase().indexOf(q) !== -1; });
|
|
59
|
+
}, [drawerItems, query, drawerOpen]);
|
|
60
|
+
|
|
61
|
+
var buckets = useMemo(function() { return bucketItems(visibleItems); }, [visibleItems]);
|
|
62
|
+
|
|
63
|
+
function renderItem(item) {
|
|
64
|
+
// A COUNT THAT HAS TO BE SEEN FROM WHEREVER YOU ARE.
|
|
65
|
+
//
|
|
66
|
+
// Optional `item.badge` — a number or a short string. It renders as a pill
|
|
67
|
+
// beside the label when the drawer is expanded and as a dot on the icon
|
|
68
|
+
// when it is collapsed, because the collapsed rail is the state most
|
|
69
|
+
// people leave it in and a badge only visible when expanded is a badge
|
|
70
|
+
// that does not do its job.
|
|
71
|
+
//
|
|
72
|
+
// 0, null and undefined all render NOTHING. A pill reading "0" is noise
|
|
73
|
+
// that trains people to stop looking at the pill.
|
|
74
|
+
var badge = item.badge === 0 || item.badge == null || item.badge === '' ? null : item.badge;
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
key={item.name}
|
|
78
|
+
type="button"
|
|
79
|
+
className={'xeplr-nav-drawer-link' + (badge ? ' xeplr-nav-drawer-link-badged' : '')}
|
|
80
|
+
onClick={item.clickHandler}
|
|
81
|
+
// The count belongs in the tooltip too — the collapsed dot says
|
|
82
|
+
// "something", and the hover has to say how many.
|
|
83
|
+
title={badge ? item.name + ' (' + badge + ')' : item.name}
|
|
84
|
+
>
|
|
85
|
+
{item.icon && (
|
|
86
|
+
<span className="xeplr-nav-drawer-icon">
|
|
87
|
+
{item.icon}
|
|
88
|
+
{badge && !drawerOpen && <span className="xeplr-nav-drawer-dot" aria-hidden="true" />}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
{drawerOpen && <span className="xeplr-nav-drawer-label">{item.name}</span>}
|
|
92
|
+
{drawerOpen && badge && <span className="xeplr-nav-drawer-badge">{badge}</span>}
|
|
93
|
+
</button>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<aside
|
|
99
|
+
className={
|
|
100
|
+
'xeplr-nav-drawer'
|
|
101
|
+
+ (drawerOpen ? ' xeplr-nav-drawer-expanded' : ' xeplr-nav-drawer-collapsed')
|
|
102
|
+
+ (drawerResizing ? ' xeplr-nav-drawer-resizing' : '')
|
|
103
|
+
}
|
|
104
|
+
/* Inline width ONLY when expanded — collapsed is the fixed icon rail and
|
|
105
|
+
nav.css keeps owning that number. Inline because it changes per
|
|
106
|
+
pointermove; a stylesheet cannot express a live drag. */
|
|
107
|
+
style={drawerOpen && drawerWidth ? { width: drawerWidth + 'px' } : undefined}
|
|
108
|
+
>
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
className="xeplr-nav-drawer-toggle"
|
|
112
|
+
onClick={toggleDrawer}
|
|
113
|
+
aria-label={drawerOpen ? 'Collapse menu' : 'Expand menu'}
|
|
114
|
+
aria-expanded={drawerOpen}
|
|
115
|
+
>
|
|
116
|
+
{/* ONE MARK AT A TIME.
|
|
117
|
+
expandedLogo is a stacked icon+wordmark lockup — it CONTAINS this
|
|
118
|
+
icon — so rendering both put the same mark on screen twice, one
|
|
119
|
+
above the other. Collapsed, the icon is the brand and the thing you
|
|
120
|
+
click to expand; open, the lockup is the brand and this is only the
|
|
121
|
+
collapse control, which the chevron says better than a second copy
|
|
122
|
+
of the logo. */}
|
|
123
|
+
{drawerOpen && expandedLogo
|
|
124
|
+
? <span className="xeplr-nav-drawer-collapse" aria-hidden="true">«</span>
|
|
125
|
+
: (logo && <img src={logo} alt="" className="xeplr-nav-drawer-logo" />)}
|
|
126
|
+
</button>
|
|
127
|
+
{/* Own row below the toggle, not squeezed inline beside it — a stacked
|
|
128
|
+
lockup needs real height to stay legible, not the icon's 22px. */}
|
|
129
|
+
{drawerOpen && expandedLogo && (
|
|
130
|
+
<img src={expandedLogo} alt="" className="xeplr-nav-drawer-expanded-logo" />
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{drawerOpen && (
|
|
134
|
+
<input
|
|
135
|
+
type="search"
|
|
136
|
+
className="xeplr-nav-drawer-search"
|
|
137
|
+
placeholder="Search"
|
|
138
|
+
value={query}
|
|
139
|
+
onChange={function(e) { setQuery(e.target.value); }}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
<nav className="xeplr-nav-drawer-links">
|
|
144
|
+
{buckets.ungrouped.map(renderItem)}
|
|
145
|
+
{buckets.groupOrder.map(function(groupName) {
|
|
146
|
+
return (
|
|
147
|
+
<div key={groupName} className="xeplr-nav-drawer-group">
|
|
148
|
+
{drawerOpen && <div className="xeplr-nav-drawer-group-label">{groupName}</div>}
|
|
149
|
+
{buckets.groups[groupName].map(renderItem)}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
})}
|
|
153
|
+
{visibleItems.length === 0 && drawerOpen && <div className="xeplr-nav-drawer-empty">No matches</div>}
|
|
154
|
+
</nav>
|
|
155
|
+
|
|
156
|
+
{drawerOpen && drawerPromo && <div className="xeplr-nav-drawer-promo">{drawerPromo}</div>}
|
|
157
|
+
|
|
158
|
+
{!hideFooterIcons && (
|
|
159
|
+
<div className="xeplr-nav-drawer-footer">
|
|
160
|
+
<NotificationsBell notifications={notifications} label={drawerOpen ? 'Notifications' : undefined} />
|
|
161
|
+
<AccountMenu
|
|
162
|
+
placement="top" triggerLabel={drawerOpen ? 'Settings' : undefined}
|
|
163
|
+
user={user} accountItems={accountItems}
|
|
164
|
+
accountOpen={accountOpen} toggleAccount={toggleAccount} closeAccount={closeAccount}
|
|
165
|
+
accountRef={accountRef} logout={logout}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{/* LAST child and absolutely positioned, so it sits over the drawer's
|
|
171
|
+
right border at any height without taking part in the column layout
|
|
172
|
+
above it. Only when expanded: there is nothing to widen on a 60px
|
|
173
|
+
icon rail, and a resize handle there would just fight the toggle. */}
|
|
174
|
+
{drawerOpen && (
|
|
175
|
+
<div
|
|
176
|
+
className="xeplr-nav-drawer-resize"
|
|
177
|
+
role="separator"
|
|
178
|
+
aria-orientation="vertical"
|
|
179
|
+
aria-label="Resize menu"
|
|
180
|
+
aria-valuenow={drawerWidth}
|
|
181
|
+
aria-valuemin={bounds.min}
|
|
182
|
+
aria-valuemax={bounds.max}
|
|
183
|
+
tabIndex={0}
|
|
184
|
+
onPointerDown={startDrawerResize}
|
|
185
|
+
onDoubleClick={resetDrawerWidth}
|
|
186
|
+
onKeyDown={onResizeKeyDown}
|
|
187
|
+
title="Drag to resize — double-click to reset"
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</aside>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default memo(NavDrawer);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import AccountMenu from './AccountMenu.jsx';
|
|
3
|
+
import NotificationsBell from './NotificationsBell.jsx';
|
|
4
|
+
import './nav.css';
|
|
5
|
+
|
|
6
|
+
// A minimal top-right overlay — just the notifications bell + account/settings
|
|
7
|
+
// trigger, no logo/middle slot/header bar around them. Opt-in via NavPage's
|
|
8
|
+
// `floatingSettings` prop, for layouts (e.g. an icon drawer) that want these
|
|
9
|
+
// two floating over the page instead of pinned to the drawer's own footer.
|
|
10
|
+
function NavFloatingSettings({
|
|
11
|
+
user, accountItems, notifications, accountOpen, toggleAccount, closeAccount, accountRef, logout
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="xeplr-nav-floating">
|
|
15
|
+
<NotificationsBell notifications={notifications} />
|
|
16
|
+
<AccountMenu
|
|
17
|
+
user={user} accountItems={accountItems}
|
|
18
|
+
accountOpen={accountOpen} toggleAccount={toggleAccount} closeAccount={closeAccount}
|
|
19
|
+
accountRef={accountRef} logout={logout}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default memo(NavFloatingSettings);
|
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
23
|
-
|
|
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}>
|
package/src/designs/admin.css
CHANGED
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
/* ─── xeplr Admin Matrix ─── */
|
|
4
4
|
|
|
5
5
|
.xeplr-admin-container {
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/designs/auth.css
CHANGED
|
@@ -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
|
-
|
|
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
|
-
}
|
package/src/designs/index.js
CHANGED
|
@@ -9,5 +9,8 @@ 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
|
|
13
|
-
export { default as
|
|
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 NavFloatingSettings } from './NavFloatingSettings.jsx';
|
|
16
|
+
export { default as NotificationsBell } from './NotificationsBell.jsx';
|