@agent-native/core 0.164.4 → 0.164.12
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/corpus/README.md +1 -1
- package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
- package/corpus/templates/calendar/app/i18n-data.ts +120 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +83 -78
- package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +1 -0
- package/corpus/templates/clips/desktop/README.md +19 -4
- package/corpus/templates/clips/desktop/src/app.tsx +78 -16
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
- package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
- package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
- package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
- package/corpus/templates/factory/AGENTS.md +3 -2
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +47 -0
- package/corpus/templates/factory/app/routes/factory.tsx +324 -61
- package/corpus/templates/factory/server/connectors/slack.ts +44 -2
- package/corpus/templates/factory/server/plugins/agent-chat.ts +12 -8
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
- package/corpus/templates/mail/actions/view-screen.ts +1 -1
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
- package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
- package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
- package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
- package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
- package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
- package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
- package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
- package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
- package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
- package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
- package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
- package/corpus/templates/slides/server/db/schema.ts +8 -0
- package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
- package/corpus/templates/slides/server/lib/emails.ts +26 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
- package/corpus/templates/slides/server/plugins/db.ts +50 -0
- package/corpus/templates/slides/shared/deck-access.ts +31 -0
- package/dist/a2a/task-store.d.ts +1 -0
- package/dist/a2a/task-store.js +1 -1
- package/dist/agent/observational-memory/store.d.ts +1 -0
- package/dist/agent/observational-memory/store.js +1 -1
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +1 -1
- package/dist/agent/tool-approval-store.d.ts +1 -0
- package/dist/agent/tool-approval-store.js +1 -1
- package/dist/application-state/store.d.ts +1 -0
- package/dist/application-state/store.js +1 -1
- package/dist/browser-sessions/store.d.ts +1 -0
- package/dist/browser-sessions/store.js +1 -1
- package/dist/checkpoints/store.d.ts +1 -0
- package/dist/checkpoints/store.js +1 -1
- package/dist/cli/code-agent-schedules.d.ts +11 -0
- package/dist/cli/code-agent-schedules.js +44 -5
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AssistantChat.d.ts +5 -1
- package/dist/client/AssistantChat.js +40 -6
- package/dist/client/EnvironmentBadge.d.ts +7 -13
- package/dist/client/EnvironmentBadge.js +43 -92
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/agent-chat-adapter.js +76 -50
- package/dist/client/app-providers.js +4 -4
- package/dist/client/chat/message-components.d.ts +4 -0
- package/dist/client/chat/message-components.js +28 -1
- package/dist/client/chat/run-recovery.js +26 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -0
- package/dist/client/chat-first/apps-rail.js +28 -2
- package/dist/client/sse-event-processor.js +13 -8
- package/dist/client/use-active-agent-chat-run.d.ts +2 -0
- package/dist/client/use-active-agent-chat-run.js +22 -0
- package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
- package/dist/coding-tools/sandbox/executions-store.js +1 -1
- package/dist/collab/awareness-store.d.ts +1 -0
- package/dist/collab/awareness-store.js +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts +1 -0
- package/dist/collab/storage.js +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.js +3 -3
- package/dist/db/ddl-guard.js +8 -0
- package/dist/db/migration-runtime.d.ts +23 -0
- package/dist/db/migration-runtime.js +41 -0
- package/dist/db/migrations.d.ts +1 -2
- package/dist/db/migrations.js +2 -24
- package/dist/email-catalog/log.d.ts +1 -0
- package/dist/email-catalog/log.js +1 -1
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.js +1 -0
- package/dist/guards/release-schema-complete.d.ts +22 -0
- package/dist/guards/release-schema-complete.js +144 -0
- package/dist/integrations/a2a-continuations-store.d.ts +1 -0
- package/dist/integrations/a2a-continuations-store.js +1 -1
- package/dist/integrations/awaiting-input-store.d.ts +1 -0
- package/dist/integrations/awaiting-input-store.js +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.js +1 -1
- package/dist/integrations/controls-store.d.ts +1 -0
- package/dist/integrations/controls-store.js +1 -1
- package/dist/integrations/identity-links-store.d.ts +1 -0
- package/dist/integrations/identity-links-store.js +1 -1
- package/dist/integrations/installations-store.d.ts +1 -0
- package/dist/integrations/installations-store.js +1 -1
- package/dist/integrations/remote-commands-store.d.ts +1 -0
- package/dist/integrations/remote-commands-store.js +1 -1
- package/dist/integrations/remote-devices-store.d.ts +1 -0
- package/dist/integrations/remote-devices-store.js +1 -1
- package/dist/integrations/remote-push-store.d.ts +1 -0
- package/dist/integrations/remote-push-store.js +1 -1
- package/dist/integrations/remote-run-events-store.d.ts +1 -0
- package/dist/integrations/remote-run-events-store.js +1 -1
- package/dist/integrations/scope-store.d.ts +1 -0
- package/dist/integrations/scope-store.js +1 -1
- package/dist/integrations/thread-mapping-store.d.ts +1 -0
- package/dist/integrations/thread-mapping-store.js +1 -1
- package/dist/integrations/usage-budget-store.d.ts +1 -0
- package/dist/integrations/usage-budget-store.js +1 -1
- package/dist/jobs/background-automation-runner.js +111 -9
- package/dist/jobs/run-history.d.ts +1 -0
- package/dist/jobs/run-history.js +1 -1
- package/dist/jobs/scheduler-health.d.ts +1 -0
- package/dist/jobs/scheduler-health.js +1 -1
- package/dist/mcp/approval-store.d.ts +1 -0
- package/dist/mcp/approval-store.js +1 -1
- package/dist/mcp/connect-store.d.ts +1 -0
- package/dist/mcp/connect-store.js +1 -1
- package/dist/mcp/oauth-store.d.ts +1 -0
- package/dist/mcp/oauth-store.js +1 -1
- package/dist/notifications/store.d.ts +1 -0
- package/dist/notifications/store.js +1 -1
- package/dist/oauth-tokens/store.d.ts +1 -0
- package/dist/oauth-tokens/store.js +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts +1 -0
- package/dist/progress/store.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
- package/dist/provider-api/corpus-jobs-store.js +1 -1
- package/dist/provider-api/custom-registry.d.ts +1 -0
- package/dist/provider-api/custom-registry.js +1 -1
- package/dist/provider-api/quota-governor.d.ts +1 -0
- package/dist/provider-api/quota-governor.js +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts +1 -0
- package/dist/provider-api/staged-datasets-store.js +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts +1 -0
- package/dist/resources/store.js +1 -1
- package/dist/scripts/dev/index.js +15 -1
- package/dist/search/index.js +3 -0
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/secrets/storage.d.ts +1 -0
- package/dist/secrets/storage.js +1 -1
- package/dist/server/agent-chat-plugin.js +38 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +1 -0
- package/dist/server/agent-teams-run-queue.js +1 -1
- package/dist/server/auth-marketing.js +3 -1
- package/dist/server/auth.d.ts +18 -1
- package/dist/server/auth.js +370 -154
- package/dist/server/embed-session.d.ts +1 -0
- package/dist/server/embed-session.js +1 -1
- package/dist/server/google-auth-plugin.js +3 -1
- package/dist/server/google-oauth.d.ts +10 -0
- package/dist/server/google-oauth.js +25 -1
- package/dist/server/identity-sso-store.d.ts +1 -0
- package/dist/server/identity-sso-store.js +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/onboarding-html.js +200 -31
- package/dist/server/poll.d.ts +2 -0
- package/dist/server/poll.js +19 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +5 -0
- package/dist/server/release-schema.d.ts +46 -0
- package/dist/server/release-schema.js +307 -0
- package/dist/settings/store.d.ts +1 -0
- package/dist/settings/store.js +1 -1
- package/dist/shared/environment-lanes.d.ts +28 -0
- package/dist/shared/environment-lanes.js +61 -0
- package/dist/sharing/actions/share-resource.js +22 -2
- package/dist/styles/agent-native.css +17 -15
- package/dist/triggers/routes.d.ts +11 -0
- package/dist/triggers/routes.js +10 -0
- package/dist/usage/alerts-store.d.ts +1 -0
- package/dist/usage/alerts-store.js +1 -1
- package/dist/usage/store.d.ts +1 -0
- package/dist/usage/store.js +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +2 -2
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -2,17 +2,96 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { defineAction } from "@agent-native/core";
|
|
4
4
|
import { notifyWithDelivery } from "@agent-native/core/notifications";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isEmailConfigured,
|
|
7
|
+
sendEmail,
|
|
8
|
+
signScopedAgentAccessToken,
|
|
9
|
+
verifyScopedAgentAccessToken,
|
|
10
|
+
} from "@agent-native/core/server";
|
|
6
11
|
import {
|
|
7
12
|
getRequestUserEmail,
|
|
8
13
|
getRequestUserName,
|
|
9
14
|
} from "@agent-native/core/server/request-context";
|
|
10
15
|
import { currentAccess, resolveAccess } from "@agent-native/core/sharing";
|
|
11
|
-
import { and, eq } from "drizzle-orm";
|
|
16
|
+
import { and, eq, sql } from "drizzle-orm";
|
|
12
17
|
import { z } from "zod";
|
|
13
18
|
|
|
14
19
|
import { getDb, schema } from "../server/db/index.js";
|
|
15
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
renderDeckAccessRequestEmail,
|
|
22
|
+
SLIDES_DECK_ACCESS_REQUEST_EMAIL_ID,
|
|
23
|
+
} from "../server/lib/access-request-email.js";
|
|
24
|
+
import {
|
|
25
|
+
SLIDES_ACCESS_REQUEST_FALLBACK_TOKEN_PREFIX,
|
|
26
|
+
SLIDES_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
27
|
+
deckAccessApprovalPath,
|
|
28
|
+
SLIDES_ACCESS_APPROVAL_TOKEN_PREFIX,
|
|
29
|
+
SLIDES_ACCESS_APPROVAL_TOKEN_TTL_SECONDS,
|
|
30
|
+
} from "../shared/deck-access.js";
|
|
31
|
+
import { getDeckUrl, getSlidesAppUrl } from "./_app-url.js";
|
|
32
|
+
|
|
33
|
+
const ANONYMOUS_ACCESS_REQUEST_WINDOW_MS = 10 * 60 * 1000;
|
|
34
|
+
const ANONYMOUS_ACCESS_REQUEST_MAX = 5;
|
|
35
|
+
|
|
36
|
+
async function claimAnonymousAccessRequestSlot(
|
|
37
|
+
db: ReturnType<typeof getDb>,
|
|
38
|
+
deckId: string,
|
|
39
|
+
): Promise<{ windowStartedAt: string }> {
|
|
40
|
+
const now = new Date();
|
|
41
|
+
const windowStartedAt = now.toISOString();
|
|
42
|
+
const resetBefore = new Date(
|
|
43
|
+
now.getTime() - ANONYMOUS_ACCESS_REQUEST_WINDOW_MS,
|
|
44
|
+
).toISOString();
|
|
45
|
+
const limits = schema.deckAccessRequestLimits;
|
|
46
|
+
const [bucket] = await db
|
|
47
|
+
.insert(limits)
|
|
48
|
+
.values({
|
|
49
|
+
deckId,
|
|
50
|
+
windowStartedAt,
|
|
51
|
+
requestCount: 1,
|
|
52
|
+
})
|
|
53
|
+
.onConflictDoUpdate({
|
|
54
|
+
target: limits.deckId,
|
|
55
|
+
set: {
|
|
56
|
+
windowStartedAt: sql`CASE WHEN ${limits.windowStartedAt} <= ${resetBefore} THEN ${windowStartedAt} ELSE ${limits.windowStartedAt} END`,
|
|
57
|
+
requestCount: sql`CASE WHEN ${limits.windowStartedAt} <= ${resetBefore} THEN 1 ELSE ${limits.requestCount} + 1 END`,
|
|
58
|
+
},
|
|
59
|
+
where: sql`${limits.windowStartedAt} <= ${resetBefore} OR ${limits.requestCount} < ${ANONYMOUS_ACCESS_REQUEST_MAX}`,
|
|
60
|
+
})
|
|
61
|
+
.returning({
|
|
62
|
+
requestCount: limits.requestCount,
|
|
63
|
+
windowStartedAt: limits.windowStartedAt,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!bucket) {
|
|
67
|
+
throw httpError(
|
|
68
|
+
"Too many anonymous access requests for this deck. Try again later.",
|
|
69
|
+
429,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return { windowStartedAt: bucket.windowStartedAt };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function refundAnonymousAccessRequestSlot(
|
|
76
|
+
db: ReturnType<typeof getDb>,
|
|
77
|
+
deckId: string,
|
|
78
|
+
windowStartedAt: string,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const limits = schema.deckAccessRequestLimits;
|
|
81
|
+
await db
|
|
82
|
+
.update(limits)
|
|
83
|
+
.set({
|
|
84
|
+
requestCount: sql`${limits.requestCount} - 1`,
|
|
85
|
+
})
|
|
86
|
+
.where(
|
|
87
|
+
and(
|
|
88
|
+
eq(limits.deckId, deckId),
|
|
89
|
+
eq(limits.windowStartedAt, windowStartedAt),
|
|
90
|
+
sql`${limits.requestCount} > 0`,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
.returning({ deckId: limits.deckId });
|
|
94
|
+
}
|
|
16
95
|
|
|
17
96
|
function httpError(message: string, statusCode: number): Error {
|
|
18
97
|
return Object.assign(new Error(message), { statusCode });
|
|
@@ -35,8 +114,11 @@ function normalizeEmail(email: string): string {
|
|
|
35
114
|
return email.trim().toLowerCase();
|
|
36
115
|
}
|
|
37
116
|
|
|
38
|
-
function
|
|
39
|
-
|
|
117
|
+
function absoluteDeckAccessApprovalUrl(
|
|
118
|
+
deckId: string,
|
|
119
|
+
approvalToken: string,
|
|
120
|
+
): string {
|
|
121
|
+
return `${getSlidesAppUrl().replace(/\/+$/, "")}${deckAccessApprovalPath(deckId, approvalToken)}`;
|
|
40
122
|
}
|
|
41
123
|
|
|
42
124
|
function accessRequestEventId(deckId: string, requesterEmail: string): string {
|
|
@@ -55,6 +137,9 @@ type AccessRequestPayload = {
|
|
|
55
137
|
requesterEmail?: string;
|
|
56
138
|
requesterName?: string;
|
|
57
139
|
requestedAt?: string;
|
|
140
|
+
approvalTokenHash?: string;
|
|
141
|
+
accessGrantedAt?: string;
|
|
142
|
+
accessShareId?: string;
|
|
58
143
|
notifiedOwner?: boolean;
|
|
59
144
|
notifiedAt?: string;
|
|
60
145
|
inAppNotified?: boolean;
|
|
@@ -68,6 +153,7 @@ type AccessRequestNotificationState = {
|
|
|
68
153
|
emailNotified: boolean;
|
|
69
154
|
emailRequired: boolean;
|
|
70
155
|
ownerCanNotify: boolean;
|
|
156
|
+
approvalTokenHash?: string;
|
|
71
157
|
};
|
|
72
158
|
|
|
73
159
|
const NOTIFICATION_CLAIM_TTL_MS = 5 * 60 * 1000;
|
|
@@ -104,6 +190,7 @@ function notificationStateFor(
|
|
|
104
190
|
emailNotified: payload.emailNotified ?? legacyNotified,
|
|
105
191
|
emailRequired: ownerCanNotify && emailConfigured,
|
|
106
192
|
ownerCanNotify,
|
|
193
|
+
approvalTokenHash: payload.approvalTokenHash,
|
|
107
194
|
};
|
|
108
195
|
}
|
|
109
196
|
|
|
@@ -145,63 +232,148 @@ async function notifyAccessRequestOwner(input: {
|
|
|
145
232
|
const ownerEmail = input.ownerEmail;
|
|
146
233
|
if (!ownerEmail || !input.state.ownerCanNotify) return input.state;
|
|
147
234
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
235
|
+
let state = input.state;
|
|
236
|
+
|
|
237
|
+
if (!state.inAppNotified) {
|
|
238
|
+
try {
|
|
239
|
+
const delivery = await notifyWithDelivery(
|
|
240
|
+
{
|
|
241
|
+
severity: "info",
|
|
242
|
+
title: "Deck access requested",
|
|
243
|
+
body: `${input.requesterName} requested access to “${input.deckTitle}”.`,
|
|
244
|
+
channels: ["inbox"],
|
|
245
|
+
metadata: {
|
|
246
|
+
deckId: input.deckId,
|
|
247
|
+
requesterEmail: input.requesterEmail,
|
|
248
|
+
link: getDeckUrl(input.deckId),
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{ owner: ownerEmail },
|
|
252
|
+
);
|
|
253
|
+
state = {
|
|
254
|
+
...state,
|
|
255
|
+
inAppNotified:
|
|
256
|
+
state.inAppNotified || delivery.deliveredChannels.includes("inbox"),
|
|
257
|
+
};
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.warn(
|
|
260
|
+
"[deck-access] in-app access request notification failed:",
|
|
261
|
+
error,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
157
265
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
266
|
+
if (state.emailRequired && !state.emailNotified) {
|
|
267
|
+
try {
|
|
268
|
+
const approvalToken = signScopedAgentAccessToken({
|
|
269
|
+
resourceKind: SLIDES_ACCESS_APPROVAL_TOKEN_PREFIX,
|
|
270
|
+
resourceId: input.deckId,
|
|
271
|
+
viewerEmail: input.requesterEmail,
|
|
272
|
+
ttlSeconds: SLIDES_ACCESS_APPROVAL_TOKEN_TTL_SECONDS,
|
|
273
|
+
});
|
|
274
|
+
await sendEmail({
|
|
275
|
+
...renderDeckAccessRequestEmail({
|
|
276
|
+
requesterName: input.requesterName,
|
|
167
277
|
requesterEmail: input.requesterEmail,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
278
|
+
deckTitle: input.deckTitle,
|
|
279
|
+
url: getDeckUrl(input.deckId),
|
|
280
|
+
allowAccessUrl: absoluteDeckAccessApprovalUrl(
|
|
281
|
+
input.deckId,
|
|
282
|
+
approvalToken,
|
|
283
|
+
),
|
|
284
|
+
}),
|
|
285
|
+
to: ownerEmail,
|
|
286
|
+
replyTo: input.requesterEmail,
|
|
287
|
+
templateId: SLIDES_DECK_ACCESS_REQUEST_EMAIL_ID,
|
|
288
|
+
});
|
|
289
|
+
state = {
|
|
290
|
+
...state,
|
|
291
|
+
emailNotified: true,
|
|
292
|
+
approvalTokenHash: createHash("sha256")
|
|
293
|
+
.update(approvalToken)
|
|
294
|
+
.digest("hex"),
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.warn("[deck-access] access request email failed:", error);
|
|
298
|
+
}
|
|
189
299
|
}
|
|
300
|
+
|
|
301
|
+
return state;
|
|
190
302
|
}
|
|
191
303
|
|
|
192
304
|
export default defineAction({
|
|
193
305
|
description:
|
|
194
|
-
"Request access to a private Agent-Native Slides deck. Records an access-request event and notifies the owner
|
|
306
|
+
"Request access to a private Agent-Native Slides deck. Signed-in viewers use their account email; anonymous viewers may provide an email address. Records an access-request event and notifies the owner in-app and by email when configured.",
|
|
195
307
|
schema: z.object({
|
|
196
308
|
deckId: z.string().min(1).describe("Deck ID to request access to."),
|
|
309
|
+
accessRequestToken: z
|
|
310
|
+
.string()
|
|
311
|
+
.trim()
|
|
312
|
+
.min(1)
|
|
313
|
+
.optional()
|
|
314
|
+
.describe("Short-lived capability from the private deck page."),
|
|
315
|
+
requesterEmail: z
|
|
316
|
+
.string()
|
|
317
|
+
.trim()
|
|
318
|
+
.email()
|
|
319
|
+
.optional()
|
|
320
|
+
.describe(
|
|
321
|
+
"Email address to request access for when the viewer is not signed in.",
|
|
322
|
+
),
|
|
197
323
|
}),
|
|
324
|
+
requiresAuth: false,
|
|
198
325
|
agentTool: false,
|
|
199
|
-
run: async ({
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
326
|
+
run: async ({
|
|
327
|
+
deckId,
|
|
328
|
+
accessRequestToken,
|
|
329
|
+
requesterEmail: requesterEmailInput,
|
|
330
|
+
}) => {
|
|
331
|
+
const sessionEmail = getRequestUserEmail();
|
|
332
|
+
let requestToken = accessRequestToken
|
|
333
|
+
? verifyScopedAgentAccessToken(accessRequestToken, {
|
|
334
|
+
resourceKind: SLIDES_ACCESS_REQUEST_TOKEN_PREFIX,
|
|
335
|
+
resourceId: deckId,
|
|
336
|
+
})
|
|
337
|
+
: { ok: false as const, reason: "missing" };
|
|
338
|
+
let accessProbeWasUnavailable = false;
|
|
339
|
+
if (accessRequestToken && !requestToken.ok) {
|
|
340
|
+
const fallbackToken = verifyScopedAgentAccessToken(accessRequestToken, {
|
|
341
|
+
resourceKind: SLIDES_ACCESS_REQUEST_FALLBACK_TOKEN_PREFIX,
|
|
342
|
+
resourceId: deckId,
|
|
343
|
+
});
|
|
344
|
+
if (fallbackToken.ok) {
|
|
345
|
+
requestToken = fallbackToken;
|
|
346
|
+
accessProbeWasUnavailable = true;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (accessRequestToken && !requestToken.ok) {
|
|
350
|
+
throw httpError(`Deck ${deckId} not found`, 404);
|
|
351
|
+
}
|
|
352
|
+
if (!sessionEmail && !requestToken.ok) {
|
|
353
|
+
throw httpError(`Deck ${deckId} not found`, 404);
|
|
354
|
+
}
|
|
355
|
+
const normalizedRequesterEmail = sessionEmail
|
|
356
|
+
? normalizeEmail(sessionEmail)
|
|
357
|
+
: requesterEmailInput
|
|
358
|
+
? normalizeEmail(requesterEmailInput)
|
|
359
|
+
: null;
|
|
360
|
+
if (!normalizedRequesterEmail) {
|
|
361
|
+
throw httpError(
|
|
362
|
+
"Sign in or provide an email address to request access to this deck.",
|
|
363
|
+
401,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const tokenViewerEmail =
|
|
367
|
+
requestToken.ok && requestToken.viewerEmail
|
|
368
|
+
? normalizeEmail(requestToken.viewerEmail)
|
|
369
|
+
: null;
|
|
370
|
+
if (tokenViewerEmail && tokenViewerEmail !== normalizedRequesterEmail) {
|
|
371
|
+
throw httpError(
|
|
372
|
+
"This request is tied to a different email. Sign in with the email that opened the link.",
|
|
373
|
+
403,
|
|
374
|
+
);
|
|
203
375
|
}
|
|
204
|
-
const requesterEmail =
|
|
376
|
+
const requesterEmail = normalizedRequesterEmail;
|
|
205
377
|
|
|
206
378
|
const db = getDb();
|
|
207
379
|
const claimNotification = async (
|
|
@@ -313,6 +485,8 @@ export default defineAction({
|
|
|
313
485
|
inAppNotified: state.inAppNotified,
|
|
314
486
|
emailNotified: state.emailNotified,
|
|
315
487
|
notifiedOwner: delivered,
|
|
488
|
+
approvalTokenHash:
|
|
489
|
+
state.approvalTokenHash ?? claimedPayload.approvalTokenHash,
|
|
316
490
|
notificationClaimedAt: undefined,
|
|
317
491
|
notificationClaimToken: undefined,
|
|
318
492
|
...(delivered ? { notifiedAt: new Date().toISOString() } : {}),
|
|
@@ -355,7 +529,9 @@ export default defineAction({
|
|
|
355
529
|
throw httpError(`Deck ${deckId} not found`, 404);
|
|
356
530
|
}
|
|
357
531
|
|
|
358
|
-
const access =
|
|
532
|
+
const access = accessProbeWasUnavailable
|
|
533
|
+
? null
|
|
534
|
+
: await resolveAccess("deck", deckId, currentAccess());
|
|
359
535
|
if (access) {
|
|
360
536
|
return {
|
|
361
537
|
ok: true as const,
|
|
@@ -491,6 +667,10 @@ export default defineAction({
|
|
|
491
667
|
};
|
|
492
668
|
}
|
|
493
669
|
|
|
670
|
+
const anonymousSlot = sessionEmail
|
|
671
|
+
? null
|
|
672
|
+
: await claimAnonymousAccessRequestSlot(db, deckId);
|
|
673
|
+
|
|
494
674
|
const requestId = accessRequestEventId(deckId, requesterEmail);
|
|
495
675
|
const requestedAt = new Date().toISOString();
|
|
496
676
|
const initialPayload: AccessRequestPayload = {
|
|
@@ -506,21 +686,40 @@ export default defineAction({
|
|
|
506
686
|
};
|
|
507
687
|
const initialPayloadJson = JSON.stringify(initialPayload);
|
|
508
688
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
689
|
+
let insertedRequest: { id: string } | undefined;
|
|
690
|
+
try {
|
|
691
|
+
[insertedRequest] = await db
|
|
692
|
+
.insert(schema.deckEvents)
|
|
693
|
+
.values({
|
|
694
|
+
id: requestId,
|
|
695
|
+
deckId,
|
|
696
|
+
type: "deck.access_requested",
|
|
697
|
+
message: `${requesterEmail} requested access to this deck.`,
|
|
698
|
+
payload: initialPayloadJson,
|
|
699
|
+
createdBy: "human",
|
|
700
|
+
createdAt: requestedAt,
|
|
701
|
+
})
|
|
702
|
+
.onConflictDoNothing()
|
|
703
|
+
.returning({ id: schema.deckEvents.id });
|
|
704
|
+
} catch (error) {
|
|
705
|
+
if (anonymousSlot) {
|
|
706
|
+
await refundAnonymousAccessRequestSlot(
|
|
707
|
+
db,
|
|
708
|
+
deckId,
|
|
709
|
+
anonymousSlot.windowStartedAt,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
522
714
|
|
|
523
715
|
if (!insertedRequest) {
|
|
716
|
+
if (anonymousSlot) {
|
|
717
|
+
await refundAnonymousAccessRequestSlot(
|
|
718
|
+
db,
|
|
719
|
+
deckId,
|
|
720
|
+
anonymousSlot.windowStartedAt,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
524
723
|
return {
|
|
525
724
|
ok: true as const,
|
|
526
725
|
alreadyHasAccess: false,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import {
|
|
3
|
+
IconArrowLeft,
|
|
4
|
+
IconAt,
|
|
5
|
+
IconLock,
|
|
6
|
+
IconLoader2,
|
|
7
|
+
IconLogin2,
|
|
8
|
+
IconRefresh,
|
|
9
|
+
IconUserPlus,
|
|
10
|
+
IconUsersGroup,
|
|
11
|
+
} from "@tabler/icons-react";
|
|
12
|
+
import type { FormEvent } from "react";
|
|
13
|
+
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import type { DeckAccessStatusResponse } from "@/hooks/use-deck-access";
|
|
16
|
+
|
|
17
|
+
import { RequestDeckAccessDialog } from "./RequestDeckAccessDialog";
|
|
18
|
+
|
|
19
|
+
export function MissingDeckAccessPane({
|
|
20
|
+
accessStatus,
|
|
21
|
+
accessStatusError,
|
|
22
|
+
accessStatusLoading,
|
|
23
|
+
hasTeamJoinOption,
|
|
24
|
+
orgLoading,
|
|
25
|
+
orgError,
|
|
26
|
+
requestAccessPending,
|
|
27
|
+
accessRequestSent,
|
|
28
|
+
accessRequestNotified,
|
|
29
|
+
requestAccessDialogOpen,
|
|
30
|
+
requesterEmail,
|
|
31
|
+
requestAccessDialogError,
|
|
32
|
+
signedIn,
|
|
33
|
+
signInHref,
|
|
34
|
+
viewerEmail,
|
|
35
|
+
refreshing,
|
|
36
|
+
onRequestAccess,
|
|
37
|
+
onRequestAccessDialogOpenChange,
|
|
38
|
+
onRequesterEmailChange,
|
|
39
|
+
onSubmitGuestAccessRequest,
|
|
40
|
+
onSignIn,
|
|
41
|
+
onRetry,
|
|
42
|
+
onBack,
|
|
43
|
+
}: {
|
|
44
|
+
accessStatus: DeckAccessStatusResponse | null;
|
|
45
|
+
accessStatusError: boolean;
|
|
46
|
+
accessStatusLoading: boolean;
|
|
47
|
+
hasTeamJoinOption: boolean;
|
|
48
|
+
orgLoading: boolean;
|
|
49
|
+
orgError: boolean;
|
|
50
|
+
requestAccessPending: boolean;
|
|
51
|
+
accessRequestSent: boolean;
|
|
52
|
+
accessRequestNotified: boolean;
|
|
53
|
+
requestAccessDialogOpen: boolean;
|
|
54
|
+
requesterEmail: string;
|
|
55
|
+
requestAccessDialogError: string | null;
|
|
56
|
+
signedIn: boolean;
|
|
57
|
+
signInHref: string;
|
|
58
|
+
viewerEmail: string | null;
|
|
59
|
+
refreshing: boolean;
|
|
60
|
+
onRequestAccess: () => void;
|
|
61
|
+
onRequestAccessDialogOpenChange: (open: boolean) => void;
|
|
62
|
+
onRequesterEmailChange: (email: string) => void;
|
|
63
|
+
onSubmitGuestAccessRequest: (event: FormEvent<HTMLFormElement>) => void;
|
|
64
|
+
onSignIn: () => void;
|
|
65
|
+
onRetry: () => void;
|
|
66
|
+
onBack: () => void;
|
|
67
|
+
}) {
|
|
68
|
+
const t = useT();
|
|
69
|
+
const privateDeck = Boolean(
|
|
70
|
+
accessStatus?.exists &&
|
|
71
|
+
!accessStatus.hasAccess &&
|
|
72
|
+
accessStatus.visibility === "private",
|
|
73
|
+
);
|
|
74
|
+
const accessCheckFailed = accessStatusError || orgError;
|
|
75
|
+
const noAccess = privateDeck || accessCheckFailed;
|
|
76
|
+
const checkingAccess = !noAccess && (accessStatusLoading || orgLoading);
|
|
77
|
+
const Icon =
|
|
78
|
+
noAccess || (!hasTeamJoinOption && !checkingAccess)
|
|
79
|
+
? IconLock
|
|
80
|
+
: IconUsersGroup;
|
|
81
|
+
const title = checkingAccess
|
|
82
|
+
? t("deckEditor.lookingForDeck")
|
|
83
|
+
: noAccess
|
|
84
|
+
? t("deckEditor.privateDeckTitle")
|
|
85
|
+
: hasTeamJoinOption
|
|
86
|
+
? t("deckEditor.joinTeamToOpen")
|
|
87
|
+
: t("deckEditor.deckUnavailable");
|
|
88
|
+
const description = checkingAccess
|
|
89
|
+
? t("deckEditor.checkingSharedAccess")
|
|
90
|
+
: noAccess
|
|
91
|
+
? t("deckEditor.privateDeckDescription")
|
|
92
|
+
: hasTeamJoinOption
|
|
93
|
+
? t("deckEditor.joinTeamDescription")
|
|
94
|
+
: t("deckEditor.deckUnavailableDescription");
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="flex min-h-screen items-center justify-center bg-background px-4 py-10">
|
|
98
|
+
<div className="w-full max-w-md rounded-lg border border-border bg-card p-6 shadow-sm">
|
|
99
|
+
<div className="mb-4 flex items-center gap-3">
|
|
100
|
+
<div className="flex size-10 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
|
|
101
|
+
<Icon className="size-5" />
|
|
102
|
+
</div>
|
|
103
|
+
<div className="min-w-0">
|
|
104
|
+
<h1 className="text-base font-semibold text-foreground">{title}</h1>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<p className="text-sm leading-6 text-muted-foreground">{description}</p>
|
|
108
|
+
{noAccess && viewerEmail ? (
|
|
109
|
+
<div className="mt-4 flex items-center gap-2 rounded-md border border-border bg-muted/35 px-3 py-2 text-sm">
|
|
110
|
+
<IconAt className="size-4 shrink-0 text-muted-foreground" />
|
|
111
|
+
<span className="min-w-0 truncate text-muted-foreground">
|
|
112
|
+
{t("deckEditor.signedInAs")}{" "}
|
|
113
|
+
<span className="font-medium text-foreground">{viewerEmail}</span>
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
) : null}
|
|
117
|
+
{noAccess && accessRequestSent ? (
|
|
118
|
+
<div className="mt-3 rounded-md border border-emerald-500/25 bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700 dark:text-emerald-300">
|
|
119
|
+
{t(
|
|
120
|
+
accessRequestNotified
|
|
121
|
+
? "deckEditor.accessRequestSentDescription"
|
|
122
|
+
: "deckEditor.accessRequestRecordedDescription",
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
) : null}
|
|
126
|
+
<div className="mt-5 flex flex-col gap-2">
|
|
127
|
+
{noAccess && !signedIn ? (
|
|
128
|
+
<>
|
|
129
|
+
<Button type="button" onClick={onSignIn}>
|
|
130
|
+
<IconLogin2 className="size-4" />
|
|
131
|
+
{t("deckEditor.signInToRequestAccess")}
|
|
132
|
+
</Button>
|
|
133
|
+
<Button
|
|
134
|
+
type="button"
|
|
135
|
+
variant="outline"
|
|
136
|
+
onClick={() => onRequestAccessDialogOpenChange(true)}
|
|
137
|
+
disabled={requestAccessPending || accessRequestSent}
|
|
138
|
+
>
|
|
139
|
+
<IconUserPlus className="size-4" />
|
|
140
|
+
{accessRequestSent
|
|
141
|
+
? t("deckEditor.accessRequestSent")
|
|
142
|
+
: t("deckEditor.requestAccess")}
|
|
143
|
+
</Button>
|
|
144
|
+
</>
|
|
145
|
+
) : noAccess ? (
|
|
146
|
+
<Button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={onRequestAccess}
|
|
149
|
+
disabled={requestAccessPending || accessRequestSent}
|
|
150
|
+
>
|
|
151
|
+
{requestAccessPending ? (
|
|
152
|
+
<IconLoader2 className="size-4 animate-spin" />
|
|
153
|
+
) : (
|
|
154
|
+
<IconUserPlus className="size-4" />
|
|
155
|
+
)}
|
|
156
|
+
{requestAccessPending
|
|
157
|
+
? t("deckEditor.requestAccessPending")
|
|
158
|
+
: accessRequestSent
|
|
159
|
+
? t("deckEditor.accessRequestSent")
|
|
160
|
+
: t("deckEditor.requestAccess")}
|
|
161
|
+
</Button>
|
|
162
|
+
) : null}
|
|
163
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
|
|
164
|
+
<Button type="button" variant="outline" onClick={onBack}>
|
|
165
|
+
<IconArrowLeft className="size-4" />
|
|
166
|
+
{t("deckEditor.backToDecks")}
|
|
167
|
+
</Button>
|
|
168
|
+
<Button
|
|
169
|
+
type="button"
|
|
170
|
+
variant={noAccess ? "ghost" : "default"}
|
|
171
|
+
onClick={onRetry}
|
|
172
|
+
disabled={refreshing || checkingAccess}
|
|
173
|
+
>
|
|
174
|
+
<IconRefresh
|
|
175
|
+
className={refreshing ? "size-4 animate-spin" : "size-4"}
|
|
176
|
+
/>
|
|
177
|
+
{t("deckEditor.tryAgain")}
|
|
178
|
+
</Button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
{noAccess && !signedIn ? (
|
|
183
|
+
<RequestDeckAccessDialog
|
|
184
|
+
open={requestAccessDialogOpen}
|
|
185
|
+
onOpenChange={onRequestAccessDialogOpenChange}
|
|
186
|
+
signInHref={signInHref}
|
|
187
|
+
email={requesterEmail}
|
|
188
|
+
onEmailChange={onRequesterEmailChange}
|
|
189
|
+
onSubmit={onSubmitGuestAccessRequest}
|
|
190
|
+
isSubmitting={requestAccessPending}
|
|
191
|
+
error={requestAccessDialogError}
|
|
192
|
+
/>
|
|
193
|
+
) : null}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|