@tailor-platform/app-shell 1.15.0 → 1.17.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/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +811 -131
- package/dist/app-shell.js +7155 -5797
- package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
- package/dist/semantic-contrast.test.ts +154 -0
- package/dist/style-entry.d.ts +147 -92
- package/dist/testing.d.ts +152 -95
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +101 -1
- package/dist/theme.bridge.css +91 -0
- package/dist/theme.css +6 -1
- package/dist/themes/_template.css +7 -1
- package/dist/themes/bloom.css +0 -6
- package/dist/themes/cream.css +0 -6
- package/dist/themes/default.css +389 -69
- package/dist/vite-plugin.d.ts +147 -92
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +108 -19
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +181 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
- package/skills/app-shell-patterns/references/migrations.md +55 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
- package/skills/app-shell-patterns/references/fundamental/design-system.md +0 -440
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: hidden
|
|
3
|
+
description: Guard function that denies access and returns 404 Not Found
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/guards/hidden.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# hidden
|
|
9
|
+
|
|
10
|
+
Returns a guard result that denies access and makes the route return 404 Not Found. The route is also hidden from navigation and search.
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
function hidden(): GuardResult;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Return Type
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
{
|
|
22
|
+
type: "hidden";
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { hidden, type Guard } from "@tailor-platform/app-shell";
|
|
30
|
+
|
|
31
|
+
const denyAccess: Guard = () => {
|
|
32
|
+
return hidden();
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## When to Use
|
|
37
|
+
|
|
38
|
+
Use `hidden()` when:
|
|
39
|
+
|
|
40
|
+
- ✅ Access should be denied
|
|
41
|
+
- ✅ User doesn't have required permissions
|
|
42
|
+
- ✅ You want to hide the page completely (404)
|
|
43
|
+
- ✅ Feature is not available for current user
|
|
44
|
+
|
|
45
|
+
## Effects
|
|
46
|
+
|
|
47
|
+
When `hidden()` is returned:
|
|
48
|
+
|
|
49
|
+
- 🚫 Route returns **404 Not Found**
|
|
50
|
+
- 🚫 Hidden from **sidebar navigation**
|
|
51
|
+
- 🚫 Hidden from **CommandPalette** search
|
|
52
|
+
- 🚫 Not shown in **breadcrumbs**
|
|
53
|
+
- 🚫 Direct URL access returns 404
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
### Deny All Access
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const denyAll: Guard = () => hidden();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Role-Based Hiding
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const adminOnly: Guard = ({ context }) => {
|
|
67
|
+
if (context.currentUser?.role !== "admin") {
|
|
68
|
+
return hidden(); // Non-admins see 404
|
|
69
|
+
}
|
|
70
|
+
return pass();
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Permission-Based Hiding
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const requirePermission =
|
|
78
|
+
(perm: string): Guard =>
|
|
79
|
+
({ context }) => {
|
|
80
|
+
if (!context.permissions.includes(perm)) {
|
|
81
|
+
return hidden(); // No permission → 404
|
|
82
|
+
}
|
|
83
|
+
return pass();
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Feature Flag
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const requireBetaFeature: Guard = ({ context }) => {
|
|
91
|
+
if (!context.featureFlags?.beta) {
|
|
92
|
+
return hidden(); // Feature disabled → 404
|
|
93
|
+
}
|
|
94
|
+
return pass();
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Subscription Tier
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const premiumOnly: Guard = ({ context }) => {
|
|
102
|
+
if (context.currentUser?.plan !== "premium") {
|
|
103
|
+
return hidden(); // Free users see 404
|
|
104
|
+
}
|
|
105
|
+
return pass();
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Conditional Hiding
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const conditionalHide: Guard = ({ context }) => {
|
|
113
|
+
// Multiple conditions
|
|
114
|
+
if (!context.currentUser || context.currentUser.suspended || !context.currentUser.emailVerified) {
|
|
115
|
+
return hidden();
|
|
116
|
+
}
|
|
117
|
+
return pass();
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Async Permission Check
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const asyncHide: Guard = async ({ context }) => {
|
|
125
|
+
const allowed = await fetch("/api/check-access");
|
|
126
|
+
if (!allowed) {
|
|
127
|
+
return hidden();
|
|
128
|
+
}
|
|
129
|
+
return pass();
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## vs redirectTo()
|
|
134
|
+
|
|
135
|
+
| Aspect | hidden() | redirectTo() |
|
|
136
|
+
| -------------- | -------------------- | ------------------------ |
|
|
137
|
+
| **Result** | 404 Not Found | Redirect to page |
|
|
138
|
+
| **URL** | Stays same | Changes to new URL |
|
|
139
|
+
| **Navigation** | Hidden | Not affected |
|
|
140
|
+
| **Use Case** | Deny access silently | Send to login/error page |
|
|
141
|
+
|
|
142
|
+
### Example: When to Use Each
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// Use hidden() - Silently deny access
|
|
146
|
+
const adminFeature: Guard = ({ context }) => {
|
|
147
|
+
if (!isAdmin(context)) {
|
|
148
|
+
return hidden(); // Non-admins don't even see this exists
|
|
149
|
+
}
|
|
150
|
+
return pass();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Use redirectTo() - Explicitly redirect
|
|
154
|
+
const requireAuth: Guard = ({ context }) => {
|
|
155
|
+
if (!context.currentUser) {
|
|
156
|
+
return redirectTo("/login"); // Unauthenticated users go to login
|
|
157
|
+
}
|
|
158
|
+
return pass();
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Navigation Impact
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// Before guard (user is not admin):
|
|
166
|
+
Sidebar:
|
|
167
|
+
› Dashboard
|
|
168
|
+
› Products
|
|
169
|
+
› Admin Panel ← Visible
|
|
170
|
+
|
|
171
|
+
// After guard returns hidden():
|
|
172
|
+
Sidebar:
|
|
173
|
+
› Dashboard
|
|
174
|
+
› Products
|
|
175
|
+
← Admin Panel disappeared
|
|
176
|
+
|
|
177
|
+
// Direct URL access: /admin
|
|
178
|
+
// Result: 404 Not Found
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## WithGuard Component
|
|
182
|
+
|
|
183
|
+
`hidden()` works with `WithGuard` to hide UI elements:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { WithGuard, hidden, pass } from "@tailor-platform/app-shell";
|
|
187
|
+
|
|
188
|
+
const isAdmin = ({ context }) =>
|
|
189
|
+
context.currentUser?.role === "admin" ? pass() : hidden();
|
|
190
|
+
|
|
191
|
+
<WithGuard guards={[isAdmin]}>
|
|
192
|
+
<Button>Delete All Users</Button>
|
|
193
|
+
</WithGuard>
|
|
194
|
+
// Non-admins: button doesn't render
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Related
|
|
198
|
+
|
|
199
|
+
- [pass()](./pass.md) - Allow access and continue
|
|
200
|
+
- [redirectTo()](./redirect-to.md) - Redirect to another page
|
|
201
|
+
- [Guards Overview](./overview.md) - Complete guard system guide
|
|
202
|
+
- [WithGuard Component](../../components/with-guard.md) - Component-level guards
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Guards Overview
|
|
3
|
+
description: Access control system for routes and components using guard functions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/guards/overview.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# Guards Overview
|
|
9
|
+
|
|
10
|
+
Guards are functions that control access to routes and UI elements in AppShell. They provide a flexible, composable way to implement permission-based and role-based access control.
|
|
11
|
+
|
|
12
|
+
## How Guards Work
|
|
13
|
+
|
|
14
|
+
Guards are executed sequentially and can return one of three results:
|
|
15
|
+
|
|
16
|
+
- **`pass()`** - Allow access, continue to next guard
|
|
17
|
+
- **`hidden()`** - Deny access, return 404
|
|
18
|
+
- **`redirectTo(path)`** - Redirect to another page
|
|
19
|
+
|
|
20
|
+
If any guard returns `hidden()` or `redirectTo()`, execution stops immediately.
|
|
21
|
+
|
|
22
|
+
## Guard Function Signature
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
type Guard = (ctx: GuardContext) => GuardResult | Promise<GuardResult>;
|
|
26
|
+
|
|
27
|
+
type GuardContext = {
|
|
28
|
+
context: ContextData; // Your custom context from AppShell
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type GuardResult = { type: "pass" } | { type: "hidden" } | { type: "redirect"; to: string };
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Guard Functions
|
|
35
|
+
|
|
36
|
+
### pass()
|
|
37
|
+
|
|
38
|
+
Allows access and continues to the next guard.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { pass } from "@tailor-platform/app-shell";
|
|
42
|
+
|
|
43
|
+
const allowAll: Guard = () => pass();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
[Full Reference →](./pass.md)
|
|
47
|
+
|
|
48
|
+
### hidden()
|
|
49
|
+
|
|
50
|
+
Denies access and shows 404 Not Found.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { hidden } from "@tailor-platform/app-shell";
|
|
54
|
+
|
|
55
|
+
const denyAll: Guard = () => hidden();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
[Full Reference →](./hidden.md)
|
|
59
|
+
|
|
60
|
+
### redirectTo(path)
|
|
61
|
+
|
|
62
|
+
Redirects to another page.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { redirectTo } from "@tailor-platform/app-shell";
|
|
66
|
+
|
|
67
|
+
const redirectToLogin: Guard = () => redirectTo("/login");
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
[Full Reference →](./redirect-to.md)
|
|
71
|
+
|
|
72
|
+
## Usage Locations
|
|
73
|
+
|
|
74
|
+
### Route-Level Guards
|
|
75
|
+
|
|
76
|
+
Applied to modules and resources:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { defineModule, pass, hidden } from "@tailor-platform/app-shell";
|
|
80
|
+
|
|
81
|
+
const adminModule = defineModule({
|
|
82
|
+
path: "admin",
|
|
83
|
+
component: AdminPage,
|
|
84
|
+
guards: [
|
|
85
|
+
({ context }) => {
|
|
86
|
+
return context.currentUser?.role === "admin" ? pass() : hidden();
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Component-Level Guards
|
|
93
|
+
|
|
94
|
+
Applied to UI elements with `WithGuard`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { WithGuard, pass, hidden } from "@tailor-platform/app-shell";
|
|
98
|
+
|
|
99
|
+
const isAdmin = ({ context }) =>
|
|
100
|
+
context.currentUser?.role === "admin" ? pass() : hidden();
|
|
101
|
+
|
|
102
|
+
<WithGuard guards={[isAdmin]}>
|
|
103
|
+
<Button>Delete All</Button>
|
|
104
|
+
</WithGuard>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Context Data
|
|
108
|
+
|
|
109
|
+
Guards receive context data from AppShell:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Define your context type
|
|
113
|
+
declare module "@tailor-platform/app-shell" {
|
|
114
|
+
interface AppShellRegister {
|
|
115
|
+
contextData: {
|
|
116
|
+
currentUser: User | null;
|
|
117
|
+
permissions: string[];
|
|
118
|
+
featureFlags: FeatureFlags;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Pass context to AppShell
|
|
124
|
+
<AppShell
|
|
125
|
+
modules={modules}
|
|
126
|
+
contextData={{
|
|
127
|
+
currentUser,
|
|
128
|
+
permissions,
|
|
129
|
+
featureFlags,
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
{/* ... */}
|
|
133
|
+
</AppShell>
|
|
134
|
+
|
|
135
|
+
// Access in guards
|
|
136
|
+
const guard: Guard = ({ context }) => {
|
|
137
|
+
// context is fully typed!
|
|
138
|
+
if (context.currentUser) {
|
|
139
|
+
return pass();
|
|
140
|
+
}
|
|
141
|
+
return hidden();
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Common Patterns
|
|
146
|
+
|
|
147
|
+
### Authentication Check
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const requireAuth: Guard = ({ context }) => {
|
|
151
|
+
if (!context.currentUser) {
|
|
152
|
+
return redirectTo("/login");
|
|
153
|
+
}
|
|
154
|
+
return pass();
|
|
155
|
+
};
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Role-Based Access
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const requireAdmin: Guard = ({ context }) => {
|
|
162
|
+
if (context.currentUser?.role !== "admin") {
|
|
163
|
+
return hidden();
|
|
164
|
+
}
|
|
165
|
+
return pass();
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Permission-Based Access
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const requirePermission =
|
|
173
|
+
(permission: string): Guard =>
|
|
174
|
+
({ context }) => {
|
|
175
|
+
if (!context.permissions.includes(permission)) {
|
|
176
|
+
return hidden();
|
|
177
|
+
}
|
|
178
|
+
return pass();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Usage
|
|
182
|
+
guards: [requirePermission("users:delete")];
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Feature Flags
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const requireFeature =
|
|
189
|
+
(flag: string): Guard =>
|
|
190
|
+
({ context }) => {
|
|
191
|
+
if (!context.featureFlags[flag]) {
|
|
192
|
+
return hidden();
|
|
193
|
+
}
|
|
194
|
+
return pass();
|
|
195
|
+
};
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Async Permission Check
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const checkApiPermission: Guard = async ({ context }) => {
|
|
202
|
+
const response = await fetch("/api/permissions/admin");
|
|
203
|
+
const { allowed } = await response.json();
|
|
204
|
+
return allowed ? pass() : hidden();
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Multiple Guards (AND logic)
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const guards = [requireAuth, requireAdmin, requireFeature("beta")];
|
|
212
|
+
|
|
213
|
+
// All must pass() for access to be granted
|
|
214
|
+
defineModule({
|
|
215
|
+
path: "admin",
|
|
216
|
+
guards: guards,
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Reusable Guard Library
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// guards.ts
|
|
224
|
+
export const requireAuth: Guard = ({ context }) => {
|
|
225
|
+
return context.currentUser ? pass() : redirectTo("/login");
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export const requireRole =
|
|
229
|
+
(role: string): Guard =>
|
|
230
|
+
({ context }) => {
|
|
231
|
+
return context.currentUser?.role === role ? pass() : hidden();
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const requirePermission =
|
|
235
|
+
(perm: string): Guard =>
|
|
236
|
+
({ context }) => {
|
|
237
|
+
return context.permissions.includes(perm) ? pass() : hidden();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Usage across application
|
|
241
|
+
import { requireAuth, requireRole } from "./guards";
|
|
242
|
+
|
|
243
|
+
defineModule({
|
|
244
|
+
path: "admin",
|
|
245
|
+
guards: [requireAuth, requireRole("admin")],
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Guard Execution Order
|
|
250
|
+
|
|
251
|
+
Guards are executed sequentially in the order they're defined:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
guards: [guardA, guardB, guardC];
|
|
255
|
+
|
|
256
|
+
// Execution:
|
|
257
|
+
// 1. guardA runs → if not pass(), stop
|
|
258
|
+
// 2. guardB runs → if not pass(), stop
|
|
259
|
+
// 3. guardC runs → if not pass(), stop
|
|
260
|
+
// 4. All passed → grant access
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Navigation Behavior
|
|
264
|
+
|
|
265
|
+
When guards return `hidden()`:
|
|
266
|
+
|
|
267
|
+
- Route returns 404 Not Found
|
|
268
|
+
- Item hidden from sidebar navigation
|
|
269
|
+
- Item hidden from CommandPalette
|
|
270
|
+
- Breadcrumbs don't show the page
|
|
271
|
+
|
|
272
|
+
When guards return `redirectTo()`:
|
|
273
|
+
|
|
274
|
+
- User is redirected immediately
|
|
275
|
+
- Original URL is not accessible
|
|
276
|
+
- Useful for login flows
|
|
277
|
+
|
|
278
|
+
## Best Practices
|
|
279
|
+
|
|
280
|
+
### Do:
|
|
281
|
+
|
|
282
|
+
- ✅ Keep guards simple and focused
|
|
283
|
+
- ✅ Reuse guards across routes and components
|
|
284
|
+
- ✅ Use descriptive names (requireAuth, not guard1)
|
|
285
|
+
- ✅ Return quickly for better performance
|
|
286
|
+
- ✅ Cache expensive checks when possible
|
|
287
|
+
|
|
288
|
+
### Don't:
|
|
289
|
+
|
|
290
|
+
- ❌ Make guards too complex (extract logic)
|
|
291
|
+
- ❌ Have side effects in guards (logging OK)
|
|
292
|
+
- ❌ Duplicate guard logic (DRY principle)
|
|
293
|
+
- ❌ Forget to handle async errors
|
|
294
|
+
- ❌ Use redirectTo() in WithGuard (not supported)
|
|
295
|
+
|
|
296
|
+
## TypeScript
|
|
297
|
+
|
|
298
|
+
Full type safety:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import { type Guard, type GuardContext, type GuardResult } from "@tailor-platform/app-shell";
|
|
302
|
+
|
|
303
|
+
const myGuard: Guard = (ctx: GuardContext): GuardResult => {
|
|
304
|
+
// Fully typed context
|
|
305
|
+
return pass();
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Async guard
|
|
309
|
+
const asyncGuard: Guard = async (ctx): Promise<GuardResult> => {
|
|
310
|
+
await someAsyncCheck();
|
|
311
|
+
return pass();
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Performance
|
|
316
|
+
|
|
317
|
+
### Sync Guards (Fast)
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const isAdmin: Guard = ({ context }) => {
|
|
321
|
+
return context.currentUser?.role === "admin" ? pass() : hidden();
|
|
322
|
+
};
|
|
323
|
+
// ~0ms - Instant
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Async Guards (Slower)
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const checkPermission: Guard = async ({ context }) => {
|
|
330
|
+
const allowed = await fetch("/api/check");
|
|
331
|
+
return allowed ? pass() : hidden();
|
|
332
|
+
};
|
|
333
|
+
// ~50-200ms - Network request
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Tip:** Prefer sync guards when possible. Cache API results in context data.
|
|
337
|
+
|
|
338
|
+
## Comparison: Route vs Component Guards
|
|
339
|
+
|
|
340
|
+
| Aspect | Route Guards | Component Guards (WithGuard) |
|
|
341
|
+
| ------------------------- | --------------------------- | ---------------------------- |
|
|
342
|
+
| **Location** | defineModule/defineResource | WithGuard component |
|
|
343
|
+
| **Supports pass()** | ✅ Yes | ✅ Yes |
|
|
344
|
+
| **Supports hidden()** | ✅ Yes | ✅ Yes |
|
|
345
|
+
| **Supports redirectTo()** | ✅ Yes | ❌ No |
|
|
346
|
+
| **Execution** | Before route loads | During render |
|
|
347
|
+
| **Use Case** | Page access control | UI element visibility |
|
|
348
|
+
|
|
349
|
+
## Related
|
|
350
|
+
|
|
351
|
+
- [pass()](./pass.md) - Allow access guard function
|
|
352
|
+
- [hidden()](./hidden.md) - Hide/deny access guard function
|
|
353
|
+
- [redirectTo()](./redirect-to.md) - Redirect guard function
|
|
354
|
+
- [WithGuard Component](../../components/with-guard.md) - Component-level guards
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: pass
|
|
3
|
+
description: Guard function that allows access and continues to the next guard
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/hooks/guards/pass.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# pass
|
|
9
|
+
|
|
10
|
+
Returns a guard result that allows access and continues to the next guard in the chain.
|
|
11
|
+
|
|
12
|
+
## Signature
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
function pass(): GuardResult;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Return Type
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
{
|
|
22
|
+
type: "pass";
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { pass, type Guard } from "@tailor-platform/app-shell";
|
|
30
|
+
|
|
31
|
+
const allowAccess: Guard = () => {
|
|
32
|
+
return pass();
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## When to Use
|
|
37
|
+
|
|
38
|
+
Use `pass()` when:
|
|
39
|
+
|
|
40
|
+
- ✅ Access should be granted
|
|
41
|
+
- ✅ User meets the required conditions
|
|
42
|
+
- ✅ You want to continue to the next guard
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
### Always Allow
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const allowAll: Guard = () => pass();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Conditional Access
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const requireAuth: Guard = ({ context }) => {
|
|
56
|
+
if (context.currentUser) {
|
|
57
|
+
return pass(); // User is authenticated
|
|
58
|
+
}
|
|
59
|
+
return redirectTo("/login");
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Role Check
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const requireAdmin: Guard = ({ context }) => {
|
|
67
|
+
if (context.currentUser?.role === "admin") {
|
|
68
|
+
return pass(); // User is admin
|
|
69
|
+
}
|
|
70
|
+
return hidden();
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Permission Check
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const requirePermission =
|
|
78
|
+
(perm: string): Guard =>
|
|
79
|
+
({ context }) => {
|
|
80
|
+
if (context.permissions.includes(perm)) {
|
|
81
|
+
return pass(); // User has permission
|
|
82
|
+
}
|
|
83
|
+
return hidden();
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Multiple Conditions
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const complexGuard: Guard = ({ context }) => {
|
|
91
|
+
// Check multiple conditions
|
|
92
|
+
if (context.currentUser && context.currentUser.active && !context.currentUser.suspended) {
|
|
93
|
+
return pass(); // All conditions met
|
|
94
|
+
}
|
|
95
|
+
return hidden();
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Async Guard
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const asyncGuard: Guard = async ({ context }) => {
|
|
103
|
+
const isValid = await validateToken(context.token);
|
|
104
|
+
if (isValid) {
|
|
105
|
+
return pass();
|
|
106
|
+
}
|
|
107
|
+
return redirectTo("/login");
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Guard Chaining
|
|
112
|
+
|
|
113
|
+
When `pass()` is returned, the next guard in the array executes:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
guards: [
|
|
117
|
+
guardA, // Returns pass() → Continue to guardB
|
|
118
|
+
guardB, // Returns pass() → Continue to guardC
|
|
119
|
+
guardC, // Returns pass() → Grant access
|
|
120
|
+
];
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
If all guards return `pass()`, access is granted.
|
|
124
|
+
|
|
125
|
+
## Related
|
|
126
|
+
|
|
127
|
+
- [hidden()](./hidden.md) - Deny access and return 404
|
|
128
|
+
- [redirectTo()](./redirect-to.md) - Redirect to another page
|
|
129
|
+
- [Guards Overview](./overview.md) - Complete guard system guide
|