handoff-app 2.0.0-8 → 2.0.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/{Changelog.md → CHANGELOG.md} +85 -1
- package/{License.md → LICENSE.md} +1 -1
- package/README.md +816 -393
- package/RECIPES.md +975 -0
- package/UPGRADE.md +57 -9
- package/components.json +20 -20
- package/config/docs/foundations/colors/guidelines.md +0 -1
- package/config/docs/foundations/colors.md +0 -1
- package/config/docs/foundations/effects/guidelines.md +0 -1
- package/config/docs/foundations/effects/resources.md +0 -1
- package/config/docs/foundations/effects.md +0 -1
- package/config/docs/foundations/fonts.md +0 -1
- package/config/docs/foundations/grid.md +0 -1
- package/config/docs/foundations/icon.md +0 -1
- package/config/docs/foundations/icons/guidelines.md +0 -1
- package/config/docs/foundations/icons.md +0 -1
- package/config/docs/foundations/images/guidelines.md +0 -1
- package/config/docs/foundations/images/resources.md +0 -1
- package/config/docs/foundations/images.md +0 -1
- package/config/docs/foundations/logo/resources.md +0 -1
- package/config/docs/foundations/logo.md +0 -1
- package/config/docs/foundations/typography/guidelines.md +0 -1
- package/config/docs/foundations/typography/resources.md +0 -1
- package/config/docs/foundations/typography.md +0 -1
- package/config/docs/foundations.md +0 -1
- package/config/docs/guidelines.md +0 -1
- package/config/docs/index.md +0 -1
- package/config/docs/system/tokens/foundations/colors.md +0 -4
- package/config/docs/system/tokens/foundations/effects.md +0 -4
- package/config/docs/system/tokens/foundations/typography.md +0 -2
- package/config/docs/system.md +0 -2
- package/config/templates/page.md +2 -1
- package/config/templates/page.mdx +2 -1
- package/dist/ai/connections.d.ts +66 -0
- package/dist/ai/connections.js +149 -0
- package/dist/ai/define.d.ts +10 -0
- package/dist/ai/define.js +13 -0
- package/dist/ai/types.d.ts +26 -0
- package/dist/ai/types.js +10 -0
- package/dist/app-builder/build.d.ts +14 -7
- package/dist/app-builder/build.js +195 -66
- package/dist/app-builder/client-config.js +3 -2
- package/dist/app-builder/paths.d.ts +0 -12
- package/dist/app-builder/paths.js +3 -20
- package/dist/app-builder/static-export.js +1 -1
- package/dist/app-builder/vercel-output.d.ts +1 -1
- package/dist/app-builder/vercel-output.js +5 -4
- package/dist/cache/build-cache.d.ts +27 -4
- package/dist/cache/build-cache.js +114 -47
- package/dist/cache/build-inputs.d.ts +13 -0
- package/dist/cache/build-inputs.js +19 -0
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +5 -1
- package/dist/catalog/normalize.js +3 -0
- package/dist/catalog/previews.js +5 -4
- package/dist/catalog/types.d.ts +8 -0
- package/dist/cli/create.js +1 -2
- package/dist/commands/build/index.d.ts +1 -1
- package/dist/commands/build/index.js +1 -1
- package/dist/commands/utils.d.ts +3 -3
- package/dist/config/defaults.js +7 -4
- package/dist/config/from-env.d.ts +4 -0
- package/dist/config/from-env.js +38 -1
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +1 -4
- package/dist/config/loader.js +28 -2
- package/dist/config/resolve-env.d.ts +2 -1
- package/dist/config/resolve-env.js +28 -12
- package/dist/config/runtime.js +9 -11
- package/dist/handlebars.d.ts +10 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +18 -22
- package/dist/nav/index.d.ts +2 -2
- package/dist/nav/registry.d.ts +3 -3
- package/dist/nav/registry.js +1 -1
- package/dist/nav/workspace.d.ts +3 -3
- package/dist/nav/workspace.js +1 -1
- package/dist/pipeline/figma.d.ts +13 -2
- package/dist/pipeline/figma.js +86 -46
- package/dist/pipeline/styles.js +1 -1
- package/dist/pipeline/validation.d.ts +1 -1
- package/dist/pipeline/validation.js +3 -3
- package/dist/providers.d.ts +10 -0
- package/dist/providers.js +13 -0
- package/dist/registry/asset-storage/define.d.ts +5 -5
- package/dist/registry/asset-storage/define.js +4 -4
- package/dist/registry/asset-storage/resolve.d.ts +9 -19
- package/dist/registry/asset-storage/resolve.js +33 -21
- package/dist/registry/asset-storage/types.d.ts +37 -17
- package/dist/registry/asset-storage/types.js +5 -5
- package/dist/registry/auth/ai-keys.d.ts +32 -0
- package/dist/registry/auth/ai-keys.js +69 -0
- package/dist/registry/auth/crypto.d.ts +17 -0
- package/dist/registry/auth/crypto.js +61 -1
- package/dist/registry/auth/index.d.ts +2 -1
- package/dist/registry/auth/index.js +11 -2
- package/dist/registry/checkout/index.d.ts +2 -1
- package/dist/registry/checkout/index.js +57 -127
- package/dist/registry/client.js +53 -17
- package/dist/registry/db/driver.js +3 -3
- package/dist/registry/db/schema.d.ts +261 -3
- package/dist/registry/db/schema.js +29 -4
- package/dist/registry/email/define.d.ts +10 -0
- package/dist/registry/email/define.js +13 -0
- package/dist/registry/email/providers/smtp.d.ts +10 -0
- package/dist/registry/email/providers/smtp.js +73 -0
- package/dist/registry/email/resolve.d.ts +28 -0
- package/dist/registry/email/resolve.js +74 -0
- package/dist/registry/email/types.d.ts +24 -0
- package/dist/registry/email/types.js +6 -0
- package/dist/registry/path.d.ts +2 -0
- package/dist/registry/path.js +11 -1
- package/dist/registry/publish/package.js +8 -0
- package/dist/registry/transfer.d.ts +4 -0
- package/dist/store/registry.d.ts +3 -3
- package/dist/store/registry.js +4 -4
- package/dist/transformers/plugins/ssr-render.js +40 -15
- package/dist/transformers/preview/component/builder.d.ts +1 -9
- package/dist/transformers/preview/component/builder.js +17 -17
- package/dist/transformers/preview/component/css.d.ts +3 -1
- package/dist/transformers/preview/component/css.js +92 -51
- package/dist/transformers/preview/component/html.d.ts +3 -1
- package/dist/transformers/preview/component/html.js +6 -2
- package/dist/transformers/preview/component/javascript.d.ts +2 -1
- package/dist/transformers/preview/component/javascript.js +10 -4
- package/dist/transformers/preview/pattern/html.js +3 -1
- package/dist/transformers/preview/types.d.ts +13 -0
- package/dist/transformers/utils/build.d.ts +24 -0
- package/dist/transformers/utils/build.js +100 -1
- package/dist/transformers/utils/module.js +2 -1
- package/dist/types/config.d.ts +135 -18
- package/dist/utils/path.d.ts +1 -5
- package/dist/utils/path.js +6 -23
- package/docs/api.md +6 -4
- package/docs/cli.md +275 -54
- package/drizzle/0007_add_ai_keys.sql +11 -0
- package/drizzle/0008_add_source_dir.sql +2 -0
- package/drizzle/meta/0007_snapshot.json +2013 -0
- package/drizzle/meta/0008_snapshot.json +2025 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +19 -8
- package/src/ai/README.md +42 -0
- package/src/ai/connections.ts +189 -0
- package/src/ai/define.ts +15 -0
- package/src/ai/types.ts +30 -0
- package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
- package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
- package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
- package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
- package/src/app/components/AiAssistant/dockState.ts +86 -0
- package/src/app/components/AiAssistant/toolLabel.ts +24 -0
- package/src/app/components/AiAssistant/useAiChat.ts +147 -0
- package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
- package/src/app/components/AiAssistant/usePageContext.ts +63 -0
- package/src/app/components/Auth/AccountLayout.tsx +58 -19
- package/src/app/components/Auth/AuthShell.tsx +27 -33
- package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
- package/src/app/components/Auth/installStatus.ts +29 -0
- package/src/app/components/Component/PageSliceResolver.tsx +1 -0
- package/src/app/components/Component/Preview.tsx +383 -189
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
- package/src/app/components/Foundations/ColorGrid.tsx +13 -14
- package/src/app/components/Layout/Header.tsx +21 -15
- package/src/app/components/Layout/Main.tsx +2 -2
- package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
- package/src/app/components/Navigation/MobileNav.tsx +2 -2
- package/src/app/components/Navigation/SideNav.tsx +1 -15
- package/src/app/components/context/NavProvider.tsx +7 -3
- package/src/app/components/ui/attachment.tsx +50 -0
- package/src/app/components/ui/bubble.tsx +57 -0
- package/src/app/components/ui/button.tsx +3 -1
- package/src/app/components/ui/confirm-dialog.tsx +87 -0
- package/src/app/components/ui/dialog.tsx +7 -2
- package/src/app/components/ui/marker.tsx +56 -0
- package/src/app/components/ui/sidebar.tsx +3 -1
- package/src/app/components/util/index.ts +3 -12
- package/src/app/css/index.css +64 -0
- package/src/app/lib/ai/auth.ts +33 -0
- package/src/app/lib/ai/context.ts +99 -0
- package/src/app/lib/ai/model.ts +79 -0
- package/src/app/lib/ai/prompt.ts +36 -0
- package/src/app/lib/ai/resolve.ts +97 -0
- package/src/app/lib/ai/tools.ts +120 -0
- package/src/app/lib/api/methods.ts +13 -0
- package/src/app/lib/asset-storage.ts +35 -36
- package/src/app/lib/auth/api.ts +4 -12
- package/src/app/lib/auth/email.ts +96 -23
- package/src/app/lib/docs-api/page-rendering.ts +1 -1
- package/src/app/lib/docs-api/registry.ts +2 -3
- package/src/app/lib/docs-api/runtime-config.ts +47 -21
- package/src/app/lib/mcp/components.ts +523 -0
- package/src/app/lib/mcp/page-url.ts +24 -0
- package/src/app/lib/mcp/pages.ts +84 -0
- package/src/app/lib/mcp/result.ts +33 -0
- package/src/app/lib/mcp/server.ts +39 -318
- package/src/app/lib/mcp/tokens.ts +334 -0
- package/src/app/lib/mcp/tool.ts +19 -0
- package/src/app/lib/registry-api/asset-transfer.ts +119 -11
- package/src/app/lib/registry-api/errors.ts +2 -0
- package/src/app/lib/registry-api/handler.ts +7 -1
- package/src/app/lib/registry-api/transfer.ts +28 -6
- package/src/app/lib/server-module.ts +34 -0
- package/src/app/lib/utils.ts +20 -0
- package/src/app/next.config.mjs +26 -24
- package/src/app/pages/[...slug]/index.tsx +2 -1
- package/src/app/pages/_app.tsx +6 -2
- package/src/app/pages/_document.tsx +6 -0
- package/src/app/pages/account/ai.tsx +183 -0
- package/src/app/pages/account/index.tsx +68 -68
- package/src/app/pages/account/tokens.tsx +37 -7
- package/src/app/pages/account/users.tsx +75 -15
- package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
- package/src/app/pages/api/account/ai/keys/index.ts +20 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
- package/src/app/pages/api/admin/users/index.ts +2 -2
- package/src/app/pages/api/ai/chat.ts +115 -0
- package/src/app/pages/api/ai/connections.ts +23 -0
- package/src/app/pages/api/auth/request-reset.ts +14 -16
- package/src/app/pages/api/docs/nav.json.ts +1 -1
- package/src/app/pages/api/install.ts +3 -3
- package/src/app/pages/api/mcp.ts +6 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
- package/src/app/pages/cli/device.tsx +10 -5
- package/src/app/pages/foundations/colors.tsx +9 -8
- package/src/app/pages/foundations/index.tsx +3 -3
- package/src/app/pages/index.tsx +2 -2
- package/src/app/pages/install.tsx +7 -2
- package/src/app/pages/login.tsx +32 -27
- package/src/app/pages/reset-password.tsx +21 -20
- package/src/app/pages/system/component/[component]/index.tsx +2 -5
- package/src/app/pages/system/index.tsx +11 -11
- package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
- package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
- package/src/app-builder/README.md +2 -1
- package/src/app-builder/build.ts +218 -73
- package/src/app-builder/client-config.ts +3 -2
- package/src/app-builder/paths.ts +3 -22
- package/src/app-builder/static-export.ts +1 -1
- package/src/app-builder/vercel-output.ts +5 -4
- package/src/cache/README.md +4 -1
- package/src/cache/build-cache.ts +128 -43
- package/src/cache/build-inputs.ts +28 -0
- package/src/cache/index.ts +4 -0
- package/src/catalog/README.md +3 -0
- package/src/catalog/normalize.ts +2 -0
- package/src/catalog/previews.ts +6 -3
- package/src/catalog/types.ts +8 -0
- package/src/cli/create.ts +1 -2
- package/src/commands/build/index.ts +1 -1
- package/src/config/README.md +10 -9
- package/src/config/defaults.ts +7 -4
- package/src/config/from-env.ts +33 -0
- package/src/config/helpers.ts +1 -3
- package/src/config/index.ts +0 -3
- package/src/config/loader.ts +27 -3
- package/src/config/resolve-env.ts +28 -15
- package/src/config/runtime.ts +10 -5
- package/src/handlebars.ts +8 -2
- package/src/index.ts +33 -29
- package/src/nav/index.ts +2 -2
- package/src/nav/registry.ts +3 -3
- package/src/nav/workspace.ts +3 -3
- package/src/pipeline/README.md +1 -1
- package/src/pipeline/figma.ts +98 -49
- package/src/pipeline/styles.ts +1 -1
- package/src/pipeline/validation.ts +3 -3
- package/src/providers.ts +18 -0
- package/src/registry/asset-storage/define.ts +6 -6
- package/src/registry/asset-storage/resolve.ts +38 -37
- package/src/registry/asset-storage/types.ts +39 -17
- package/src/registry/auth/ai-keys.ts +62 -0
- package/src/registry/auth/crypto.ts +63 -1
- package/src/registry/auth/index.ts +11 -1
- package/src/registry/checkout/index.ts +60 -138
- package/src/registry/client.ts +60 -20
- package/src/registry/db/driver.ts +2 -2
- package/src/registry/db/schema.ts +49 -22
- package/src/registry/email/define.ts +15 -0
- package/src/registry/email/providers/smtp.ts +39 -0
- package/src/registry/email/resolve.ts +94 -0
- package/src/registry/email/types.ts +28 -0
- package/src/registry/path.ts +11 -0
- package/src/registry/publish/package.ts +9 -0
- package/src/registry/transfer.ts +4 -0
- package/src/store/registry.ts +7 -7
- package/src/transformers/plugins/ssr-render.ts +53 -16
- package/src/transformers/preview/component/builder.ts +19 -25
- package/src/transformers/preview/component/css.ts +101 -53
- package/src/transformers/preview/component/html.ts +6 -2
- package/src/transformers/preview/component/javascript.ts +14 -4
- package/src/transformers/preview/pattern/html.ts +3 -1
- package/src/transformers/preview/types.ts +14 -0
- package/src/transformers/utils/build.ts +93 -0
- package/src/transformers/utils/module.ts +4 -2
- package/src/types/config.ts +148 -20
- package/src/utils/README.md +1 -1
- package/src/utils/path.ts +6 -29
- package/templates/default/handoff.config.js.tpl +10 -2
- package/templates/default/handoff.config.ts.tpl +10 -2
- package/templates/default/handoff.config.with-examples.js.tpl +10 -2
- package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
- package/templates/default/package.json.tpl +2 -2
- package/.editorconfig +0 -19
- package/.eslintrc.json +0 -4
- package/.prettierignore +0 -0
- package/.prettierrc.json +0 -8
- package/.vscode/settings.json +0 -17
- package/dist/config/validator.d.ts +0 -9
- package/dist/config/validator.js +0 -24
- package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
- package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
- package/src/app/components/old/Header.tsx +0 -89
- package/src/app/lib/mcp/shape.ts +0 -596
- package/src/app/pages/assets/fonts.tsx +0 -129
- package/src/app/pages/assets/index.tsx +0 -91
- package/src/app/pages/assets/logos.tsx +0 -75
- package/src/config/validator.ts +0 -22
- package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
|
@@ -13,6 +13,11 @@ export default async function requestResetHandler(req: NextApiRequest, res: Next
|
|
|
13
13
|
if (!method) return;
|
|
14
14
|
const context = await prepareRegistryApi(req, res, { mutation: true });
|
|
15
15
|
if (!context) return;
|
|
16
|
+
// The same answer for every address, so this reveals nothing about which accounts exist.
|
|
17
|
+
if (!registryEmailIsConfigured()) {
|
|
18
|
+
res.status(503).json({ error: 'Password reset by email is not available on this registry. Ask an administrator for a reset link.' });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
16
21
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
17
22
|
const email = normalizeEmail(typeof body.email === 'string' ? body.email : '');
|
|
18
23
|
const throttle = await consumeAuthRateLimit(context.db, {
|
|
@@ -27,26 +32,19 @@ export default async function requestResetHandler(req: NextApiRequest, res: Next
|
|
|
27
32
|
return;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
if (!registryEmailIsConfigured()) {
|
|
31
|
-
res.status(200).json({ ok: true });
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
35
|
const result = await createPasswordReset(context.db, email);
|
|
35
36
|
if (result.token && result.user) {
|
|
36
37
|
const resetUrl = registryPageUrl('/reset-password', undefined, { token: result.token });
|
|
38
|
+
// The result is ignored so the response never reveals whether the account exists.
|
|
37
39
|
if (resetUrl) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
47
|
-
} catch {
|
|
48
|
-
// Keep the response enumeration-safe. Operational email failures never reveal account state.
|
|
49
|
-
}
|
|
40
|
+
await sendRegistryAuthEmail({
|
|
41
|
+
to: result.user.email,
|
|
42
|
+
subject: 'Reset your Handoff Registry password',
|
|
43
|
+
heading: 'Reset your password',
|
|
44
|
+
message: 'Use this single-use link to choose a new password. It expires in one hour.',
|
|
45
|
+
actionLabel: 'Reset password',
|
|
46
|
+
actionUrl: resetUrl,
|
|
47
|
+
});
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
res.status(200).json({ ok: true });
|
|
@@ -5,7 +5,7 @@ import { ensureGet, resolveDocsBackend, sendDocsData, sendDocsError } from '@/li
|
|
|
5
5
|
// Kept static so the baked structure is traced into every registry serverless bundle.
|
|
6
6
|
import navShell from '@/generated/nav-shell.json';
|
|
7
7
|
|
|
8
|
-
/** Thin
|
|
8
|
+
/** Thin route: `getRegistryNavData` builds and fills the whole navigation. */
|
|
9
9
|
export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
10
10
|
if (!ensureGet(req, res)) return;
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|
|
2
2
|
import { getRegistryInstallationState, installRegistry } from '@handoff/registry/auth';
|
|
3
3
|
import { allowApiMethods, prepareRegistryApi } from '../../lib/auth/api';
|
|
4
4
|
import { canonicalRegistryUrl, registryAuthSecret } from '../../lib/auth/config';
|
|
5
|
-
import { registryEmailIsConfigured } from '../../lib/auth/email';
|
|
5
|
+
import { missingRegistryEmailSettings, registryEmailIsConfigured, registryEmailProviderName } from '../../lib/auth/email';
|
|
6
6
|
|
|
7
7
|
const check = (id: string, label: string, ok: boolean, message: string, optional = false) => ({
|
|
8
8
|
id,
|
|
@@ -60,8 +60,8 @@ export default async function installHandler(req: NextApiRequest, res: NextApiRe
|
|
|
60
60
|
'Email delivery',
|
|
61
61
|
registryEmailIsConfigured(),
|
|
62
62
|
registryEmailIsConfigured()
|
|
63
|
-
?
|
|
64
|
-
:
|
|
63
|
+
? `Delivery through ${registryEmailProviderName()} is configured.`
|
|
64
|
+
: `Optional. Set ${missingRegistryEmailSettings().join(', ')}, or deliver invitation links manually.`,
|
|
65
65
|
true
|
|
66
66
|
),
|
|
67
67
|
];
|
package/src/app/pages/api/mcp.ts
CHANGED
|
@@ -22,6 +22,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// The status probe of the docs app. A 204 shows only that the route is served, so the probe needs no credential.
|
|
26
|
+
if (req.method === 'HEAD') {
|
|
27
|
+
res.status(204).end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
if (!(await authorizeMcpRequest(req, res))) {
|
|
26
32
|
return;
|
|
27
33
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleAssetBlobCompleteRoute } from '@/lib/registry-api/asset-transfer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `POST /api/registry/transfer/assets/blobs/:hash/complete`: given `{ storageRef, size, contentType }`
|
|
6
|
+
* from a direct upload, confirm the object exists in storage and record the blob. Requires a
|
|
7
|
+
* write-scoped credential; registry-runtime only.
|
|
8
|
+
*/
|
|
9
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
10
|
+
return handleAssetBlobCompleteRoute(req, res);
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
2
|
+
import { handleAssetBlobUploadRoute } from '@/lib/registry-api/asset-transfer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `POST /api/registry/transfer/assets/blobs/:hash/upload`: given `{ size, contentType }`, return
|
|
6
|
+
* `{ upload }`, a signed URL to upload the blob directly to storage, or `null` when the client must
|
|
7
|
+
* upload through `PUT /api/registry/transfer/assets/blobs/:hash`. Requires a write-scoped
|
|
8
|
+
* credential; registry-runtime only.
|
|
9
|
+
*/
|
|
10
|
+
export default function handler(req: NextApiRequest, res: NextApiResponse): Promise<void> {
|
|
11
|
+
return handleAssetBlobUploadRoute(req, res);
|
|
12
|
+
}
|
|
@@ -5,7 +5,7 @@ import { handleAssetBlobRoute } from '@/lib/registry-api/asset-transfer';
|
|
|
5
5
|
* `/api/registry/transfer/assets/blobs/:hash`: one content-addressed blob.
|
|
6
6
|
*
|
|
7
7
|
* - `PUT` stores the raw binary body through the active storage provider (inline `bytea` by default,
|
|
8
|
-
* or
|
|
8
|
+
* or a custom storage provider). Idempotent by hash; requires a `registry:write` token.
|
|
9
9
|
* - `GET` returns the blob's bytes, or redirects to a provider URL for object-backed content.
|
|
10
10
|
* Requires a `registry:read` token.
|
|
11
11
|
*
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ClientConfig } from '@handoff/types/config';
|
|
2
|
+
import type { GetStaticProps } from 'next';
|
|
3
|
+
import { getClientRuntimeConfig } from '../../components/util';
|
|
1
4
|
import { AuthShell } from '../../components/Auth/AuthShell';
|
|
2
5
|
import { authApiUrl, readApiError } from '../../components/Auth/api';
|
|
3
6
|
import { Alert, AlertDescription, AlertTitle } from '../../components/ui/alert';
|
|
@@ -13,15 +16,17 @@ import { FormEvent, useEffect, useState } from 'react';
|
|
|
13
16
|
|
|
14
17
|
const normalizeCode = (value: string) => value.replace(/\s/g, '').toUpperCase();
|
|
15
18
|
|
|
16
|
-
export
|
|
19
|
+
export const getStaticProps: GetStaticProps = async () => ({ props: { config: getClientRuntimeConfig() } });
|
|
20
|
+
|
|
21
|
+
export default function DeviceApprovalPage({ config }: { config: ClientConfig }) {
|
|
17
22
|
if (process.env.HANDOFF_RUNTIME_MODE !== 'registry') {
|
|
18
|
-
return <AuthShell title="Authorize CLI">{null}</AuthShell>;
|
|
23
|
+
return <AuthShell config={config} title="Authorize CLI">{null}</AuthShell>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
return <RegistryDeviceApprovalPage />;
|
|
26
|
+
return <RegistryDeviceApprovalPage config={config} />;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
function RegistryDeviceApprovalPage() {
|
|
29
|
+
function RegistryDeviceApprovalPage({ config }: { config: ClientConfig }) {
|
|
25
30
|
const router = useRouter();
|
|
26
31
|
const { data: session, status } = useSession();
|
|
27
32
|
const [userCode, setUserCode] = useState('');
|
|
@@ -60,7 +65,7 @@ function RegistryDeviceApprovalPage() {
|
|
|
60
65
|
const callbackUrl = `/cli/device${router.asPath.includes('?') ? router.asPath.slice(router.asPath.indexOf('?')) : ''}`;
|
|
61
66
|
|
|
62
67
|
return (
|
|
63
|
-
<AuthShell title="Authorize CLI">
|
|
68
|
+
<AuthShell config={config} title="Authorize CLI">
|
|
64
69
|
<Card>
|
|
65
70
|
<CardHeader>
|
|
66
71
|
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-muted">
|
|
@@ -33,6 +33,10 @@ export const getStaticProps: GetStaticProps = async () => {
|
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
// Tokens without a Figma group land under an empty key.
|
|
37
|
+
const groupTitle = (group: string) => upperFirst(group) || 'Other';
|
|
38
|
+
const groupId = (group: string) => group || 'other';
|
|
39
|
+
|
|
36
40
|
const ColorsPage = ({
|
|
37
41
|
content,
|
|
38
42
|
menu,
|
|
@@ -82,20 +86,17 @@ const ColorsPage = ({
|
|
|
82
86
|
<div className="lg:gap-10 lg:py-8 xl:grid xl:grid-cols-[1fr_280px]">
|
|
83
87
|
<div className="flex flex-col gap-0">
|
|
84
88
|
{Object.keys(colorGroups).map((group) => (
|
|
85
|
-
<ColorGrid
|
|
86
|
-
title={upperFirst(group)}
|
|
87
|
-
group={group}
|
|
88
|
-
description="Colors that are used most frequently across all pages and components."
|
|
89
|
-
colors={colorGroups[group]}
|
|
90
|
-
key={group}
|
|
91
|
-
/>
|
|
89
|
+
<ColorGrid title={groupTitle(group)} group={groupId(group)} colors={colorGroups[group]} key={group} />
|
|
92
90
|
))}
|
|
93
91
|
<PrevNextNav previous={null} next={{ title: 'Typography', href: '/foundations/typography' }} />
|
|
94
92
|
</div>
|
|
95
93
|
|
|
96
94
|
<AnchorNav
|
|
97
95
|
groups={[
|
|
98
|
-
Object.assign(
|
|
96
|
+
Object.assign(
|
|
97
|
+
{},
|
|
98
|
+
...[...Object.keys(colorGroups).map((group) => ({ [`${groupId(group)}-colors`]: `${groupTitle(group)} Colors` }))]
|
|
99
|
+
),
|
|
99
100
|
]}
|
|
100
101
|
/>
|
|
101
102
|
|
|
@@ -50,7 +50,7 @@ const DesignPage = ({ content, menu, metadata, current, config }: DocumentationP
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
title: 'Colors',
|
|
53
|
-
description: 'Official
|
|
53
|
+
description: 'Official color palette used for all digital products.',
|
|
54
54
|
icon: Palette,
|
|
55
55
|
link: '/foundations/colors',
|
|
56
56
|
cta: 'Explore Colors',
|
|
@@ -74,14 +74,14 @@ const DesignPage = ({ content, menu, metadata, current, config }: DocumentationP
|
|
|
74
74
|
description: 'Downloadable icon set for use in digital and offline materials.',
|
|
75
75
|
icon: Shapes,
|
|
76
76
|
link: '/foundations/icons',
|
|
77
|
-
cta: '
|
|
77
|
+
cta: 'Explore Icons',
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
title: 'Effects',
|
|
81
81
|
description: 'Shadows, blurs, and other effects used in the design system.',
|
|
82
82
|
icon: Sun,
|
|
83
83
|
link: '/foundations/effects',
|
|
84
|
-
cta: '
|
|
84
|
+
cta: 'Explore Effects',
|
|
85
85
|
},
|
|
86
86
|
]}
|
|
87
87
|
/>
|
package/src/app/pages/index.tsx
CHANGED
|
@@ -57,11 +57,11 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|
|
57
57
|
|
|
58
58
|
export const Home = ({ content, menu, metadata, config, current }: DocumentationProps) => {
|
|
59
59
|
return (
|
|
60
|
-
<Layout config={config} menu={menu} current={current} metadata={metadata} fullWidthHero={true}>
|
|
60
|
+
<Layout config={config} menu={menu} current={current} metadata={{ ...metadata, metaTitle: metadata.metaTitle || config?.app?.title }} fullWidthHero={true}>
|
|
61
61
|
<div className="w-full bg-linear-to-r py-12 dark:from-gray-900 dark:to-gray-800 sm:py-20">
|
|
62
62
|
<div className="container mx-auto px-8">
|
|
63
63
|
<HeadersType.H1 className="max-w-4xl text-3xl font-semibold leading-[-0.05px] sm:text-4xl">
|
|
64
|
-
{config?.app?.
|
|
64
|
+
{config?.app?.title}
|
|
65
65
|
</HeadersType.H1>
|
|
66
66
|
{content && (
|
|
67
67
|
<div className="text-lg max-w-4xl empty:hidden [&>*]:mt-6 [&>p]:font-light [&>h2:first-child]:mt-0 [&>h2:first-child]:font-normal sm:text-xl">
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ClientConfig } from '@handoff/types/config';
|
|
2
|
+
import type { GetStaticProps } from 'next';
|
|
3
|
+
import { getClientRuntimeConfig } from '../components/util';
|
|
1
4
|
import { CheckCircle2, CircleAlert, Mail, RefreshCw, UserRound } from 'lucide-react';
|
|
2
5
|
import Link from 'next/link';
|
|
3
6
|
import { useRouter } from 'next/router';
|
|
@@ -66,7 +69,9 @@ const normalizeChecks = (status: InstallStatus): PreflightCheck[] => {
|
|
|
66
69
|
];
|
|
67
70
|
};
|
|
68
71
|
|
|
69
|
-
export
|
|
72
|
+
export const getStaticProps: GetStaticProps = async () => ({ props: { config: getClientRuntimeConfig() } });
|
|
73
|
+
|
|
74
|
+
export default function InstallPage({ config }: { config: ClientConfig }) {
|
|
70
75
|
const router = useRouter();
|
|
71
76
|
const [step, setStep] = useState<InstallStep>('preflight');
|
|
72
77
|
const [checks, setChecks] = useState<PreflightCheck[]>([]);
|
|
@@ -161,7 +166,7 @@ export default function InstallPage() {
|
|
|
161
166
|
};
|
|
162
167
|
|
|
163
168
|
return (
|
|
164
|
-
<AuthShell title="Install registry" description="Configure the first Handoff Registry administrator." hideNav>
|
|
169
|
+
<AuthShell config={config} title="Install registry" description="Configure the first Handoff Registry administrator." hideNav>
|
|
165
170
|
<div className="mb-8">
|
|
166
171
|
<p className="text-sm font-medium text-muted-foreground">Registry installation</p>
|
|
167
172
|
<div className="mt-3 grid grid-cols-3 gap-2" aria-label="Installation progress">
|
package/src/app/pages/login.tsx
CHANGED
|
@@ -1,32 +1,40 @@
|
|
|
1
|
-
import { Alert, AlertDescription } from '../components/ui/alert';
|
|
2
1
|
import { Button } from '../components/ui/button';
|
|
3
2
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../components/ui/card';
|
|
4
3
|
import { Input } from '../components/ui/input';
|
|
5
4
|
import { Label } from '../components/ui/label';
|
|
6
|
-
import {
|
|
5
|
+
import type { ClientConfig } from '@handoff/types/config';
|
|
6
|
+
import type { GetStaticProps } from 'next';
|
|
7
7
|
import Link from 'next/link';
|
|
8
8
|
import { signIn, useSession } from 'next-auth/react';
|
|
9
9
|
import { useRouter } from 'next/router';
|
|
10
10
|
import { FormEvent, useEffect, useState } from 'react';
|
|
11
11
|
import { AuthShell } from '../components/Auth/AuthShell';
|
|
12
|
-
import {
|
|
12
|
+
import { loadInstallStatus } from '../components/Auth/installStatus';
|
|
13
|
+
import { getClientRuntimeConfig } from '../components/util';
|
|
13
14
|
|
|
14
15
|
const safeCallbackUrl = (value: unknown): string =>
|
|
15
16
|
typeof value === 'string' && value.startsWith('/') && !value.startsWith('//') && !value.startsWith('/\\') ? value : '/';
|
|
16
17
|
|
|
17
|
-
export
|
|
18
|
+
export const getStaticProps: GetStaticProps = async () => ({ props: { config: getClientRuntimeConfig() } });
|
|
19
|
+
|
|
20
|
+
export default function LoginPage({ config }: { config: ClientConfig }) {
|
|
18
21
|
if (process.env.HANDOFF_RUNTIME_MODE !== 'registry') {
|
|
19
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
<AuthShell config={config} title="Sign in" centered>
|
|
24
|
+
{null}
|
|
25
|
+
</AuthShell>
|
|
26
|
+
);
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
return <RegistryLoginPage />;
|
|
29
|
+
return <RegistryLoginPage config={config} />;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
function RegistryLoginPage() {
|
|
32
|
+
function RegistryLoginPage({ config }: { config: ClientConfig }) {
|
|
26
33
|
const router = useRouter();
|
|
27
34
|
const { data: session, status } = useSession();
|
|
28
35
|
const [pending, setPending] = useState(false);
|
|
29
36
|
const [checkingInstall, setCheckingInstall] = useState(true);
|
|
37
|
+
const [emailConfigured, setEmailConfigured] = useState(true);
|
|
30
38
|
const [error, setError] = useState<string | null>(null);
|
|
31
39
|
|
|
32
40
|
useEffect(() => {
|
|
@@ -34,11 +42,11 @@ function RegistryLoginPage() {
|
|
|
34
42
|
setCheckingInstall(false);
|
|
35
43
|
return;
|
|
36
44
|
}
|
|
37
|
-
void
|
|
38
|
-
.then(async (
|
|
39
|
-
if (!
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
void loadInstallStatus()
|
|
46
|
+
.then(async (status) => {
|
|
47
|
+
if (!status) return;
|
|
48
|
+
if (status.installed === false) await router.replace('/install');
|
|
49
|
+
setEmailConfigured(status.emailConfigured !== false);
|
|
42
50
|
})
|
|
43
51
|
.finally(() => setCheckingInstall(false));
|
|
44
52
|
}, [router]);
|
|
@@ -74,25 +82,18 @@ function RegistryLoginPage() {
|
|
|
74
82
|
};
|
|
75
83
|
|
|
76
84
|
return (
|
|
77
|
-
<AuthShell title="Sign in">
|
|
85
|
+
<AuthShell config={config} title="Sign in" centered>
|
|
78
86
|
<Card>
|
|
79
87
|
<CardHeader>
|
|
80
88
|
<CardTitle>Sign in</CardTitle>
|
|
81
|
-
<CardDescription>Use your Handoff
|
|
89
|
+
<CardDescription>Use your Handoff account email and password.</CardDescription>
|
|
82
90
|
</CardHeader>
|
|
83
91
|
<form onSubmit={submit}>
|
|
84
92
|
<CardContent className="space-y-4">
|
|
85
93
|
{router.query.updated === '1' ? (
|
|
86
|
-
<
|
|
87
|
-
<AlertDescription>Your password was updated. Sign in below.</AlertDescription>
|
|
88
|
-
</Alert>
|
|
89
|
-
) : null}
|
|
90
|
-
{error ? (
|
|
91
|
-
<Alert variant="destructive">
|
|
92
|
-
<CircleAlert />
|
|
93
|
-
<AlertDescription>{error}</AlertDescription>
|
|
94
|
-
</Alert>
|
|
94
|
+
<p className="text-sm text-green-600 dark:text-green-400">Your password was updated. Sign in below.</p>
|
|
95
95
|
) : null}
|
|
96
|
+
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
|
96
97
|
<div className="space-y-2">
|
|
97
98
|
<Label htmlFor="email">Email</Label>
|
|
98
99
|
<Input id="email" name="email" type="email" autoComplete="email" required placeholder="you@company.com" />
|
|
@@ -102,13 +103,17 @@ function RegistryLoginPage() {
|
|
|
102
103
|
<Input id="password" name="password" type="password" autoComplete="current-password" required />
|
|
103
104
|
</div>
|
|
104
105
|
</CardContent>
|
|
105
|
-
<CardFooter className="flex-col gap-3">
|
|
106
|
+
<CardFooter className="flex flex-col gap-3">
|
|
106
107
|
<Button type="submit" className="w-full" disabled={pending || checkingInstall}>
|
|
107
108
|
{pending ? 'Signing in…' : 'Sign in'}
|
|
108
109
|
</Button>
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
{emailConfigured ? (
|
|
111
|
+
<Link href="/reset-password" className="text-center text-sm text-muted-foreground underline-offset-4 hover:underline">
|
|
112
|
+
Forgot password?
|
|
113
|
+
</Link>
|
|
114
|
+
) : (
|
|
115
|
+
<p className="text-center text-sm text-muted-foreground">Forgot your password? Ask an administrator for a reset link.</p>
|
|
116
|
+
)}
|
|
112
117
|
</CardFooter>
|
|
113
118
|
</form>
|
|
114
119
|
</Card>
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { Alert, AlertDescription } from '../components/ui/alert';
|
|
2
1
|
import { Button } from '../components/ui/button';
|
|
3
2
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../components/ui/card';
|
|
4
3
|
import { Input } from '../components/ui/input';
|
|
5
4
|
import { Label } from '../components/ui/label';
|
|
5
|
+
import type { ClientConfig } from '@handoff/types/config';
|
|
6
|
+
import type { GetStaticProps } from 'next';
|
|
6
7
|
import Head from 'next/head';
|
|
7
8
|
import Link from 'next/link';
|
|
8
9
|
import { useRouter } from 'next/router';
|
|
9
10
|
import { FormEvent, useEffect, useState } from 'react';
|
|
10
11
|
import { AuthShell } from '../components/Auth/AuthShell';
|
|
11
12
|
import { authApiUrl, readApiError } from '../components/Auth/api';
|
|
13
|
+
import { getClientRuntimeConfig } from '../components/util';
|
|
12
14
|
|
|
13
|
-
export
|
|
15
|
+
export const getStaticProps: GetStaticProps = async () => ({ props: { config: getClientRuntimeConfig() } });
|
|
16
|
+
|
|
17
|
+
export default function ResetPasswordPage({ config }: { config: ClientConfig }) {
|
|
14
18
|
const router = useRouter();
|
|
15
19
|
const [token, setToken] = useState('');
|
|
16
20
|
const [purpose, setPurpose] = useState<'invite' | 'reset'>('reset');
|
|
@@ -88,7 +92,7 @@ export default function ResetPasswordPage() {
|
|
|
88
92
|
};
|
|
89
93
|
|
|
90
94
|
return (
|
|
91
|
-
<AuthShell title={token ? 'Set a new password' : 'Reset password'}>
|
|
95
|
+
<AuthShell config={config} title={token ? 'Set a new password' : 'Reset password'} centered>
|
|
92
96
|
<Head>
|
|
93
97
|
<meta name="referrer" content="no-referrer" />
|
|
94
98
|
</Head>
|
|
@@ -97,32 +101,27 @@ export default function ResetPasswordPage() {
|
|
|
97
101
|
<CardTitle>{token ? 'Set a new password' : 'Reset password'}</CardTitle>
|
|
98
102
|
<CardDescription>
|
|
99
103
|
{sent
|
|
100
|
-
? 'If an account exists for that email,
|
|
104
|
+
? 'If an account exists for that email, we sent a link to reset your password.'
|
|
101
105
|
: token
|
|
102
|
-
? 'Choose a
|
|
103
|
-
: 'Enter your email and we will send a reset link if an account exists.'}
|
|
106
|
+
? 'Choose a password at least 12 characters long.'
|
|
107
|
+
: 'Enter your email and we will send you a reset link if an account exists.'}
|
|
104
108
|
</CardDescription>
|
|
105
109
|
</CardHeader>
|
|
106
110
|
{sent ? (
|
|
107
|
-
<CardFooter>
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
</
|
|
111
|
+
<CardFooter className="flex flex-col gap-3">
|
|
112
|
+
<Link href="/login" className="text-center text-sm text-primary underline-offset-4 hover:underline">
|
|
113
|
+
Return to sign in
|
|
114
|
+
</Link>
|
|
111
115
|
</CardFooter>
|
|
112
116
|
) : (
|
|
113
117
|
<form onSubmit={token ? resetPassword : requestReset}>
|
|
114
118
|
<CardContent className="space-y-4">
|
|
115
|
-
{error ?
|
|
116
|
-
<Alert variant="destructive">
|
|
117
|
-
<AlertDescription>{error}</AlertDescription>
|
|
118
|
-
</Alert>
|
|
119
|
-
) : null}
|
|
119
|
+
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
|
120
120
|
{token ? (
|
|
121
121
|
<>
|
|
122
122
|
<div className="space-y-2">
|
|
123
123
|
<Label htmlFor="password">New password</Label>
|
|
124
124
|
<Input id="password" name="password" type="password" autoComplete="new-password" required minLength={12} />
|
|
125
|
-
<p className="text-xs text-muted-foreground">Use at least 12 characters.</p>
|
|
126
125
|
</div>
|
|
127
126
|
<div className="space-y-2">
|
|
128
127
|
<Label htmlFor="passwordConfirmation">Confirm password</Label>
|
|
@@ -143,13 +142,15 @@ export default function ResetPasswordPage() {
|
|
|
143
142
|
</div>
|
|
144
143
|
)}
|
|
145
144
|
</CardContent>
|
|
146
|
-
<CardFooter className="flex-col gap-3">
|
|
145
|
+
<CardFooter className="flex flex-col gap-3">
|
|
147
146
|
<Button type="submit" className="w-full" disabled={pending}>
|
|
148
147
|
{pending ? 'Please wait…' : token ? 'Update password' : 'Send reset link'}
|
|
149
148
|
</Button>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
{token ? (
|
|
150
|
+
<Link href="/login" className="text-center text-sm text-muted-foreground underline-offset-4 hover:underline">
|
|
151
|
+
Back to sign in
|
|
152
|
+
</Link>
|
|
153
|
+
) : null}
|
|
153
154
|
</CardFooter>
|
|
154
155
|
</form>
|
|
155
156
|
)}
|
|
@@ -96,9 +96,6 @@ export const getStaticProps = async (context) => {
|
|
|
96
96
|
const previousComponent = sameGroupComponents[groupIndex - 1] ?? null;
|
|
97
97
|
const nextComponent = sameGroupComponents[groupIndex + 1] ?? null;
|
|
98
98
|
|
|
99
|
-
const fallbackTitle = componentData.title || startCase(component as string);
|
|
100
|
-
const fallbackMetaTitle = `${fallbackTitle}${config?.app?.client ? ` | ${config.app.client} Design System` : ''}`;
|
|
101
|
-
|
|
102
99
|
return {
|
|
103
100
|
props: {
|
|
104
101
|
id: component,
|
|
@@ -110,7 +107,7 @@ export const getStaticProps = async (context) => {
|
|
|
110
107
|
...componentData,
|
|
111
108
|
title: componentData.title || docs.metadata.title || startCase(component as string),
|
|
112
109
|
description: componentData.description,
|
|
113
|
-
metaTitle: docs.metadata.metaTitle
|
|
110
|
+
metaTitle: docs.metadata.metaTitle ?? '',
|
|
114
111
|
metaDescription: docs.metadata.metaDescription || componentData.description,
|
|
115
112
|
image: docs.metadata.image || 'hero-brand-assets',
|
|
116
113
|
},
|
|
@@ -256,7 +253,7 @@ const GenericComponentPage = ({ menu, metadata, current, id, config, componentHo
|
|
|
256
253
|
</Button>
|
|
257
254
|
)}
|
|
258
255
|
<Drawer direction="right">
|
|
259
|
-
<DrawerTrigger>
|
|
256
|
+
<DrawerTrigger asChild>
|
|
260
257
|
<Button variant="outline" size={'sm'} className="font-normal [&_svg]:size-3!">
|
|
261
258
|
API Reference
|
|
262
259
|
</Button>
|
|
@@ -107,20 +107,20 @@ const ComponentsPage = ({ content, menu, metadata, current, config }: ComponentP
|
|
|
107
107
|
<div className="mt-3 flex flex-row justify-between gap-3">
|
|
108
108
|
<p className="text-lg leading-relaxed text-gray-600 dark:text-gray-300">{metadata.description}</p>
|
|
109
109
|
<Drawer direction="right">
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
<
|
|
113
|
-
<
|
|
110
|
+
<TooltipProvider>
|
|
111
|
+
<Tooltip>
|
|
112
|
+
<TooltipTrigger asChild>
|
|
113
|
+
<DrawerTrigger asChild>
|
|
114
114
|
<Button variant="outline">
|
|
115
115
|
API <Webhook strokeWidth={1.5} />
|
|
116
116
|
</Button>
|
|
117
|
-
</
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</
|
|
121
|
-
</
|
|
122
|
-
</
|
|
123
|
-
</
|
|
117
|
+
</DrawerTrigger>
|
|
118
|
+
</TooltipTrigger>
|
|
119
|
+
<TooltipContent side="left">
|
|
120
|
+
<Badge>{apiUrl}</Badge>
|
|
121
|
+
</TooltipContent>
|
|
122
|
+
</Tooltip>
|
|
123
|
+
</TooltipProvider>
|
|
124
124
|
<DrawerContent>
|
|
125
125
|
<div className="mx-5 w-full max-w-lg">
|
|
126
126
|
<DrawerHeader>
|
|
@@ -130,7 +130,7 @@ export const getStaticProps = async (context: { params: IParams }) => {
|
|
|
130
130
|
metadata: {
|
|
131
131
|
title: fallbackTitle,
|
|
132
132
|
description: patternData?.description || '',
|
|
133
|
-
metaTitle:
|
|
133
|
+
metaTitle: '',
|
|
134
134
|
metaDescription: patternData?.description || '',
|
|
135
135
|
},
|
|
136
136
|
previousPattern,
|
|
@@ -189,7 +189,7 @@ const PatternPage = ({ menu, metadata, current, id, config, previousPattern, nex
|
|
|
189
189
|
|
|
190
190
|
if (patternNotFound) {
|
|
191
191
|
return (
|
|
192
|
-
<Layout config={config} menu={menu} current={current} metadata={{ ...metadata, title: '
|
|
192
|
+
<Layout config={config} menu={menu} current={current} metadata={{ ...metadata, title: 'Page Not Found', metaTitle: '' }}>
|
|
193
193
|
<div className="flex min-h-[60vh] flex-col items-center justify-center">
|
|
194
194
|
<NotFound />
|
|
195
195
|
</div>
|
|
@@ -70,14 +70,10 @@ export const getStaticProps = async (context) => {
|
|
|
70
70
|
description = componentData?.description;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const fallbackTitle = displayName || startCase(componentSlug);
|
|
74
|
-
const fallbackMetaTitle = `${fallbackTitle}${config?.app?.client ? ` | ${config.app.client} Design System` : ''}`;
|
|
75
|
-
|
|
76
73
|
markdownProps.metadata = {
|
|
77
74
|
...markdownProps.metadata,
|
|
78
75
|
title: displayName || markdownProps.metadata.title || startCase(componentSlug),
|
|
79
76
|
description: markdownProps.metadata.description || description || '',
|
|
80
|
-
metaTitle: markdownProps.metadata.metaTitle || fallbackMetaTitle,
|
|
81
77
|
metaDescription: markdownProps.metadata.metaDescription || description || '',
|
|
82
78
|
};
|
|
83
79
|
|
|
@@ -9,7 +9,8 @@ Builds and runs the Next.js documentation app from runtime config + generated pr
|
|
|
9
9
|
|
|
10
10
|
2. **Build component artifacts**
|
|
11
11
|
- Production: `buildComponents` + `buildPatterns`.
|
|
12
|
-
- Watch mode: `processComponents(
|
|
12
|
+
- Watch mode: `processComponents(handoff)`.
|
|
13
|
+
- Every full component build uses the build cache to skip unchanged components (see `src/cache`).
|
|
13
14
|
|
|
14
15
|
3. **Build global bundles**
|
|
15
16
|
- Build main JS/CSS entry bundles when configured.
|