@lukoweb/apitogo 0.2.30 → 0.2.34
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/cli/cli.js +794 -248
- package/dist/declarations/app/entry.server.d.ts +2 -2
- package/dist/declarations/app/main.d.ts +3 -1
- package/dist/declarations/cli/platform/client/platform-api.d.ts +16 -5
- package/dist/declarations/cli/platform/helpers/deploy-artifact-hash.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/package-files.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/platform-plan.d.ts +1 -1
- package/dist/declarations/cli/platform/helpers/zip.d.ts +1 -1
- package/dist/declarations/cli/platform/tools/infrastructure.d.ts +0 -4
- package/dist/declarations/config/effective-redirects.d.ts +6 -0
- package/dist/declarations/config/header-navigation-guard.d.ts +2 -0
- package/dist/declarations/config/resolve-modules.d.ts +1 -4
- package/dist/declarations/config/validators/HeaderNavigationSchema.d.ts +1 -2
- package/docs/cli/commands.md +151 -75
- package/docs/cli/go-live.md +135 -123
- package/docs/cli/walkthrough.md +40 -1
- package/docs/configuration/gateway.md +52 -0
- package/docs/guides/customer-account.md +33 -2
- package/docs/mcp/tools.md +101 -102
- package/package.json +2 -1
- package/src/app/demo.tsx +78 -77
- package/src/app/entry.client.tsx +115 -107
- package/src/app/entry.server.tsx +164 -160
- package/src/app/main.tsx +175 -167
- package/src/app/standalone.tsx +61 -60
- package/src/config/effective-redirects.ts +31 -0
- package/src/config/header-navigation-guard.ts +13 -0
- package/src/config/resolve-modules.ts +331 -354
- package/src/config/v2/home-redirect.ts +104 -98
- package/src/config/v2/index.ts +127 -126
- package/src/config/v2/ledger.ts +242 -235
- package/src/config/v2/resolve.ts +12 -4
- package/src/config/v2/rules.ts +716 -714
- package/src/config/v2/services.ts +799 -663
- package/src/config/v2/workspace.ts +17 -1
- package/src/config/validators/HeaderNavigationSchema.ts +42 -44
- package/src/lib/assets/powered-by-icon.png +0 -0
- package/src/lib/authentication/providers/dev-portal-auth-pages.tsx +667 -602
- package/src/lib/authentication/providers/dev-portal.tsx +476 -448
- package/src/lib/components/HeaderNavigation.tsx +176 -176
- package/src/lib/components/MobileTopNavigation.tsx +283 -283
- package/src/lib/components/navigation/PoweredByZudoku.tsx +42 -39
- package/src/vite/plugin-offroot-resolve.ts +68 -67
- package/src/vite/prerender/prerender.ts +242 -242
|
@@ -1,283 +1,283 @@
|
|
|
1
|
-
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
2
|
-
import { Separator } from "@lukoweb/apitogo/ui/Separator.js";
|
|
3
|
-
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
|
4
|
-
import { deepEqual } from "fast-equals";
|
|
5
|
-
import {
|
|
6
|
-
ChevronDownIcon,
|
|
7
|
-
LogOutIcon,
|
|
8
|
-
MenuIcon,
|
|
9
|
-
type LucideIcon,
|
|
10
|
-
} from "lucide-react";
|
|
11
|
-
import { useState } from "react";
|
|
12
|
-
import { Link } from "react-router";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "../../config/validators/HeaderNavigationSchema.js";
|
|
18
|
-
import { withoutMarketingCtaHeaderNavigation } from "../authentication/header-nav-filter.js";
|
|
19
|
-
import { useAuth } from "../authentication/hook.js";
|
|
20
|
-
import {
|
|
21
|
-
Collapsible,
|
|
22
|
-
CollapsibleContent,
|
|
23
|
-
CollapsibleTrigger,
|
|
24
|
-
} from "../ui/Collapsible.js";
|
|
25
|
-
import {
|
|
26
|
-
Drawer,
|
|
27
|
-
DrawerContent,
|
|
28
|
-
DrawerTitle,
|
|
29
|
-
DrawerTrigger,
|
|
30
|
-
} from "../ui/Drawer.js";
|
|
31
|
-
import { AnchorLink } from "./AnchorLink.js";
|
|
32
|
-
import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
|
|
33
|
-
import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
|
|
34
|
-
import { getFirstMatchingPath, shouldShowItem } from "./navigation/utils.js";
|
|
35
|
-
import { PageProgress } from "./PageProgress.js";
|
|
36
|
-
import { Slot } from "./Slot.js";
|
|
37
|
-
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
38
|
-
|
|
39
|
-
const MobileHeaderNavLink = ({
|
|
40
|
-
item,
|
|
41
|
-
onClick,
|
|
42
|
-
}: {
|
|
43
|
-
item: HeaderNavLinkItem;
|
|
44
|
-
onClick: () => void;
|
|
45
|
-
}) => {
|
|
46
|
-
const Icon = item.icon as LucideIcon | undefined;
|
|
47
|
-
return (
|
|
48
|
-
<AnchorLink
|
|
49
|
-
to={item.to}
|
|
50
|
-
target={item.target}
|
|
51
|
-
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
52
|
-
onClick={onClick}
|
|
53
|
-
className="flex items-center font-medium gap-2 py-2 text-foreground/80 hover:text-foreground"
|
|
54
|
-
>
|
|
55
|
-
{Icon && <Icon size={16} />}
|
|
56
|
-
{item.label}
|
|
57
|
-
</AnchorLink>
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const MobileHeaderNavItem = ({
|
|
62
|
-
item,
|
|
63
|
-
onNavigate,
|
|
64
|
-
}: {
|
|
65
|
-
item: HeaderNavItem;
|
|
66
|
-
onNavigate: () => void;
|
|
67
|
-
}) => {
|
|
68
|
-
if ("to" in item) {
|
|
69
|
-
const Icon = item.icon as LucideIcon | undefined;
|
|
70
|
-
return (
|
|
71
|
-
<li className="w-full">
|
|
72
|
-
<AnchorLink
|
|
73
|
-
to={item.to}
|
|
74
|
-
target={item.target}
|
|
75
|
-
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
76
|
-
onClick={onNavigate}
|
|
77
|
-
className="flex items-center gap-2 py-2 text-base font-medium"
|
|
78
|
-
>
|
|
79
|
-
{Icon && <Icon size={16} />}
|
|
80
|
-
{item.label}
|
|
81
|
-
</AnchorLink>
|
|
82
|
-
</li>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<li className="w-full">
|
|
88
|
-
<Collapsible>
|
|
89
|
-
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-base font-medium group">
|
|
90
|
-
{item.label}
|
|
91
|
-
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
|
92
|
-
</CollapsibleTrigger>
|
|
93
|
-
<CollapsibleContent>
|
|
94
|
-
<ul className="flex flex-col border-l ms-1 ps-3 my-1">
|
|
95
|
-
{item.items.map((subItem) =>
|
|
96
|
-
isHeaderNavGroup(subItem) ? (
|
|
97
|
-
<li key={subItem.label} className="flex flex-col">
|
|
98
|
-
<div className="text-sm text-muted-foreground py-2">
|
|
99
|
-
{subItem.label}
|
|
100
|
-
</div>
|
|
101
|
-
{subItem.items.map((link) => (
|
|
102
|
-
<MobileHeaderNavLink
|
|
103
|
-
key={link.to}
|
|
104
|
-
item={link}
|
|
105
|
-
onClick={onNavigate}
|
|
106
|
-
/>
|
|
107
|
-
))}
|
|
108
|
-
</li>
|
|
109
|
-
) : (
|
|
110
|
-
<li key={subItem.to}>
|
|
111
|
-
<MobileHeaderNavLink item={subItem} onClick={onNavigate} />
|
|
112
|
-
</li>
|
|
113
|
-
),
|
|
114
|
-
)}
|
|
115
|
-
</ul>
|
|
116
|
-
</CollapsibleContent>
|
|
117
|
-
</Collapsible>
|
|
118
|
-
</li>
|
|
119
|
-
);
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export const MobileTopNavigation = () => {
|
|
123
|
-
const context = useZudoku();
|
|
124
|
-
const authState = useAuth();
|
|
125
|
-
const currentNav = useCurrentNavigation();
|
|
126
|
-
|
|
127
|
-
const {
|
|
128
|
-
options: { header, navigation = [], site },
|
|
129
|
-
getProfileMenuItems,
|
|
130
|
-
} = context;
|
|
131
|
-
const headerNavigation = withoutMarketingCtaHeaderNavigation(
|
|
132
|
-
header?.navigation ?? [],
|
|
133
|
-
);
|
|
134
|
-
const { isAuthenticated, profile, isAuthEnabled, login, signup } = authState;
|
|
135
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
136
|
-
|
|
137
|
-
const accountItems = getProfileMenuItems();
|
|
138
|
-
const filteredItems = navigation.filter(
|
|
139
|
-
shouldShowItem({ auth: authState, context }),
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<Drawer
|
|
144
|
-
direction={site?.dir === "rtl" ? "left" : "right"}
|
|
145
|
-
open={drawerOpen}
|
|
146
|
-
onOpenChange={setDrawerOpen}
|
|
147
|
-
>
|
|
148
|
-
<div className="flex lg:hidden justify-self-end">
|
|
149
|
-
<DrawerTrigger className="lg:hidden">
|
|
150
|
-
<MenuIcon size={22} />
|
|
151
|
-
</DrawerTrigger>
|
|
152
|
-
<PageProgress />
|
|
153
|
-
</div>
|
|
154
|
-
<DrawerContent
|
|
155
|
-
className="lg:hidden h-dvh inset-e-0 start-auto w-[340px] rounded-none"
|
|
156
|
-
aria-describedby={undefined}
|
|
157
|
-
>
|
|
158
|
-
<div className="py-2 h-full flex flex-col">
|
|
159
|
-
<div className="flex-1 overflow-y-auto overscroll-none">
|
|
160
|
-
<VisuallyHidden>
|
|
161
|
-
<DrawerTitle>Navigation</DrawerTitle>
|
|
162
|
-
</VisuallyHidden>
|
|
163
|
-
<ul className="flex flex-col gap-1 px-4">
|
|
164
|
-
{filteredItems.map((item) => {
|
|
165
|
-
if (item.type === "separator") {
|
|
166
|
-
return <Separator className="my-2" key={item.label} />;
|
|
167
|
-
}
|
|
168
|
-
if (item.type === "section" || item.type === "filter") {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
const path = getFirstMatchingPath(item);
|
|
172
|
-
const isActive = deepEqual(currentNav.topNavItem, item);
|
|
173
|
-
return (
|
|
174
|
-
<li key={item.label}>
|
|
175
|
-
<Link
|
|
176
|
-
to={path}
|
|
177
|
-
onClick={() => setDrawerOpen(false)}
|
|
178
|
-
className={`flex items-center gap-2 py-2 text-base font-medium ${isActive ? "text-foreground" : "text-foreground/75 hover:text-foreground"}`}
|
|
179
|
-
>
|
|
180
|
-
{item.icon && <item.icon size={16} />}
|
|
181
|
-
{item.label}
|
|
182
|
-
</Link>
|
|
183
|
-
</li>
|
|
184
|
-
);
|
|
185
|
-
})}
|
|
186
|
-
{filteredItems.length > 0 && headerNavigation.length > 0 && (
|
|
187
|
-
<Separator className="my-2" />
|
|
188
|
-
)}
|
|
189
|
-
<li className="empty:hidden">
|
|
190
|
-
<Slot.Target name="top-navigation-side" />
|
|
191
|
-
</li>
|
|
192
|
-
{headerNavigation.map((item) => (
|
|
193
|
-
<MobileHeaderNavItem
|
|
194
|
-
key={item.label}
|
|
195
|
-
item={item}
|
|
196
|
-
onNavigate={() => setDrawerOpen(false)}
|
|
197
|
-
/>
|
|
198
|
-
))}
|
|
199
|
-
{isAuthEnabled && isAuthenticated && (
|
|
200
|
-
<>
|
|
201
|
-
<Separator className="my-2" />
|
|
202
|
-
<li className="py-2">
|
|
203
|
-
<div className="text-base font-medium">
|
|
204
|
-
{profile?.name ?? "My Account"}
|
|
205
|
-
</div>
|
|
206
|
-
{profile?.email && profile.email !== profile?.name && (
|
|
207
|
-
<div className="text-sm text-muted-foreground">
|
|
208
|
-
{profile.email}
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
</li>
|
|
212
|
-
{accountItems.map((i) => (
|
|
213
|
-
<li key={i.label}>
|
|
214
|
-
<Link
|
|
215
|
-
to={i.path ?? ""}
|
|
216
|
-
target={i.target}
|
|
217
|
-
rel={
|
|
218
|
-
i.target === "_blank"
|
|
219
|
-
? "noopener noreferrer"
|
|
220
|
-
: undefined
|
|
221
|
-
}
|
|
222
|
-
onClick={() => setDrawerOpen(false)}
|
|
223
|
-
className="flex items-center py-2 text-base font-medium text-foreground/75 hover:text-foreground"
|
|
224
|
-
>
|
|
225
|
-
{i.label}
|
|
226
|
-
</Link>
|
|
227
|
-
</li>
|
|
228
|
-
))}
|
|
229
|
-
</>
|
|
230
|
-
)}
|
|
231
|
-
</ul>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="border-t shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] px-4 pt-3 flex flex-col gap-2">
|
|
234
|
-
<div className="flex items-center justify-between gap-2">
|
|
235
|
-
{isAuthEnabled &&
|
|
236
|
-
(isAuthenticated ? (
|
|
237
|
-
<Button asChild variant="outline">
|
|
238
|
-
<Link
|
|
239
|
-
to="/signout"
|
|
240
|
-
onClick={() => setDrawerOpen(false)}
|
|
241
|
-
className="flex items-center gap-2"
|
|
242
|
-
>
|
|
243
|
-
<LogOutIcon
|
|
244
|
-
size={16}
|
|
245
|
-
strokeWidth={1}
|
|
246
|
-
absoluteStrokeWidth
|
|
247
|
-
/>
|
|
248
|
-
Log out
|
|
249
|
-
</Link>
|
|
250
|
-
</Button>
|
|
251
|
-
) : (
|
|
252
|
-
<div className="flex flex-1 items-center gap-2">
|
|
253
|
-
<Button
|
|
254
|
-
variant="ghost"
|
|
255
|
-
className="font-semibold"
|
|
256
|
-
onClick={() => {
|
|
257
|
-
setDrawerOpen(false);
|
|
258
|
-
void login();
|
|
259
|
-
}}
|
|
260
|
-
>
|
|
261
|
-
Log in
|
|
262
|
-
</Button>
|
|
263
|
-
<Button
|
|
264
|
-
onClick={() => {
|
|
265
|
-
setDrawerOpen(false);
|
|
266
|
-
void signup();
|
|
267
|
-
}}
|
|
268
|
-
>
|
|
269
|
-
Get started
|
|
270
|
-
</Button>
|
|
271
|
-
</div>
|
|
272
|
-
))}
|
|
273
|
-
<ThemeSwitch variant="compact" />
|
|
274
|
-
</div>
|
|
275
|
-
{site?.showPoweredBy !== false && (
|
|
276
|
-
<PoweredByZudoku className="grow-0 justify-center gap-1" />
|
|
277
|
-
)}
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
280
|
-
</DrawerContent>
|
|
281
|
-
</Drawer>
|
|
282
|
-
);
|
|
283
|
-
};
|
|
1
|
+
import { Button } from "@lukoweb/apitogo/ui/Button.js";
|
|
2
|
+
import { Separator } from "@lukoweb/apitogo/ui/Separator.js";
|
|
3
|
+
import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
|
4
|
+
import { deepEqual } from "fast-equals";
|
|
5
|
+
import {
|
|
6
|
+
ChevronDownIcon,
|
|
7
|
+
LogOutIcon,
|
|
8
|
+
MenuIcon,
|
|
9
|
+
type LucideIcon,
|
|
10
|
+
} from "lucide-react";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { Link } from "react-router";
|
|
13
|
+
import { isHeaderNavGroup } from "../../config/header-navigation-guard.js";
|
|
14
|
+
import type {
|
|
15
|
+
HeaderNavItem,
|
|
16
|
+
HeaderNavLinkItem,
|
|
17
|
+
} from "../../config/validators/HeaderNavigationSchema.js";
|
|
18
|
+
import { withoutMarketingCtaHeaderNavigation } from "../authentication/header-nav-filter.js";
|
|
19
|
+
import { useAuth } from "../authentication/hook.js";
|
|
20
|
+
import {
|
|
21
|
+
Collapsible,
|
|
22
|
+
CollapsibleContent,
|
|
23
|
+
CollapsibleTrigger,
|
|
24
|
+
} from "../ui/Collapsible.js";
|
|
25
|
+
import {
|
|
26
|
+
Drawer,
|
|
27
|
+
DrawerContent,
|
|
28
|
+
DrawerTitle,
|
|
29
|
+
DrawerTrigger,
|
|
30
|
+
} from "../ui/Drawer.js";
|
|
31
|
+
import { AnchorLink } from "./AnchorLink.js";
|
|
32
|
+
import { useCurrentNavigation, useZudoku } from "./context/ZudokuContext.js";
|
|
33
|
+
import { PoweredByZudoku } from "./navigation/PoweredByZudoku.js";
|
|
34
|
+
import { getFirstMatchingPath, shouldShowItem } from "./navigation/utils.js";
|
|
35
|
+
import { PageProgress } from "./PageProgress.js";
|
|
36
|
+
import { Slot } from "./Slot.js";
|
|
37
|
+
import { ThemeSwitch } from "./ThemeSwitch.js";
|
|
38
|
+
|
|
39
|
+
const MobileHeaderNavLink = ({
|
|
40
|
+
item,
|
|
41
|
+
onClick,
|
|
42
|
+
}: {
|
|
43
|
+
item: HeaderNavLinkItem;
|
|
44
|
+
onClick: () => void;
|
|
45
|
+
}) => {
|
|
46
|
+
const Icon = item.icon as LucideIcon | undefined;
|
|
47
|
+
return (
|
|
48
|
+
<AnchorLink
|
|
49
|
+
to={item.to}
|
|
50
|
+
target={item.target}
|
|
51
|
+
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
className="flex items-center font-medium gap-2 py-2 text-foreground/80 hover:text-foreground"
|
|
54
|
+
>
|
|
55
|
+
{Icon && <Icon size={16} />}
|
|
56
|
+
{item.label}
|
|
57
|
+
</AnchorLink>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const MobileHeaderNavItem = ({
|
|
62
|
+
item,
|
|
63
|
+
onNavigate,
|
|
64
|
+
}: {
|
|
65
|
+
item: HeaderNavItem;
|
|
66
|
+
onNavigate: () => void;
|
|
67
|
+
}) => {
|
|
68
|
+
if ("to" in item) {
|
|
69
|
+
const Icon = item.icon as LucideIcon | undefined;
|
|
70
|
+
return (
|
|
71
|
+
<li className="w-full">
|
|
72
|
+
<AnchorLink
|
|
73
|
+
to={item.to}
|
|
74
|
+
target={item.target}
|
|
75
|
+
rel={item.target === "_blank" ? "noopener noreferrer" : undefined}
|
|
76
|
+
onClick={onNavigate}
|
|
77
|
+
className="flex items-center gap-2 py-2 text-base font-medium"
|
|
78
|
+
>
|
|
79
|
+
{Icon && <Icon size={16} />}
|
|
80
|
+
{item.label}
|
|
81
|
+
</AnchorLink>
|
|
82
|
+
</li>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<li className="w-full">
|
|
88
|
+
<Collapsible>
|
|
89
|
+
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-base font-medium group">
|
|
90
|
+
{item.label}
|
|
91
|
+
<ChevronDownIcon className="size-4 transition-transform group-data-[state=open]:rotate-180" />
|
|
92
|
+
</CollapsibleTrigger>
|
|
93
|
+
<CollapsibleContent>
|
|
94
|
+
<ul className="flex flex-col border-l ms-1 ps-3 my-1">
|
|
95
|
+
{item.items.map((subItem) =>
|
|
96
|
+
isHeaderNavGroup(subItem) ? (
|
|
97
|
+
<li key={subItem.label} className="flex flex-col">
|
|
98
|
+
<div className="text-sm text-muted-foreground py-2">
|
|
99
|
+
{subItem.label}
|
|
100
|
+
</div>
|
|
101
|
+
{subItem.items.map((link) => (
|
|
102
|
+
<MobileHeaderNavLink
|
|
103
|
+
key={link.to}
|
|
104
|
+
item={link}
|
|
105
|
+
onClick={onNavigate}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</li>
|
|
109
|
+
) : (
|
|
110
|
+
<li key={subItem.to}>
|
|
111
|
+
<MobileHeaderNavLink item={subItem} onClick={onNavigate} />
|
|
112
|
+
</li>
|
|
113
|
+
),
|
|
114
|
+
)}
|
|
115
|
+
</ul>
|
|
116
|
+
</CollapsibleContent>
|
|
117
|
+
</Collapsible>
|
|
118
|
+
</li>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const MobileTopNavigation = () => {
|
|
123
|
+
const context = useZudoku();
|
|
124
|
+
const authState = useAuth();
|
|
125
|
+
const currentNav = useCurrentNavigation();
|
|
126
|
+
|
|
127
|
+
const {
|
|
128
|
+
options: { header, navigation = [], site },
|
|
129
|
+
getProfileMenuItems,
|
|
130
|
+
} = context;
|
|
131
|
+
const headerNavigation = withoutMarketingCtaHeaderNavigation(
|
|
132
|
+
header?.navigation ?? [],
|
|
133
|
+
);
|
|
134
|
+
const { isAuthenticated, profile, isAuthEnabled, login, signup } = authState;
|
|
135
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
136
|
+
|
|
137
|
+
const accountItems = getProfileMenuItems();
|
|
138
|
+
const filteredItems = navigation.filter(
|
|
139
|
+
shouldShowItem({ auth: authState, context }),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Drawer
|
|
144
|
+
direction={site?.dir === "rtl" ? "left" : "right"}
|
|
145
|
+
open={drawerOpen}
|
|
146
|
+
onOpenChange={setDrawerOpen}
|
|
147
|
+
>
|
|
148
|
+
<div className="flex lg:hidden justify-self-end">
|
|
149
|
+
<DrawerTrigger className="lg:hidden">
|
|
150
|
+
<MenuIcon size={22} />
|
|
151
|
+
</DrawerTrigger>
|
|
152
|
+
<PageProgress />
|
|
153
|
+
</div>
|
|
154
|
+
<DrawerContent
|
|
155
|
+
className="lg:hidden h-dvh inset-e-0 start-auto w-[340px] rounded-none"
|
|
156
|
+
aria-describedby={undefined}
|
|
157
|
+
>
|
|
158
|
+
<div className="py-2 h-full flex flex-col">
|
|
159
|
+
<div className="flex-1 overflow-y-auto overscroll-none">
|
|
160
|
+
<VisuallyHidden>
|
|
161
|
+
<DrawerTitle>Navigation</DrawerTitle>
|
|
162
|
+
</VisuallyHidden>
|
|
163
|
+
<ul className="flex flex-col gap-1 px-4">
|
|
164
|
+
{filteredItems.map((item) => {
|
|
165
|
+
if (item.type === "separator") {
|
|
166
|
+
return <Separator className="my-2" key={item.label} />;
|
|
167
|
+
}
|
|
168
|
+
if (item.type === "section" || item.type === "filter") {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const path = getFirstMatchingPath(item);
|
|
172
|
+
const isActive = deepEqual(currentNav.topNavItem, item);
|
|
173
|
+
return (
|
|
174
|
+
<li key={item.label}>
|
|
175
|
+
<Link
|
|
176
|
+
to={path}
|
|
177
|
+
onClick={() => setDrawerOpen(false)}
|
|
178
|
+
className={`flex items-center gap-2 py-2 text-base font-medium ${isActive ? "text-foreground" : "text-foreground/75 hover:text-foreground"}`}
|
|
179
|
+
>
|
|
180
|
+
{item.icon && <item.icon size={16} />}
|
|
181
|
+
{item.label}
|
|
182
|
+
</Link>
|
|
183
|
+
</li>
|
|
184
|
+
);
|
|
185
|
+
})}
|
|
186
|
+
{filteredItems.length > 0 && headerNavigation.length > 0 && (
|
|
187
|
+
<Separator className="my-2" />
|
|
188
|
+
)}
|
|
189
|
+
<li className="empty:hidden">
|
|
190
|
+
<Slot.Target name="top-navigation-side" />
|
|
191
|
+
</li>
|
|
192
|
+
{headerNavigation.map((item) => (
|
|
193
|
+
<MobileHeaderNavItem
|
|
194
|
+
key={item.label}
|
|
195
|
+
item={item}
|
|
196
|
+
onNavigate={() => setDrawerOpen(false)}
|
|
197
|
+
/>
|
|
198
|
+
))}
|
|
199
|
+
{isAuthEnabled && isAuthenticated && (
|
|
200
|
+
<>
|
|
201
|
+
<Separator className="my-2" />
|
|
202
|
+
<li className="py-2">
|
|
203
|
+
<div className="text-base font-medium">
|
|
204
|
+
{profile?.name ?? "My Account"}
|
|
205
|
+
</div>
|
|
206
|
+
{profile?.email && profile.email !== profile?.name && (
|
|
207
|
+
<div className="text-sm text-muted-foreground">
|
|
208
|
+
{profile.email}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</li>
|
|
212
|
+
{accountItems.map((i) => (
|
|
213
|
+
<li key={i.label}>
|
|
214
|
+
<Link
|
|
215
|
+
to={i.path ?? ""}
|
|
216
|
+
target={i.target}
|
|
217
|
+
rel={
|
|
218
|
+
i.target === "_blank"
|
|
219
|
+
? "noopener noreferrer"
|
|
220
|
+
: undefined
|
|
221
|
+
}
|
|
222
|
+
onClick={() => setDrawerOpen(false)}
|
|
223
|
+
className="flex items-center py-2 text-base font-medium text-foreground/75 hover:text-foreground"
|
|
224
|
+
>
|
|
225
|
+
{i.label}
|
|
226
|
+
</Link>
|
|
227
|
+
</li>
|
|
228
|
+
))}
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
231
|
+
</ul>
|
|
232
|
+
</div>
|
|
233
|
+
<div className="border-t shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] px-4 pt-3 flex flex-col gap-2">
|
|
234
|
+
<div className="flex items-center justify-between gap-2">
|
|
235
|
+
{isAuthEnabled &&
|
|
236
|
+
(isAuthenticated ? (
|
|
237
|
+
<Button asChild variant="outline">
|
|
238
|
+
<Link
|
|
239
|
+
to="/signout"
|
|
240
|
+
onClick={() => setDrawerOpen(false)}
|
|
241
|
+
className="flex items-center gap-2"
|
|
242
|
+
>
|
|
243
|
+
<LogOutIcon
|
|
244
|
+
size={16}
|
|
245
|
+
strokeWidth={1}
|
|
246
|
+
absoluteStrokeWidth
|
|
247
|
+
/>
|
|
248
|
+
Log out
|
|
249
|
+
</Link>
|
|
250
|
+
</Button>
|
|
251
|
+
) : (
|
|
252
|
+
<div className="flex flex-1 items-center gap-2">
|
|
253
|
+
<Button
|
|
254
|
+
variant="ghost"
|
|
255
|
+
className="font-semibold"
|
|
256
|
+
onClick={() => {
|
|
257
|
+
setDrawerOpen(false);
|
|
258
|
+
void login();
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
Log in
|
|
262
|
+
</Button>
|
|
263
|
+
<Button
|
|
264
|
+
onClick={() => {
|
|
265
|
+
setDrawerOpen(false);
|
|
266
|
+
void signup();
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
Get started
|
|
270
|
+
</Button>
|
|
271
|
+
</div>
|
|
272
|
+
))}
|
|
273
|
+
<ThemeSwitch variant="compact" />
|
|
274
|
+
</div>
|
|
275
|
+
{site?.showPoweredBy !== false && (
|
|
276
|
+
<PoweredByZudoku className="grow-0 justify-center gap-1" />
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</DrawerContent>
|
|
281
|
+
</Drawer>
|
|
282
|
+
);
|
|
283
|
+
};
|
|
@@ -1,39 +1,42 @@
|
|
|
1
|
-
import { ChevronRightIcon } from "lucide-react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
2
|
+
// Inlined: a file URL is root-relative in the server render and absolute in the browser when the build has a CDN base,
|
|
3
|
+
// which React reports as a hydration mismatch on every page that shows this link.
|
|
4
|
+
import poweredByIcon from "../../assets/powered-by-icon.png?inline";
|
|
5
|
+
import { cn } from "../../util/cn.js";
|
|
6
|
+
import ZuploLogo from "./ZuploLogo.js";
|
|
7
|
+
|
|
8
|
+
export const PoweredByZudoku = ({ className }: { className?: string }) => (
|
|
9
|
+
<a
|
|
10
|
+
href={
|
|
11
|
+
import.meta.env.IS_ZUPLO ? "https://zuplo.com" : "https://apitogo.com"
|
|
12
|
+
}
|
|
13
|
+
target="_blank"
|
|
14
|
+
rel="noopener noreferrer"
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex justify-between items-center w-full border border-transparent hover:border-border rounded-full hover:shadow-xs h-7 px-3 text-nowrap hover:bg-muted/80 transition-all",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
>
|
|
20
|
+
<div className="opacity-70 hover:opacity-100 transition-opacity gap-1.5 text-[11px] font-medium rounded-full h-7 flex items-center text-nowrap">
|
|
21
|
+
{import.meta.env.IS_ZUPLO ? (
|
|
22
|
+
<ZuploLogo className="w-3.5 h-3.5 dark:fill-white" />
|
|
23
|
+
) : (
|
|
24
|
+
<img
|
|
25
|
+
src={poweredByIcon}
|
|
26
|
+
alt=""
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
className="w-3.5 h-3.5"
|
|
29
|
+
/>
|
|
30
|
+
)}
|
|
31
|
+
powered by {import.meta.env.IS_ZUPLO ? "Zuplo" : "apitogo"}
|
|
32
|
+
</div>
|
|
33
|
+
<div className="text-xs font-medium opacity-70 hover:text-foreground transition-colors cursor-pointer">
|
|
34
|
+
<ChevronRightIcon
|
|
35
|
+
size={12}
|
|
36
|
+
absoluteStrokeWidth
|
|
37
|
+
strokeWidth={1.5}
|
|
38
|
+
className="rtl:rotate-180"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</a>
|
|
42
|
+
);
|