diff --git a/studio/src/components/auth/auth-components.tsx b/studio/src/components/auth/auth-components.tsx index 472c3b48dd..8e035fb653 100644 --- a/studio/src/components/auth/auth-components.tsx +++ b/studio/src/components/auth/auth-components.tsx @@ -11,6 +11,7 @@ import { MagnifyingGlassIcon, RocketLaunchIcon, } from "@heroicons/react/24/outline"; +import { getSignupContent, type SignupVariant } from "@/lib/signup-content"; /** * Auth Card - The card container for auth forms @@ -129,23 +130,37 @@ export const TrustedCompanies = () => { /** * Marketing Header - Title and description for the right side */ -export const MarketingHeader = () => { +export const MarketingHeader = ({ + title, + description, +}: { + title?: string; + description?: string; +}) => { + const defaultTitle = "Cosmo: Open-Source\nGraphQL Federation Solution"; + const defaultDescription = + "Unify distributed APIs into one federated graph. Platform teams get observability and control. Service teams ship independently."; + + const displayTitle = title || defaultTitle; + const displayDescription = description || defaultDescription; + return (

- Cosmo: Open-Source -
- GraphQL Federation Solution + {displayTitle.split("\n").map((line, index) => ( + + {line} + {index < displayTitle.split("\n").length - 1 &&
} +
+ ))}

-

- Unify distributed APIs into one federated graph. Platform teams get observability and control. Service teams ship independently. -

+

{displayDescription}

); }; /** - * Feature Item - Individual feature with icon box + * Feature Item - Individual feature with icon tile (glossy, border highlight) */ const FeatureItem = ({ icon, @@ -156,9 +171,25 @@ const FeatureItem = ({ title: string; description: string; }) => ( -
-
- {icon} +
+
+ {/* Glossy highlight (top edge) */} +
+ {/* Border highlight (static tilt) */} +
+ {icon}

{title}

@@ -170,60 +201,76 @@ const FeatureItem = ({ /** * Product Cosmo Stack - Marketing content with features list */ -export const ProductCosmoStack = ({ variant = "login" }: { variant?: "login" | "signup" }) => { +export const ProductCosmoStack = ({ + variant = "login", + signupVariant, +}: { + variant?: "login" | "signup"; + signupVariant?: "default" | "apollo"; +}) => { + // Icon mapping function (larger icons for feature tiles) + const getIcon = (iconName: string) => { + const iconClass = "h-8 w-8 text-purple-400"; + switch (iconName) { + case "bolt": + return ; + case "code-bracket": + return ; + case "shield-check": + return ; + case "share": + return ; + case "magnifying-glass": + return ; + case "rocket-launch": + return ; + default: + return ; + } + }; + const loginFeatures = [ { - icon: , + icon: , title: "Real time subscriptions without new infrastructure", description: "Cosmo Streams turns existing event streams into GraphQL subscriptions by handling authorization, filtering, and fan out in the Cosmo Router, keeping subgraphs stateless and avoiding a separate service.", }, { - icon: , + icon: , title: "Extend the router with TypeScript", description: "With TypeScript plugin support in Cosmo Connect, you can extend the Cosmo Router using TypeScript and run custom logic directly inside the router, without deploying separate services.", }, { - icon: , + icon: , title: "Enforce custom schema rules before deploy", description: "With Subgraph Check Extensions, you can run your own validation logic as part of Cosmo's subgraph checks, enforcing custom schema rules before changes are deployed.", }, ]; - const signupFeatures = [ - { - icon: , - title: "Federate Any API, Not Just GraphQL", - description: - "Connect REST, gRPC, and GraphQL services without rewrites. Cosmo Connect wraps existing APIs into your graph without forcing migrations.", - }, - { - icon: , - title: "Track Every Query Across Your Entire Graph", - description: - "Native OpenTelemetry tracing from gateway to subgraph. Find slow queries and failing services in seconds with zero instrumentation required.", - }, - { - icon: , - title: "Catch Breaking Changes Before Deployment", - description: - "Schema checks run automatically in CI/CD. Service teams ship on their own schedule, while platform teams prevent breaking changes from reaching production.", - }, - { - icon: , - title: "Built for Scale and Performance", - description: - "Go router with sub-millisecond overhead. Deploy with built-in caching, rate limiting, and security controls wherever your infrastructure lives.", - }, - ]; + // Signup content always from content map (single source of truth) + let marketingTitle: string | undefined; + let marketingDescription: string | undefined; + const signupContent = + variant === "signup" ? getSignupContent(signupVariant ?? "default") : null; + if (signupContent) { + marketingTitle = signupContent.marketingTitle; + marketingDescription = signupContent.marketingDescription; + } + const signupFeatures = + signupContent?.features.map((feature) => ({ + icon: getIcon(feature.icon), + title: feature.title, + description: feature.description, + })) ?? []; const features = variant === "login" ? loginFeatures : signupFeatures; return (
- +
{features.map((feature, index) => ( = { + default: { + heading: "Sign up for free", + description: "Try Cosmo as Managed Service. No card required.", + marketingTitle: "Cosmo: Open-Source\nGraphQL Federation Solution", + marketingDescription: + "Unify distributed APIs into one federated graph. Platform teams get observability and control. Service teams ship independently.", + features: [ + { + icon: "share", + title: "Federate Any API, Not Just GraphQL", + description: + "Connect REST, gRPC, and GraphQL services without rewrites. Cosmo Connect wraps existing APIs into your graph without forcing migrations.", + }, + { + icon: "magnifying-glass", + title: "Track Every Query Across Your Entire Graph", + description: + "Native OpenTelemetry tracing from gateway to subgraph. Find slow queries and failing services in seconds with zero instrumentation required.", + }, + { + icon: "shield-check", + title: "Catch Breaking Changes Before Deployment", + description: + "Schema checks run automatically in CI/CD. Service teams ship on their own schedule, while platform teams prevent breaking changes from reaching production.", + }, + { + icon: "rocket-launch", + title: "Built for Scale and Performance", + description: + "Go router with sub-millisecond overhead. Deploy with built-in caching, rate limiting, and security controls wherever your infrastructure lives.", + }, + ], + }, + apollo: { + heading: "Sign up for free", + description: "Try Cosmo managed and migrate from Apollo GraphOS in minutes. No credit card required.", + marketingTitle: "Migrate to Cosmo\nfrom Apollo GraphOS", + marketingDescription: + "Escape the vendor lock. 100% open source GraphQL Federation with full control. A drop-in GraphOS replacement.", + features: [ + { + icon: "code-bracket", + title: "Schema design and governance as it should be", + description: + "Get linting, breaking-change detection, schema contracts, and PR-based checks from day one. Cosmo doesn't gate governance behind tiers.", + }, + { + icon: "rocket-launch", + title: "Cosmo delivers value, not traffic bills", + description: + "Enjoy predictable, transparent pricing for what drives value in your organization, as well as world-class support.", + }, + { + icon: "share", + title: "Connect legacy services without a proprietary lock-in", + description: + "Wrap REST, gRPC, SOAP and other existing APIs into your supergraph without rewriting backends. No schema changes required.", + }, + { + icon: "bolt", + title: "Build real-time event-driven subscriptions that scale", + description: + "Turn Kafka, NATS, or Redis into GraphQL subscriptions. Subgraphs stay stateless. Scale to tens of thousands of clients effortlessly.", + }, + ], + }, +}; + +/** + * Get signup content based on the variant + * @param variant - The signup variant (from URL parameter) + * @returns The content configuration for the variant + */ +export const getSignupContent = (variant: SignupVariant = "default"): SignupContent => { + return signupContentMap[variant] || signupContentMap.default; +}; + +/** + * Parse the 'uc' (use case) parameter from URL query + * @param ucParam - The 'uc' query parameter value + * @returns The signup variant or 'default' if not recognized + */ +export const parseSignupVariant = (ucParam?: string): SignupVariant => { + if (ucParam?.toLowerCase() === "apollo") { + return "apollo"; + } + return "default"; +}; diff --git a/studio/src/pages/signup.tsx b/studio/src/pages/signup.tsx index 5389f840a3..1da0149885 100644 --- a/studio/src/pages/signup.tsx +++ b/studio/src/pages/signup.tsx @@ -13,11 +13,13 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { FaGoogle } from "react-icons/fa"; import { z } from "zod"; +import { getSignupContent, parseSignupVariant } from "@/lib/signup-content"; const signupUrl = `${process.env.NEXT_PUBLIC_COSMO_CP_URL}/v1/auth/signup`; const querySchema = z.object({ redirectURL: z.string().url().optional(), + uc: z.string().optional(), }); const constructSignupURL = ({ @@ -37,10 +39,20 @@ const constructSignupURL = ({ return signupUrl + (queryString.length ? "?" + queryString : ""); }; +function getUcFromUrl(): string | undefined { + if (typeof window === "undefined") return undefined; + return new URLSearchParams(window.location.search).get("uc") ?? undefined; +} + const SignupPage: NextPageWithLayout = () => { const router = useRouter(); - - const { redirectURL } = querySchema.parse(router.query); + // Parse query safely so invalid params (e.g. redirectURL from OAuth) don't crash the page + const parseResult = querySchema.safeParse(router.query); + const query = parseResult.success ? parseResult.data : { redirectURL: undefined, uc: undefined }; + const uc = router.isReady ? query.uc : getUcFromUrl() ?? query.uc; + const variant = parseSignupVariant(uc); + const content = getSignupContent(variant); + const redirectURL = query.redirectURL; return (
@@ -50,7 +62,7 @@ const SignupPage: NextPageWithLayout = () => { {/* Left section - Marketing */}
- +
@@ -64,10 +76,10 @@ const SignupPage: NextPageWithLayout = () => {

- Sign up for free + {content.heading}

- Try Cosmo as Managed Service. No card required. + {content.description}

diff --git a/studio/src/styles/globals.css b/studio/src/styles/globals.css index e777ad1272..88eb7f8d4e 100644 --- a/studio/src/styles/globals.css +++ b/studio/src/styles/globals.css @@ -165,6 +165,39 @@ } } +/* Static highlight: fixed tilt (no animation) */ +.feature-icon-tile-border-highlight { + width: 150%; + height: 150%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) rotate(22deg); + /* Two ellipses: one end anchored at center (50% 50%), opposite directions, reach past corners */ + background: + radial-gradient( + ellipse 47% 12% at 3% 50%, + transparent 0%, + rgba(255, 255, 255, 0.2) 28%, + rgba(255, 255, 255, 0.12) 50%, + transparent 72% + ), + radial-gradient( + ellipse 47% 12% at 97% 50%, + transparent 0%, + rgba(255, 255, 255, 0.2) 28%, + rgba(255, 255, 255, 0.12) 50%, + transparent 72% + ); +} + +.feature-icon-tile-border-highlight::after { + content: ""; + position: absolute; + inset: 1px; + border-radius: 7px; + background: rgba(0, 0, 0, 0.5); +} + pre[class*="language-"] code { display: block; }