Skip to content
Closed
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
3 changes: 3 additions & 0 deletions apps/apollo-vertex/app/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default {
"data-querying": "Data Querying",
localization: "Localization",
mcp: "MCP Server",
"guided-buying": {
display: "hidden",
},
auth_callback: {
display: "hidden",
},
Expand Down
4 changes: 4 additions & 0 deletions apps/apollo-vertex/app/experiment/_meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
index: { display: "hidden" },
"guided-buying": "Guided Buying",
};
18 changes: 18 additions & 0 deletions apps/apollo-vertex/app/experiment/guided-buying/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Guided Buying

An experimental prototype exploring a guided buying experience built on the
Apollo Vertex shell. The prototype opens in its own full-screen window with a
minimal shell and the following sections: **Dashboard**, **Buy**, **Catalog**,
and **Workbench**.

<a
href="/guided-buying"
target="_blank"
rel="noreferrer"
className="not-prose inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground no-underline transition-colors hover:bg-primary/90"
>
Open the prototype ↗
</a>

The prototype launches in a new tab so it can take over the full viewport
without the documentation chrome.
37 changes: 37 additions & 0 deletions apps/apollo-vertex/app/guided-buying/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use client";

import { useEffect } from "react";
import { createRoot } from "react-dom/client";
import { ThemeProvider } from "@/registry/shell/shell-theme-provider";
import { GuidedBuyingShell } from "@/templates/guided-buying/GuidedBuyingShell";

export default function GuidedBuyingPage() {
// The shell is a full-screen client app. Rendering it through Next's tree
// puts it inside Nextra's docs layout, whose interrupted hydration leaves the
// subtree stuck `display:none`. So we mount it in a *separate* React root
// attached to <body> — built by a fresh client render (no hydration), fully
// outside Nextra's tree and Suspense, so nothing can hide it. It carries its
// own ThemeProvider (normally supplied by the app's root ThemeWrapper).
useEffect(() => {
const container = document.createElement("div");
document.body.append(container);
const root = createRoot(container);
root.render(
<ThemeProvider storageKey="theme">
<div className="fixed inset-0 z-50 bg-background">
<GuidedBuyingShell />
</div>
</ThemeProvider>,
);
return () => {
root.unmount();
container.remove();
};
}, []);

return (
<div className="fixed inset-0 z-50 flex h-screen items-center justify-center bg-background text-sm text-muted-foreground">
Loading prototype…
</div>
);
}
5 changes: 5 additions & 0 deletions apps/apollo-vertex/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"autopilot_empty_description": "Ask me anything about your automation data.",
"bad_response": "Bad response",
"business_user": "Business user",
"buy": "Buy",
"cancel": "Cancel",
"card_component": "Card Component",
"catalog": "Catalog",
"changes_apply_instantly": "Changes apply instantly",
Comment on lines +19 to 23
"chart_render_failed_title": "Couldn't render this chart",
"chat_messages": "Chat messages",
Expand Down Expand Up @@ -108,6 +110,7 @@
"projects": "Projects",
"remove_file": "Remove {{name}}",
"remove_quoted_text": "Remove quoted text",
"requests": "Requests",
"reset_to_default": "Reset to Default",
"retry": "Retry",
"romanian": "Romanian",
Expand Down Expand Up @@ -139,6 +142,7 @@
"start_conversation_with": "Start a conversation with {{name}}",
"stop": "Stop",
"success": "Success",
"switch_user": "Switch user",
"system": "System",
"table_no_valid_fields": "None of the requested fields exist on {{entity}}: {{fields}}.",
"team": "Team",
Expand Down Expand Up @@ -166,6 +170,7 @@
"view_customer": "View customer",
"view_payment_details": "View payment details",
"warning": "Warning",
"workbench": "Workbench",
"wrong_dimension_type_in_entity": "Field \"{{field}}\" on {{entity}} is {{actual}}; this chart needs a {{expected}} field.",
"wrong_dimension_type_in_joined": "Field \"{{field}}\" is {{actual}}; this chart needs a {{expected}} field.",
"wrong_metric_type_in_entity": "Field \"{{field}}\" on {{entity}} is {{actual}}; {{aggregation}} requires a numeric field.",
Expand Down
2 changes: 1 addition & 1 deletion apps/apollo-vertex/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ export function AiChatMessageActions({
<div
className={cn(
"flex items-center gap-0.5 -ml-[7px] transition-opacity",
isLatest
? "opacity-100"
: "opacity-0 group-hover/message:opacity-100 group-focus-within/message:opacity-100 has-[[data-state=delayed-open]]:opacity-100 has-[[data-state=instant-open]]:opacity-100",
// Hover-only (also revealed on keyboard focus / open tooltip) to cut
// persistent noise — including on the latest message.
"opacity-0 group-hover/message:opacity-100 group-focus-within/message:opacity-100 has-[[data-state=delayed-open]]:opacity-100 has-[[data-state=instant-open]]:opacity-100",
)}
>
{content.trim() && <CopyToClipboardButton value={content} />}
Expand Down
14 changes: 4 additions & 10 deletions apps/apollo-vertex/registry/ai-chat/components/ai-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export function AiChat<
{messages.length === 0 ? (
<div className="flex-1 flex flex-col items-center justify-center min-h-0">
<div className="w-full">
<div className="px-4 text-center mb-7">
<div className="px-4 text-center mb-5">
{emptyState ?? defaultEmptyState}
</div>
<AiChatInput {...sharedInputProps} hasMessages={false} />
Expand All @@ -262,22 +262,16 @@ export function AiChat<
</div>
) : (
<div className="relative flex-1 min-h-0">
<div
className="absolute top-0 left-0 right-0 h-6 z-10 pointer-events-none"
style={{
background:
"linear-gradient(to bottom, var(--background) 0%, transparent 100%)",
}}
aria-hidden="true"
/>
<div
ref={attachScrollListeners}
role="log"
aria-label={t("chat_messages")}
aria-live="polite"
aria-atomic="false"
aria-busy={isInFlight}
className="relative h-full overflow-y-auto py-4 pl-10 pr-10"
// Fade content out at the top edge via an alpha mask (works over any
// background, unlike a solid-color gradient overlay).
className="relative h-full overflow-y-auto py-4 pl-10 pr-10 [scrollbar-gutter:stable] [mask-image:linear-gradient(to_bottom,transparent_0,#000_24px)] [-webkit-mask-image:linear-gradient(to_bottom,transparent_0,#000_24px)]"
>
{enableTextSelection && (
<AiChatSelectionMenu
Expand Down
2 changes: 1 addition & 1 deletion apps/apollo-vertex/registry/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function Card({
className={cn(
"absolute inset-0 rounded-2xl pointer-events-none blur-xl",
"transition-opacity duration-150",
selected ? "opacity-100" : "opacity-0",
selected ? "opacity-50" : "opacity-0",
)}
style={{ background: "var(--ai-gradient)" }}
/>
Expand Down
4 changes: 4 additions & 0 deletions apps/apollo-vertex/registry/shell/shell-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface ShellLayoutProps {
variant?: "minimal";
companyLogo?: CompanyLogo;
navItems: ShellNavItem[];
onUserClick?: () => void;
}

function DarkGradientBackground() {
Expand Down Expand Up @@ -177,6 +178,7 @@ export function ShellLayout({
variant,
companyLogo,
navItems,
onUserClick,
}: PropsWithChildren<ShellLayoutProps>) {
if (variant === "minimal") {
return (
Expand All @@ -189,6 +191,7 @@ export function ShellLayout({
productName={productName}
companyLogo={companyLogo}
navItems={navItems}
onUserClick={onUserClick}
/>
<div className="flex-1 overflow-y-auto custom-scrollbar">
{children}
Expand All @@ -210,6 +213,7 @@ export function ShellLayout({
productName={productName}
companyLogo={companyLogo}
navItems={navItems}
onUserClick={onUserClick}
/>
<SidebarInset className="relative flex-1 flex flex-col overflow-hidden rounded-none m-0 ml-0 shadow-none bg-transparent">
<header className="flex items-center h-12 px-4 md:hidden">
Expand Down
8 changes: 7 additions & 1 deletion apps/apollo-vertex/registry/shell/shell-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface ShellSidebarProps {
variant?: "minimal";
companyLogo?: CompanyLogo;
navItems: ShellNavItem[];
onUserClick?: () => void;
}

export const ShellSidebar = ({
Expand All @@ -55,6 +56,7 @@ export const ShellSidebar = ({
variant,
companyLogo,
navItems,
onUserClick,
}: ShellSidebarProps) => {
if (variant === "minimal") {
return (
Expand Down Expand Up @@ -82,6 +84,7 @@ export const ShellSidebar = ({
isCollapsed
collapsedMenuSide="bottom"
collapsedMenuAlign="end"
onUserClick={onUserClick}
/>
</div>
</header>
Expand All @@ -94,6 +97,7 @@ export const ShellSidebar = ({
productName={productName}
companyLogo={companyLogo}
navItems={navItems}
onUserClick={onUserClick}
/>
);
};
Expand All @@ -103,13 +107,15 @@ interface SidebarNavProps {
productName: string;
companyLogo?: CompanyLogo;
navItems: ShellNavItem[];
onUserClick?: () => void;
}

function SidebarNav({
companyName,
productName,
companyLogo,
navItems,
onUserClick,
}: SidebarNavProps) {
const { t } = useTranslation();
const { state, toggleSidebar } = useSidebar();
Expand Down Expand Up @@ -338,7 +344,7 @@ function SidebarNav({
</SidebarContent>

<SidebarFooter className="p-4 pt-0">
<UserProfile isCollapsed={isCollapsed} />
<UserProfile isCollapsed={isCollapsed} onUserClick={onUserClick} />
</SidebarFooter>

<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Globe, LogOut, Monitor, Moon, Sun } from "lucide-react";
import {
ArrowLeftRight,
Globe,
LogOut,
Monitor,
Moon,
Sun,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import {
DropdownMenuItem,
Expand All @@ -15,7 +22,14 @@ import type { LanguageChangedEvent } from "./shell-constants";
import { Text } from "./shell-text";
import { useTheme } from "./shell-theme-provider";

export const UserProfileMenuItems = () => {
interface UserProfileMenuItemsProps {
/** When set, a "Switch user" item appears at the top (e.g. demo seats). */
onSwitchUser?: () => void;
}

export const UserProfileMenuItems = ({
onSwitchUser,
}: UserProfileMenuItemsProps) => {
const { t, i18n } = useTranslation();
const { logout } = useAuth();
const { setTheme } = useTheme();
Expand All @@ -31,6 +45,15 @@ export const UserProfileMenuItems = () => {

return (
<>
{onSwitchUser && (
<>
<DropdownMenuItem onClick={onSwitchUser}>
<ArrowLeftRight className="w-4 h-4" />
<span>{t("switch_user")}</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Sun className="w-4 h-4 dark:hidden" />
Expand Down
7 changes: 5 additions & 2 deletions apps/apollo-vertex/registry/shell/shell-user-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ interface UserProfileProps {
isCollapsed: boolean;
collapsedMenuSide?: "top" | "right" | "bottom" | "left";
collapsedMenuAlign?: "start" | "center" | "end";
/** When set, a "Switch user" item appears in the menu (e.g. demo seats). */
onUserClick?: () => void;
}

export const UserProfile = ({
isCollapsed,
collapsedMenuSide = "top",
collapsedMenuAlign = "start",
onUserClick,
}: UserProfileProps) => {
const { t } = useTranslation();
const { user } = useUser();
Expand Down Expand Up @@ -73,7 +76,7 @@ export const UserProfile = ({
</div>
</div>
<DropdownMenuSeparator />
<UserProfileMenuItems />
<UserProfileMenuItems onSwitchUser={onUserClick} />
</DropdownMenuContent>
</DropdownMenu>
) : (
Expand Down Expand Up @@ -110,7 +113,7 @@ export const UserProfile = ({
side="top"
sideOffset={8}
>
<UserProfileMenuItems />
<UserProfileMenuItems onSwitchUser={onUserClick} />
</DropdownMenuContent>
</DropdownMenu>
)}
Expand Down
15 changes: 13 additions & 2 deletions apps/apollo-vertex/registry/shell/shell-user-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export const useUser = () => {

export const UserContext = createContext<UserContextValue | null>(null);

export const ShellUserProvider: FC<PropsWithChildren> = ({ children }) => {
export const ShellUserProvider: FC<
PropsWithChildren<{ userOverride?: User | null }>
> = ({ children, userOverride }) => {
const { user: authUser, isAuthenticated, isLoading } = useAuth();
const [user, setUser] = useState<User | null>(null);

Expand All @@ -52,8 +54,17 @@ export const ShellUserProvider: FC<PropsWithChildren> = ({ children }) => {
}
}, [authUser]);

// A caller-supplied identity (e.g. a demo "seat") wins over the auth user.
const effectiveUser = userOverride ?? user;

return (
<UserContext.Provider value={{ user, isAuthenticated, isLoading }}>
<UserContext.Provider
value={{
user: effectiveUser,
isAuthenticated: userOverride != null || isAuthenticated,
isLoading,
}}
>
{children}
</UserContext.Provider>
);
Expand Down
Loading
Loading