Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/team-invite-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"server": minor
"dashboard": minor
---

Add team invite flow with accept page, configurable expiry, and security hardening
11 changes: 11 additions & 0 deletions .speakeasy/out.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19311,12 +19311,21 @@ components:
created_at:
type: string
format: date-time
email:
type: string
description: User email address.
id:
type: string
description: Gram relationship row ID.
name:
type: string
description: User display name.
organization_id:
type: string
description: Gram organization ID.
photo_url:
type: string
description: User photo URL.
updated_at:
type: string
format: date-time
Expand All @@ -19330,6 +19339,8 @@ components:
- id
- organization_id
- user_id
- name
- email
- created_at
- updated_at
Package:
Expand Down
10 changes: 4 additions & 6 deletions .speakeasy/workflow.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@ speakeasyVersion: 1.730.1
sources:
Gram-Internal:
sourceNamespace: gram-api-description
sourceRevisionDigest: sha256:ea6451246f12798f5d5e0bc6c77f8cb2ac5271624edbafd33a4bf261ef3641ed
sourceBlobDigest: sha256:914ac860d58d68c0a6aa122bc5887f5a608fd8a98065f0db2a23b625675ba7f0
sourceRevisionDigest: sha256:e424afbe13dec4c548733505642fdeec1cff508a08d468d3c62221706c5c917a
sourceBlobDigest: sha256:cd2369f7854dea0cc38d1046ca3c528386badbb2dcc9f7a23fade01f4246b11b
tags:
- latest
- 0.0.1
targets:
gram-internal:
source: Gram-Internal
sourceNamespace: gram-api-description
sourceRevisionDigest: sha256:ea6451246f12798f5d5e0bc6c77f8cb2ac5271624edbafd33a4bf261ef3641ed
sourceBlobDigest: sha256:914ac860d58d68c0a6aa122bc5887f5a608fd8a98065f0db2a23b625675ba7f0
codeSamplesNamespace: gram-api-description-typescript-code-samples
codeSamplesRevisionDigest: sha256:08f3e13cb1e424beba82fe8b2e0845dd8eb7460203f49a1eba0d74e3b57b54cb
sourceRevisionDigest: sha256:e424afbe13dec4c548733505642fdeec1cff508a08d468d3c62221706c5c917a
sourceBlobDigest: sha256:cd2369f7854dea0cc38d1046ca3c528386badbb2dcc9f7a23fade01f4246b11b
workflow:
workflowVersion: 1.0.0
speakeasyVersion: pinned
Expand Down
35 changes: 20 additions & 15 deletions client/dashboard/src/components/org-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export function OrgSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const isAdmin = useIsAdmin();
const telemetry = useTelemetry();
const isRbacEnabled = telemetry.isFeatureEnabled("gram-rbac") ?? false;
const isTeamPageEnabled =
telemetry.isFeatureEnabled("gram-team-page") ?? false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for now, but this is not good - these features def. don't belong in telemetry.

We seriously need to nail down the feature flag strategy

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah this is actually a pattern copied directly from the registry codebase


const teamUrl =
const externalTeamUrl =
organization?.userWorkspaceSlugs &&
organization.userWorkspaceSlugs.length > 0
? `https://app.speakeasy.com/org/${organization.slug}/${organization.userWorkspaceSlugs[0]}/settings/team`
Expand All @@ -42,6 +44,7 @@ export function OrgSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<NavMenu
items={[
orgRoutes.billing,
...(isTeamPageEnabled ? [orgRoutes.team] : []),
orgRoutes.apiKeys,
orgRoutes.domains,
orgRoutes.logs,
Expand All @@ -50,20 +53,22 @@ export function OrgSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
...(isAdmin ? [orgRoutes.adminSettings] : []),
]}
>
<SidebarMenuItem>
<NavButton
title="Team"
titleNode={
<span className="flex items-center gap-1.5">
Team
<ExternalLink className="w-3 h-3 text-muted-foreground" />
</span>
}
href={teamUrl}
target="_blank"
Icon={(props) => <Icon name="users-round" {...props} />}
/>
</SidebarMenuItem>
{!isTeamPageEnabled && (
<SidebarMenuItem>
<NavButton
title="Team"
titleNode={
<span className="flex items-center gap-1.5">
Team
<ExternalLink className="w-3 h-3 text-muted-foreground" />
</span>
}
href={externalTeamUrl}
target="_blank"
Icon={(props) => <Icon name="users-round" {...props} />}
/>
</SidebarMenuItem>
)}
</NavMenu>
</SidebarGroupContent>
</SidebarGroup>
Expand Down
12 changes: 6 additions & 6 deletions client/dashboard/src/contexts/Auth.tsx
Comment thread
adaam2 marked this conversation as resolved.
Comment thread
adaam2 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
useNavigate,
useSearchParams,
} from "react-router";
import { ORG_ROUTE_STRUCTURE } from "@/routes";
import { useSlugs } from "./Sdk";
import {
useCaptureUserAuthorizationEvent,
Expand Down Expand Up @@ -212,7 +213,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {

// Paths that don't require authentication — skip the loading shell on these
// to avoid a brief flash of the authenticated skeleton (e.g. after logout).
const UNAUTHENTICATED_PATHS = ["/login", "/register", "/book-demo"];
const UNAUTHENTICATED_PATHS = ["/login", "/register", "/invite", "/book-demo"];
Comment thread
adaam2 marked this conversation as resolved.

// Paths that are authenticated but don't require org/project slug context.
const SLUG_EXEMPT_PATHS = ["/slack/register"];
Expand Down Expand Up @@ -273,13 +274,12 @@ const AuthHandler = ({ children }: { children: React.ReactNode }) => {
// Backwards-compat: redirect old /:orgSlug/:projectSlug/... URLs to /:orgSlug/projects/:projectSlug/...
// If the second segment is a known project slug (and not "projects" or an org-level route),
// redirect to the new URL structure.
// Known org-level route paths that should not be treated as project slugs
// Derived from ORG_ROUTE_STRUCTURE so new org routes are automatically excluded from project slug redirects
const ORG_ROUTE_PATHS = [
"billing",
"api-keys",
"domains",
"logs",
"projects",
...Object.values(ORG_ROUTE_STRUCTURE)
.map((r) => r.url)
.filter(Boolean),
];
const isProjectSlug = session.organization?.projects.some(
(p) => p.slug === pathParts[1],
Expand Down
Loading
Loading