mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
[Docs] - Full cleanup (#1105)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Removed Features** * Dashboard embed functionality has been removed from the platform. * Python template documentation and guides have been removed. * **Documentation** * Documentation structure has been significantly reorganized and simplified. * Extensive template content and component guides have been removed. * Apple OAuth integration guide updated with streamlined secret generation flow. * **Refactor** * Button component styling and variant system updated. * Routing configuration updated with simplified path handling. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
import Footer from '@/components/homepage/homepage-footer';
|
||||
import { HomeLayout } from '@/components/layouts/home-layout';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<HomeLayout>
|
||||
{children}
|
||||
<Footer />
|
||||
</HomeLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// This page is never reached because of the redirect in next.config.mjs
|
||||
// Kept as a fallback in case the redirect configuration is removed
|
||||
export default function HomePage() {
|
||||
return null;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { getEmbeddedMDXComponents } from '@/mdx-components';
|
||||
import { dashboardSource } from 'lib/source';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function DashboardEmbedPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug?: string[] }>,
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const page = dashboardSource.getPage(slug ?? []);
|
||||
|
||||
if (!page) redirect("/");
|
||||
|
||||
const MDX = page.data.body;
|
||||
|
||||
return (
|
||||
<div className="p-6 prose prose-neutral dark:prose-invert max-w-none overflow-x-hidden">
|
||||
<div className="w-full">
|
||||
<MDX components={getEmbeddedMDXComponents()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { EmbeddedLinkInterceptor } from '@/components/embedded-link-interceptor';
|
||||
|
||||
// Embedded layout for dashboard docs - no navbar, optimized for iframe
|
||||
export default function DashboardEmbedLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-fd-background">
|
||||
<EmbeddedLinkInterceptor />
|
||||
{/* Main content area - no header, no padding, prevent horizontal overflow */}
|
||||
<main className="h-screen overflow-hidden">
|
||||
<div className="h-full overflow-y-auto overflow-x-hidden scrollbar-hide">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import { stringCompare } from '@stackframe/stack-shared/dist/utils/strings';
|
||||
import { AlertTriangle, ChevronDown, Key, X } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSidebar } from '../layouts/sidebar-context';
|
||||
import { Button } from '../mdx/button';
|
||||
import { useAPIPageContext } from './api-page-wrapper';
|
||||
import { Button } from './button';
|
||||
|
||||
type StackAuthHeaderKey =
|
||||
| 'X-Stack-Access-Type'
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { cn } from '../../lib/cn';
|
||||
|
||||
type ButtonProps = {
|
||||
variant?: 'default' | 'outline' | 'ghost' | 'secondary',
|
||||
size?: 'default' | 'sm' | 'lg' | 'icon',
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>
|
||||
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = 'default', size = 'default', ...props }, ref) => {
|
||||
const baseClasses = 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fd-primary disabled:pointer-events-none disabled:opacity-50';
|
||||
|
||||
const variants = {
|
||||
default: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/90',
|
||||
outline: 'border border-fd-border bg-fd-background hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
secondary: 'bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-secondary/80',
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
default: 'h-9 px-4 py-2 text-sm',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
baseClasses,
|
||||
variants[variant],
|
||||
sizes[size],
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export { Button };
|
||||
export type { ButtonProps };
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import type { OpenAPIOperation, OpenAPIParameter, OpenAPISchema, OpenAPISpec } from '../../lib/openapi-types';
|
||||
import { resolveSchema } from '../../lib/openapi-utils';
|
||||
import { useAPIPageContext } from './api-page-wrapper';
|
||||
import { Button } from './button';
|
||||
import { Button } from '../mdx/button';
|
||||
|
||||
type EnhancedAPIPageProps = {
|
||||
document: string,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { ArrowRight, Check, Code, Copy, Sparkles, Webhook } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { codeToHtml } from 'shiki';
|
||||
import { Button } from './button';
|
||||
import { Button } from '../mdx/button';
|
||||
|
||||
// Types for OpenAPI specification (focused on webhooks)
|
||||
type OpenAPISchema = {
|
||||
|
||||
@@ -24,8 +24,7 @@ const getEmbeddedUrl = (href: string, currentPath: string): string => {
|
||||
// Already an embedded URL?
|
||||
if (
|
||||
cleanPath.startsWith('/docs-embed') ||
|
||||
cleanPath.startsWith('/api-embed') ||
|
||||
cleanPath.startsWith('/dashboard-embed')
|
||||
cleanPath.startsWith('/api-embed')
|
||||
) {
|
||||
return withSuffix(cleanPath);
|
||||
}
|
||||
@@ -37,9 +36,6 @@ const getEmbeddedUrl = (href: string, currentPath: string): string => {
|
||||
if (cleanPath === '/api' || cleanPath.startsWith('/api/')) {
|
||||
return withSuffix(cleanPath.replace(/^\/api(?=\/|$)/, '/api-embed'));
|
||||
}
|
||||
if (cleanPath === '/dashboard' || cleanPath.startsWith('/dashboard/')) {
|
||||
return withSuffix(cleanPath.replace(/^\/dashboard(?=\/|$)/, '/dashboard-embed'));
|
||||
}
|
||||
|
||||
// Relative paths -> resolve against current embedded section (if present)
|
||||
if (!cleanPath.startsWith('/') && !cleanPath.startsWith('#')) {
|
||||
@@ -125,7 +121,6 @@ export function EmbeddedLinkInterceptor() {
|
||||
if (
|
||||
href.startsWith('/docs/') ||
|
||||
href.startsWith('/api/') ||
|
||||
href.startsWith('/dashboard/') ||
|
||||
(!/^[a-zA-Z][a-zA-Z+\-.]*:/.test(href) && !href.startsWith('#'))
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Footer() {
|
||||
const footerLinks = [
|
||||
{
|
||||
title: "Product",
|
||||
links: [
|
||||
{ name: "Website", href: "https://stack-auth.com" },
|
||||
{ name: "Dashboard", href: "https://app.stack-auth.com/projects" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Community",
|
||||
links: [
|
||||
{ name: "GitHub", href: "https://github.com/stack-auth/stack-auth" },
|
||||
{ name: "Discord", href: "https://discord.stack-auth.com/" }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Company",
|
||||
links: [
|
||||
{ name: "Careers", href: "https://jobs.gem.com/stack-auth-com" }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<footer className="border-t border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="max-w-7xl mx-auto px-6 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 lg:gap-12">
|
||||
{/* Logo and Description */}
|
||||
<div className="md:col-span-1">
|
||||
<div className="flex items-center mb-4">
|
||||
<svg
|
||||
width="32"
|
||||
height="26"
|
||||
viewBox="0 0 200 242"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="mr-3"
|
||||
>
|
||||
<path d="M103.504 1.81227C101.251 0.68679 98.6002 0.687576 96.3483 1.81439L4.4201 47.8136C1.71103 49.1692 0 51.9387 0 54.968V130.55C0 133.581 1.7123 136.351 4.42292 137.706L96.4204 183.695C98.6725 184.82 101.323 184.82 103.575 183.694L168.422 151.271C173.742 148.611 180 152.479 180 158.426V168.879C180 171.91 178.288 174.68 175.578 176.035L103.577 212.036C101.325 213.162 98.6745 213.162 96.4224 212.036L11.5771 169.623C6.25791 166.964 0 170.832 0 176.779V187.073C0 190.107 1.71689 192.881 4.43309 194.234L96.5051 240.096C98.7529 241.216 101.396 241.215 103.643 240.094L195.571 194.235C198.285 192.881 200 190.109 200 187.076V119.512C200 113.565 193.741 109.697 188.422 112.356L131.578 140.778C126.258 143.438 120 139.57 120 133.623V123.17C120 120.14 121.712 117.37 124.422 116.014L195.578 80.4368C198.288 79.0817 200 76.3116 200 73.2814V54.9713C200 51.9402 198.287 49.1695 195.576 47.8148L103.504 1.81227Z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span className="text-xl font-bold">Stack Auth</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed mb-6">
|
||||
Complete authentication solution for modern applications. Secure, scalable, and developer-friendly.
|
||||
</p>
|
||||
<div className="flex space-x-1">
|
||||
{['rgb(59, 130, 246)', 'rgb(16, 185, 129)', 'rgb(245, 158, 11)', 'rgb(168, 85, 247)'].map((color, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-3 h-3 rounded-full opacity-60"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Links */}
|
||||
{footerLinks.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h3 className="font-semibold text-foreground mb-4 text-sm uppercase tracking-wide">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{section.links.map((link) => (
|
||||
<li key={link.name}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors duration-200"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{link.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Book, Code, Command, Layers, Search, Zap } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
type DocsSection = {
|
||||
id: string,
|
||||
title: string,
|
||||
description: string,
|
||||
icon: React.ReactNode,
|
||||
url: string,
|
||||
color: string,
|
||||
}
|
||||
|
||||
type DocsIcon3DProps = {
|
||||
sections?: DocsSection[],
|
||||
onSectionSelect?: (section: DocsSection) => void,
|
||||
}
|
||||
const DEFAULT_SECTIONS: DocsSection[] = [
|
||||
{
|
||||
id: "guides",
|
||||
title: "Guides",
|
||||
description: "Complete guides and tutorials",
|
||||
icon: <Book size={24} />,
|
||||
url: "/docs/overview",
|
||||
color: "rgb(59, 130, 246)",
|
||||
},
|
||||
{
|
||||
id: "sdk",
|
||||
title: "SDK",
|
||||
description: "Software development kits",
|
||||
icon: <Code size={24} />,
|
||||
url: "/docs/sdk",
|
||||
color: "rgb(16, 185, 129)",
|
||||
},
|
||||
{
|
||||
id: "components",
|
||||
title: "Components",
|
||||
description: "Reusable UI components",
|
||||
icon: <Layers size={24} />,
|
||||
url: "/docs/components",
|
||||
color: "rgb(245, 101, 101)",
|
||||
},
|
||||
{
|
||||
id: "api",
|
||||
title: "API Reference",
|
||||
description: "Complete API documentation",
|
||||
icon: <Zap size={24} />,
|
||||
url: "/api/overview",
|
||||
color: "rgb(168, 85, 247)",
|
||||
},
|
||||
];
|
||||
|
||||
const DocsIcon3D: React.FC<DocsIcon3DProps> = ({
|
||||
sections = DEFAULT_SECTIONS,
|
||||
onSectionSelect,
|
||||
}) => {
|
||||
const [hoveredSection, setHoveredSection] = useState<string | null>(null);
|
||||
|
||||
const handleSectionClick = (section: DocsSection) => {
|
||||
if (onSectionSelect) {
|
||||
onSectionSelect(section);
|
||||
} else {
|
||||
window.location.href = section.url;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to convert rgb to rgba
|
||||
const rgbToRgba = (rgb: string, alpha: number) => {
|
||||
const match = rgb.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
|
||||
if (match) {
|
||||
return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${alpha})`;
|
||||
}
|
||||
return rgb;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center items-center p-4">
|
||||
<div
|
||||
className={`
|
||||
grid gap-6 max-w-4xl w-full justify-center
|
||||
${sections.length === 1 ? 'grid-cols-1 max-w-xs' : ''}
|
||||
${sections.length === 2 ? 'grid-cols-1 md:grid-cols-2 max-w-lg' : ''}
|
||||
${sections.length === 3 ? 'grid-cols-1 md:grid-cols-3 max-w-2xl' : ''}
|
||||
${sections.length >= 4 ? 'grid-cols-2 md:grid-cols-4' : ''}
|
||||
`}
|
||||
>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.id}
|
||||
className="cursor-pointer group transition-transform hover:scale-105"
|
||||
onMouseEnter={() => setHoveredSection(section.id)}
|
||||
onMouseLeave={() => setHoveredSection(null)}
|
||||
onClick={() => handleSectionClick(section)}
|
||||
>
|
||||
<div
|
||||
className="bg-card border border-border rounded-xl p-4 w-full h-40 flex flex-col items-center justify-center shadow-sm hover:shadow-lg transition-all overflow-hidden"
|
||||
style={{
|
||||
borderColor: hoveredSection === section.id ? section.color : rgbToRgba(section.color, 0.4),
|
||||
}}
|
||||
>
|
||||
{/* Icon Container */}
|
||||
<div className="mb-3 flex-shrink-0">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center transition-all"
|
||||
style={{
|
||||
backgroundColor: hoveredSection === section.id ? section.color : rgbToRgba(section.color, 0.2),
|
||||
color: hoveredSection === section.id ? 'white' : section.color,
|
||||
transform: hoveredSection === section.id ? 'scale(1.1)' : 'scale(1)',
|
||||
}}
|
||||
>
|
||||
{React.cloneElement(section.icon as React.ReactElement, {
|
||||
size: 18,
|
||||
strokeWidth: hoveredSection === section.id ? 2.5 : 2,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3
|
||||
className="text-sm font-semibold mb-1.5 text-center transition-transform line-clamp-1"
|
||||
style={{
|
||||
color: section.color,
|
||||
transform: hoveredSection === section.id ? 'scale(1.05)' : 'scale(1)',
|
||||
}}
|
||||
>
|
||||
{section.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-xs text-center text-muted-foreground leading-snug px-1 opacity-80 line-clamp-2">
|
||||
{section.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DocsSelector() {
|
||||
const handleSectionSelect = (section: DocsSection) => {
|
||||
//console.log("Selected section:", section);
|
||||
// Navigate to the selected section
|
||||
if (section.url) {
|
||||
window.location.href = section.url;
|
||||
}
|
||||
};
|
||||
|
||||
// Simple search button that opens the shared search dialog
|
||||
const handleSearchOpen = () => {
|
||||
// Trigger the main search dialog by dispatching the Cmd+K event
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key: 'k',
|
||||
metaKey: true,
|
||||
bubbles: true
|
||||
});
|
||||
document.dispatchEvent(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto">
|
||||
{/* Search Bar - uses shared search dialog */}
|
||||
<div className="mb-8 flex justify-center">
|
||||
<div className="w-full max-w-md">
|
||||
<button
|
||||
onClick={handleSearchOpen}
|
||||
className="group flex w-full items-center gap-4 rounded-xl border border-fd-border/60 bg-fd-background/80 px-4 py-4 text-left text-sm text-fd-muted-foreground backdrop-blur-sm hover:border-fd-border hover:bg-fd-background hover:text-fd-foreground hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-fd-primary/20"
|
||||
>
|
||||
<Search className="h-5 w-5 flex-shrink-0" />
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">Search documentation</div>
|
||||
<div className="text-xs text-fd-muted-foreground/70">Find guides, API references, and examples</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<kbd className="inline-flex h-7 min-w-[28px] items-center justify-center rounded-md border border-fd-border/60 bg-fd-muted/50 px-2 font-mono text-xs font-medium text-fd-muted-foreground/80 group-hover:border-fd-border group-hover:bg-fd-muted group-hover:text-fd-muted-foreground">
|
||||
<Command className="h-4 w-4" />
|
||||
</kbd>
|
||||
<kbd className="inline-flex h-7 w-7 items-center justify-center rounded-md border border-fd-border/60 bg-fd-muted/50 font-mono text-xs font-medium text-fd-muted-foreground/80 group-hover:border-fd-border group-hover:bg-fd-muted group-hover:text-fd-muted-foreground">
|
||||
K
|
||||
</kbd>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DocsIcon3D
|
||||
sections={DEFAULT_SECTIONS}
|
||||
onSectionSelect={handleSectionSelect}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Export the core component for direct use
|
||||
export { DocsIcon3D };
|
||||
@@ -1,77 +0,0 @@
|
||||
'use client';
|
||||
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
||||
import { useSearchContext } from 'fumadocs-ui/contexts/search';
|
||||
import { Search } from 'lucide-react';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { cn } from '../../lib/cn';
|
||||
import { type ButtonProps, buttonVariants } from '../ui/button';
|
||||
|
||||
type SearchToggleProps = {
|
||||
hideIfDisabled?: boolean,
|
||||
} & Omit<ComponentProps<'button'>, 'color'> & ButtonProps
|
||||
|
||||
export function SearchToggle({
|
||||
hideIfDisabled,
|
||||
size = 'icon',
|
||||
color = 'ghost',
|
||||
...props
|
||||
}: SearchToggleProps) {
|
||||
const { setOpenSearch, enabled } = useSearchContext();
|
||||
if (hideIfDisabled && !enabled) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
size,
|
||||
color,
|
||||
}),
|
||||
props.className,
|
||||
)}
|
||||
data-search=""
|
||||
aria-label="Open Search"
|
||||
onClick={() => {
|
||||
setOpenSearch(true);
|
||||
}}
|
||||
>
|
||||
<Search className="p-px" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function LargeSearchToggle({
|
||||
hideIfDisabled,
|
||||
...props
|
||||
}: ComponentProps<'button'> & {
|
||||
hideIfDisabled?: boolean,
|
||||
}) {
|
||||
const { enabled, hotKey, setOpenSearch } = useSearchContext();
|
||||
const { text } = useI18n();
|
||||
if (hideIfDisabled && !enabled) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-search-full=""
|
||||
{...props}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2 rounded-lg border bg-fd-secondary/50 p-1.5 ps-2 text-sm text-fd-muted-foreground transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
props.className,
|
||||
)}
|
||||
onClick={() => {
|
||||
setOpenSearch(true);
|
||||
}}
|
||||
>
|
||||
<Search className="size-4" />
|
||||
{text.search}
|
||||
<div className="ms-auto inline-flex gap-0.5">
|
||||
{hotKey.map((k, i) => (
|
||||
<kbd key={i} className="rounded-md border bg-fd-background px-1.5">
|
||||
{k.display}
|
||||
</kbd>
|
||||
))}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { UserButton } from '@stackframe/stack';
|
||||
import { Github, Menu, Sparkles, X } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState, type ReactNode } from 'react';
|
||||
import { AIChatDrawer } from '../chat/ai-chat';
|
||||
import { CustomSearchDialog } from '../layout/custom-search-dialog';
|
||||
import { SearchInputToggle } from '../layout/custom-search-toggle';
|
||||
import { ThemeToggle } from '../layout/theme-toggle';
|
||||
import { SidebarProvider, useSidebar } from './sidebar-context';
|
||||
|
||||
// Discord Icon Component
|
||||
function DiscordIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Stack Auth Logo Component
|
||||
function StackAuthLogo() {
|
||||
return (
|
||||
<Link href="https://stack-auth.com" className="flex items-center gap-2.5 text-fd-foreground hover:text-fd-foreground/80 transition-colors">
|
||||
<svg
|
||||
width="30"
|
||||
height="24"
|
||||
viewBox="0 0 200 242"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Stack Logo"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
<path d="M103.504 1.81227C101.251 0.68679 98.6002 0.687576 96.3483 1.81439L4.4201 47.8136C1.71103 49.1692 0 51.9387 0 54.968V130.55C0 133.581 1.7123 136.351 4.42292 137.706L96.4204 183.695C98.6725 184.82 101.323 184.82 103.575 183.694L168.422 151.271C173.742 148.611 180 152.479 180 158.426V168.879C180 171.91 178.288 174.68 175.578 176.035L103.577 212.036C101.325 213.162 98.6745 213.162 96.4224 212.036L11.5771 169.623C6.25791 166.964 0 170.832 0 176.779V187.073C0 190.107 1.71689 192.881 4.43309 194.234L96.5051 240.096C98.7529 241.216 101.396 241.215 103.643 240.094L195.571 194.235C198.285 192.881 200 190.109 200 187.076V119.512C200 113.565 193.741 109.697 188.422 112.356L131.578 140.778C126.258 143.438 120 139.57 120 133.623V123.17C120 120.14 121.712 117.37 124.422 116.014L195.578 80.4368C198.288 79.0817 200 76.3116 200 73.2814V54.9713C200 51.9402 198.287 49.1695 195.576 47.8148L103.504 1.81227Z" fill="currentColor"/>
|
||||
</svg>
|
||||
<span className="font-medium text-[15px]">Stack Auth</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// AI Chat Toggle Button for Home Layout
|
||||
function HomeAIChatToggleButton({ compact = false }: { compact?: boolean }) {
|
||||
const sidebarContext = useSidebar();
|
||||
const { isChatOpen, toggleChat } = sidebarContext || {
|
||||
isChatOpen: false,
|
||||
toggleChat: () => {},
|
||||
};
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<button
|
||||
onClick={toggleChat}
|
||||
className="flex items-center justify-center transition-all duration-500 ease-out w-8 h-8 rounded-lg text-sm font-medium relative overflow-hidden text-white chat-gradient-active hover:scale-105 hover:brightness-110 hover:shadow-lg"
|
||||
title={isChatOpen ? 'Close AI chat' : 'Open AI chat'}
|
||||
>
|
||||
<Sparkles className="h-4 w-4 relative z-10" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleChat}
|
||||
className="flex items-center gap-2 transition-all duration-500 ease-out px-2 py-1 rounded-lg text-xs font-medium relative overflow-hidden text-white chat-gradient-active hover:scale-105 hover:brightness-110 hover:shadow-lg"
|
||||
title={isChatOpen ? 'Close AI chat' : 'Open AI chat'}
|
||||
>
|
||||
<Sparkles className="h-3 w-3 relative z-10" />
|
||||
<span className="font-medium relative z-10">AI Chat</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Home Navbar Component
|
||||
function HomeNavbar() {
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const sidebarContext = useSidebar();
|
||||
const { isChatOpen, isChatExpanded } = sidebarContext || {
|
||||
isChatOpen: false,
|
||||
isChatExpanded: false,
|
||||
};
|
||||
|
||||
// Scroll detection
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollY = window.scrollY;
|
||||
setIsScrolled(scrollY > 50);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
// Close mobile menu when scrolling
|
||||
useEffect(() => {
|
||||
if (isScrolled && mobileMenuOpen) {
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
}, [isScrolled, mobileMenuOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Full Navbar */}
|
||||
<header className={`sticky top-0 z-50 w-full border-b border-fd-border bg-fd-background/95 backdrop-blur supports-[backdrop-filter]:bg-fd-background/60 transition-all duration-300 ${isScrolled ? 'opacity-0 pointer-events-none' : 'opacity-100'} ${(isChatOpen || isChatExpanded) ? 'fixed left-0 right-0 z-[80]' : ''}`}>
|
||||
<div className="flex h-14 items-center justify-between px-4 md:px-6 container max-w-screen-2xl">
|
||||
{/* Left - Logo + Social Links */}
|
||||
<div className="flex items-center gap-4">
|
||||
<StackAuthLogo />
|
||||
|
||||
{/* Desktop Social Links */}
|
||||
<div className="hidden md:flex items-center gap-1">
|
||||
<Link
|
||||
href="https://github.com/stack-auth/stack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
title="GitHub"
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="https://discord.stack-auth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-lg text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
title="Discord"
|
||||
>
|
||||
<DiscordIcon className="h-4 w-4" />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right - Search + Actions */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Desktop Search */}
|
||||
<div className="hidden md:block w-64">
|
||||
<SearchInputToggle
|
||||
onOpen={() => setSearchOpen(true)}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* AI Chat Toggle */}
|
||||
<HomeAIChatToggleButton />
|
||||
|
||||
{/* Theme Toggle */}
|
||||
<ThemeToggle className="p-0" />
|
||||
|
||||
{/* User Sign in */}
|
||||
<UserButton />
|
||||
|
||||
{/* Mobile Search */}
|
||||
<div className="md:hidden">
|
||||
<SearchInputToggle
|
||||
onOpen={() => setSearchOpen(true)}
|
||||
className="w-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Toggle */}
|
||||
<button
|
||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||
className="md:hidden inline-flex h-9 w-9 items-center justify-center rounded-lg text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{mobileMenuOpen ? <X className="h-4 w-4" /> : <Menu className="h-4 w-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileMenuOpen && (
|
||||
<div className="md:hidden border-t border-fd-border bg-fd-background">
|
||||
<div className="container px-4 py-4 space-y-3">
|
||||
<Link
|
||||
href="https://github.com/stack-auth/stack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg text-fd-muted-foreground hover:bg-fd-muted hover:text-fd-foreground transition-colors"
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
<span>GitHub</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://discord.stack-auth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
className="flex items-center gap-3 px-3 py-2 rounded-lg text-fd-muted-foreground hover:bg-fd-muted hover:text-fd-foreground transition-colors"
|
||||
>
|
||||
<DiscordIcon className="h-4 w-4" />
|
||||
<span>Discord</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Compact Pill Navbar */}
|
||||
<div className={`fixed top-4 left-1/2 -translate-x-1/2 transition-all duration-300 ${isScrolled ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4 pointer-events-none'} ${isChatExpanded ? 'z-[80]' : 'z-50'}`}>
|
||||
<div className="flex items-center gap-3 px-4 py-2 bg-fd-background/95 backdrop-blur border border-fd-border rounded-full shadow-lg supports-[backdrop-filter]:bg-fd-background/80">
|
||||
{/* Left Side - Social Links and Search */}
|
||||
<div className="flex items-center gap-1">
|
||||
<Link
|
||||
href="https://github.com/stack-auth/stack"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
title="GitHub"
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="https://discord.stack-auth.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
title="Discord"
|
||||
>
|
||||
<DiscordIcon className="h-4 w-4" />
|
||||
</Link>
|
||||
|
||||
{/* Compact Search */}
|
||||
<button
|
||||
onClick={() => setSearchOpen(true)}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded-full text-fd-muted-foreground transition-colors hover:bg-fd-muted hover:text-fd-foreground"
|
||||
title="Search (⌘K)"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Center - Much Larger Logo */}
|
||||
<Link href="https://stack-auth.com" className="flex items-center text-fd-foreground hover:text-fd-foreground/80 transition-colors">
|
||||
<svg
|
||||
width="40"
|
||||
height="32"
|
||||
viewBox="0 0 200 242"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="Stack Logo"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
<path d="M103.504 1.81227C101.251 0.68679 98.6002 0.687576 96.3483 1.81439L4.4201 47.8136C1.71103 49.1692 0 51.9387 0 54.968V130.55C0 133.581 1.7123 136.351 4.42292 137.706L96.4204 183.695C98.6725 184.82 101.323 184.82 103.575 183.694L168.422 151.271C173.742 148.611 180 152.479 180 158.426V168.879C180 171.91 178.288 174.68 175.578 176.035L103.577 212.036C101.325 213.162 98.6745 213.162 96.4224 212.036L11.5771 169.623C6.25791 166.964 0 170.832 0 176.779V187.073C0 190.107 1.71689 192.881 4.43309 194.234L96.5051 240.096C98.7529 241.216 101.396 241.215 103.643 240.094L195.571 194.235C198.285 192.881 200 190.109 200 187.076V119.512C200 113.565 193.741 109.697 188.422 112.356L131.578 140.778C126.258 143.438 120 139.57 120 133.623V123.17C120 120.14 121.712 117.37 124.422 116.014L195.578 80.4368C198.288 79.0817 200 76.3116 200 73.2814V54.9713C200 51.9402 198.287 49.1695 195.576 47.8148L103.504 1.81227Z" fill="currentColor"/>
|
||||
</svg>
|
||||
</Link>
|
||||
|
||||
{/* Right Side - Actions */}
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Compact Theme Toggle */}
|
||||
<ThemeToggle compact />
|
||||
|
||||
{/* Compact AI Chat Toggle */}
|
||||
<HomeAIChatToggleButton compact />
|
||||
|
||||
{/* Compact User Button */}
|
||||
<UserButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search Dialog */}
|
||||
<CustomSearchDialog
|
||||
open={searchOpen}
|
||||
onOpenChange={setSearchOpen}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Main Home Layout Component
|
||||
export function HomeLayout({ children }: { children: ReactNode }) {
|
||||
// Add home-page class to body to exclude from chat content shifting
|
||||
useEffect(() => {
|
||||
document.body.classList.add('home-page');
|
||||
return () => {
|
||||
document.body.classList.remove('home-page');
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Add scroll detection for homepage
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
const scrollY = window.scrollY;
|
||||
const isScrolled = scrollY > 50;
|
||||
|
||||
if (isScrolled) {
|
||||
document.body.classList.add('scrolled');
|
||||
} else {
|
||||
document.body.classList.remove('scrolled');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
document.body.classList.remove('scrolled');
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<div className="relative flex min-h-screen flex-col bg-fd-background">
|
||||
<HomeNavbar />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<AIChatDrawer />
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Background,
|
||||
Edge,
|
||||
Handle,
|
||||
MarkerType,
|
||||
Node,
|
||||
NodeTypes,
|
||||
Position,
|
||||
ReactFlow,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
|
||||
// Actor node component - vertical lifelines with alternating sections
|
||||
const ActorNode = ({ data }: { data: { label: string, sections: { highlighted: boolean, id: string }[] } }) => {
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
{/* Actor header */}
|
||||
<div className="px-4 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white text-center font-medium text-sm mb-2">
|
||||
{data.label}
|
||||
</div>
|
||||
|
||||
{/* Vertical lifeline with sections */}
|
||||
<div className="flex flex-col items-center">
|
||||
{data.sections.map((section) => (
|
||||
<div key={section.id} className="relative">
|
||||
{section.highlighted ? (
|
||||
// Highlighted section (box)
|
||||
<div className="w-12 h-16 bg-gray-800 border border-gray-600 rounded flex items-center justify-center">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={`${section.id}-left`}
|
||||
style={{ left: -8, background: 'transparent', border: 'none', width: 16, height: 16 }}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={`${section.id}-right`}
|
||||
style={{ right: -8, background: 'transparent', border: 'none', width: 16, height: 16 }}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
// Regular line section
|
||||
<div className="w-0.5 h-16 bg-gray-400 flex items-center justify-center relative">
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={`${section.id}-left`}
|
||||
style={{ left: -8, background: 'transparent', border: 'none', width: 16, height: 16 }}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={`${section.id}-right`}
|
||||
style={{ right: -8, background: 'transparent', border: 'none', width: 16, height: 16 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Action node component - text between actors
|
||||
const ActionNode = ({ data }: { data: { label: string, dashed?: boolean } }) => {
|
||||
return (
|
||||
<div className={`px-3 py-1 bg-gray-800 border border-gray-600 rounded text-white text-sm font-medium whitespace-nowrap ${data.dashed ? 'border-dashed' : ''}`}>
|
||||
{data.label}
|
||||
<Handle type="target" position={Position.Left} style={{ background: 'transparent', border: 'none' }} />
|
||||
<Handle type="source" position={Position.Right} style={{ background: 'transparent', border: 'none' }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const nodeTypes: NodeTypes = {
|
||||
actor: ActorNode,
|
||||
action: ActionNode,
|
||||
};
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
// Actor nodes with their lifeline sections
|
||||
{
|
||||
id: 'user-actor',
|
||||
type: 'actor',
|
||||
position: { x: 50, y: 50 },
|
||||
data: {
|
||||
label: 'User/Client',
|
||||
sections: [
|
||||
{ highlighted: true, id: 'user-1' },
|
||||
{ highlighted: false, id: 'user-2' },
|
||||
{ highlighted: false, id: 'user-3' },
|
||||
{ highlighted: false, id: 'user-4' },
|
||||
{ highlighted: true, id: 'user-5' },
|
||||
]
|
||||
},
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'server-actor',
|
||||
type: 'actor',
|
||||
position: { x: 300, y: 50 },
|
||||
data: {
|
||||
label: 'Your Application Server',
|
||||
sections: [
|
||||
{ highlighted: true, id: 'server-1' },
|
||||
{ highlighted: true, id: 'server-2' },
|
||||
{ highlighted: true, id: 'server-3' },
|
||||
{ highlighted: true, id: 'server-4' },
|
||||
{ highlighted: true, id: 'server-5' },
|
||||
]
|
||||
},
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'auth-actor',
|
||||
type: 'actor',
|
||||
position: { x: 600, y: 50 },
|
||||
data: {
|
||||
label: 'Stack Auth Service',
|
||||
sections: [
|
||||
{ highlighted: false, id: 'auth-1' },
|
||||
{ highlighted: true, id: 'auth-2' },
|
||||
{ highlighted: true, id: 'auth-3' },
|
||||
{ highlighted: false, id: 'auth-4' },
|
||||
{ highlighted: false, id: 'auth-5' },
|
||||
]
|
||||
},
|
||||
draggable: false,
|
||||
},
|
||||
|
||||
// Action nodes
|
||||
{
|
||||
id: 'action-1',
|
||||
type: 'action',
|
||||
position: { x: 175, y: 120 },
|
||||
data: { label: 'API request with API key' },
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'action-2',
|
||||
type: 'action',
|
||||
position: { x: 450, y: 180 },
|
||||
data: { label: 'Validate API key' },
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'action-3',
|
||||
type: 'action',
|
||||
position: { x: 450, y: 240 },
|
||||
data: { label: 'Return authenticated User object', dashed: true },
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'action-4',
|
||||
type: 'action',
|
||||
position: { x: 300, y: 300 },
|
||||
data: { label: 'Process request' },
|
||||
draggable: false,
|
||||
},
|
||||
{
|
||||
id: 'action-5',
|
||||
type: 'action',
|
||||
position: { x: 175, y: 360 },
|
||||
data: { label: 'Response with data', dashed: true },
|
||||
draggable: false,
|
||||
},
|
||||
];
|
||||
|
||||
const initialEdges: Edge[] = [
|
||||
// Step 1: API request
|
||||
{
|
||||
id: 'edge-1',
|
||||
source: 'user-actor',
|
||||
sourceHandle: 'user-1-right',
|
||||
target: 'action-1',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
{
|
||||
id: 'edge-1b',
|
||||
source: 'action-1',
|
||||
target: 'server-actor',
|
||||
targetHandle: 'server-1-left',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
|
||||
// Step 2: Validate API key
|
||||
{
|
||||
id: 'edge-2',
|
||||
source: 'server-actor',
|
||||
sourceHandle: 'server-2-right',
|
||||
target: 'action-2',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
{
|
||||
id: 'edge-2b',
|
||||
source: 'action-2',
|
||||
target: 'auth-actor',
|
||||
targetHandle: 'auth-2-left',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
|
||||
// Step 3: Return user object (dashed)
|
||||
{
|
||||
id: 'edge-3',
|
||||
source: 'auth-actor',
|
||||
sourceHandle: 'auth-3-left',
|
||||
target: 'action-3',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
{
|
||||
id: 'edge-3b',
|
||||
source: 'action-3',
|
||||
target: 'server-actor',
|
||||
targetHandle: 'server-3-right',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
|
||||
// Step 4: Process request (self-loop)
|
||||
{
|
||||
id: 'edge-4',
|
||||
source: 'server-actor',
|
||||
sourceHandle: 'server-4-right',
|
||||
target: 'action-4',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
{
|
||||
id: 'edge-4b',
|
||||
source: 'action-4',
|
||||
target: 'server-actor',
|
||||
targetHandle: 'server-4-left',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
|
||||
// Step 5: Response with data (dashed)
|
||||
{
|
||||
id: 'edge-5',
|
||||
source: 'server-actor',
|
||||
sourceHandle: 'server-5-left',
|
||||
target: 'action-5',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
{
|
||||
id: 'edge-5b',
|
||||
source: 'action-5',
|
||||
target: 'user-actor',
|
||||
targetHandle: 'user-5-right',
|
||||
style: { stroke: '#ffffff', strokeWidth: 2, strokeDasharray: '5,5' },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, color: '#ffffff' },
|
||||
},
|
||||
];
|
||||
|
||||
export default function ApiSequenceDiagram() {
|
||||
return (
|
||||
<div className="w-full h-[500px] bg-gray-900 rounded-lg border border-gray-700">
|
||||
<ReactFlow
|
||||
nodes={initialNodes}
|
||||
edges={initialEdges}
|
||||
nodeTypes={nodeTypes}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 50 }}
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
elementsSelectable={false}
|
||||
panOnDrag={false}
|
||||
zoomOnScroll={false}
|
||||
zoomOnPinch={false}
|
||||
zoomOnDoubleClick={false}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
className="bg-gray-900"
|
||||
>
|
||||
<Background color="#374151" gap={20} />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,20 +3,26 @@ import * as React from "react";
|
||||
import { cn } from "../../lib/cn";
|
||||
import { buttonVariants } from "../ui/button";
|
||||
|
||||
type ColorVariant = 'primary' | 'default' | 'secondary' | 'outline' | 'ghost';
|
||||
type SizeVariant = 'default' | 'sm' | 'lg' | 'icon' | 'icon-sm';
|
||||
|
||||
type BaseButtonProps = {
|
||||
color?: 'primary' | 'secondary' | 'outline' | 'ghost',
|
||||
size?: 'sm' | 'icon' | 'icon-sm',
|
||||
/** @deprecated Use `variant` instead */
|
||||
color?: ColorVariant,
|
||||
/** Alias for `color` - preferred prop name */
|
||||
variant?: ColorVariant,
|
||||
size?: SizeVariant,
|
||||
icon?: React.ReactNode,
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
type ButtonAsButton = BaseButtonProps &
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'color'> & {
|
||||
href?: never,
|
||||
};
|
||||
|
||||
type ButtonAsLink = BaseButtonProps &
|
||||
React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'color'> & {
|
||||
href: string,
|
||||
};
|
||||
|
||||
@@ -25,7 +31,10 @@ type ButtonProps = ButtonAsButton | ButtonAsLink;
|
||||
export const Button = React.forwardRef<
|
||||
HTMLButtonElement | HTMLAnchorElement,
|
||||
ButtonProps
|
||||
>(({ className, color = 'secondary', size = 'sm', icon, href, children, ...props }, ref) => {
|
||||
>(({ className, color, variant, size = 'sm', icon, href, children, ...props }, ref) => {
|
||||
// Support both `variant` and `color` props (variant takes precedence)
|
||||
const resolvedColor = variant ?? color ?? 'secondary';
|
||||
|
||||
const buttonContent = (
|
||||
<>
|
||||
{icon && <span className="inline-flex items-center justify-center w-3.5 h-3.5">{icon}</span>}
|
||||
@@ -35,7 +44,7 @@ export const Button = React.forwardRef<
|
||||
|
||||
const buttonClasses = cn(
|
||||
buttonVariants({
|
||||
color,
|
||||
color: resolvedColor,
|
||||
size,
|
||||
className: 'gap-2 no-underline hover:no-underline'
|
||||
}),
|
||||
@@ -68,3 +77,5 @@ export const Button = React.forwardRef<
|
||||
});
|
||||
|
||||
Button.displayName = "Button";
|
||||
|
||||
export type { ButtonProps };
|
||||
|
||||
@@ -23,7 +23,7 @@ const getEmbeddedUrl = (href: string, currentPath?: string): string => {
|
||||
// Handle absolute paths
|
||||
if (href.startsWith('/')) {
|
||||
// Already embedded - leave as is
|
||||
if (href.startsWith('/docs-embed/') || href.startsWith('/api-embed/') || href.startsWith('/dashboard-embed/')) {
|
||||
if (href.startsWith('/docs-embed/') || href.startsWith('/api-embed/')) {
|
||||
return href;
|
||||
}
|
||||
|
||||
@@ -34,9 +34,6 @@ const getEmbeddedUrl = (href: string, currentPath?: string): string => {
|
||||
if (href.startsWith('/api/')) {
|
||||
return href.replace('/api/', '/api-embed/');
|
||||
}
|
||||
if (href.startsWith('/dashboard/')) {
|
||||
return href.replace('/dashboard/', '/dashboard-embed/');
|
||||
}
|
||||
|
||||
// Other absolute paths - leave as is
|
||||
return href;
|
||||
@@ -44,7 +41,7 @@ const getEmbeddedUrl = (href: string, currentPath?: string): string => {
|
||||
|
||||
// Handle relative links (like ./setup.mdx or users.mdx)
|
||||
// These need to be resolved relative to the current embedded path
|
||||
if (currentPath && (currentPath.startsWith('/docs-embed/') || currentPath.startsWith('/api-embed/') || currentPath.startsWith('/dashboard-embed/'))) {
|
||||
if (currentPath && (currentPath.startsWith('/docs-embed/') || currentPath.startsWith('/api-embed/'))) {
|
||||
// Remove .mdx extension if present
|
||||
const cleanHref = href.replace(/\.mdx?$/, '');
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
// Centralized platform and framework configuration
|
||||
export type PlatformConfig = {
|
||||
[platformName: string]: {
|
||||
[frameworkName: string]: {
|
||||
defaultFilename?: string,
|
||||
language: string,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const PLATFORM_CONFIG: PlatformConfig = {
|
||||
"Python": {
|
||||
"Django": {
|
||||
defaultFilename: "views.py",
|
||||
language: "python"
|
||||
},
|
||||
"FastAPI": {
|
||||
defaultFilename: "main.py",
|
||||
language: "python"
|
||||
},
|
||||
"Flask": {
|
||||
defaultFilename: "app.py",
|
||||
language: "python"
|
||||
}
|
||||
},
|
||||
"JavaScript": {
|
||||
"Next.js": {
|
||||
defaultFilename: "app/api/route.ts",
|
||||
language: "typescript"
|
||||
},
|
||||
"Express": {
|
||||
defaultFilename: "server.js",
|
||||
language: "javascript"
|
||||
},
|
||||
"React": {
|
||||
defaultFilename: "components/LoginForm.tsx",
|
||||
language: "typescript"
|
||||
},
|
||||
"Node.js": {
|
||||
defaultFilename: "index.js",
|
||||
language: "javascript"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to get available platforms
|
||||
export function getAvailablePlatforms(): string[] {
|
||||
return Object.keys(PLATFORM_CONFIG);
|
||||
}
|
||||
|
||||
// Helper function to get frameworks for a platform
|
||||
export function getFrameworksForPlatform(platform: string): string[] {
|
||||
return Object.keys(PLATFORM_CONFIG[platform]);
|
||||
}
|
||||
|
||||
// Helper function to get config for a platform/framework combination
|
||||
export function getPlatformFrameworkConfig(platform: string, framework: string) {
|
||||
return PLATFORM_CONFIG[platform][framework];
|
||||
}
|
||||
|
||||
// Default framework preferences (can be overridden)
|
||||
export const DEFAULT_FRAMEWORK_PREFERENCES: { [platform: string]: string } = {
|
||||
"Python": "Django",
|
||||
"JavaScript": "Next.js"
|
||||
};
|
||||
|
||||
@@ -189,14 +189,3 @@ export function SignInPasswordFirstTab() {
|
||||
</StackContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export function SignInExtraInfo() {
|
||||
return (
|
||||
<StackContainer color="blue">
|
||||
<SignIn
|
||||
extraInfo={<>By signing in, you agree to our <a href="#" className="text-fd-primary hover:underline">Terms of Service</a></>}
|
||||
mockProject={mockProject}
|
||||
/>
|
||||
</StackContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors duration-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-fd-primary disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
color: {
|
||||
primary:
|
||||
'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80',
|
||||
outline: 'border hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
default:
|
||||
'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/90',
|
||||
outline: 'border border-fd-border bg-fd-background hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
secondary:
|
||||
'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
},
|
||||
size: {
|
||||
sm: 'gap-1 px-2 py-1.5 text-xs',
|
||||
icon: 'p-1.5 [&_svg]:size-5',
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 gap-1 px-3 py-1.5 text-xs rounded-md',
|
||||
lg: 'h-10 px-8 rounded-md',
|
||||
icon: 'h-9 w-9 p-1.5 [&_svg]:size-5',
|
||||
'icon-sm': 'p-1.5 [&_svg]:size-4.5',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: 'primary',
|
||||
size: 'default',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { WebhooksAPIPage } from './components/api/webhooks-api-page';
|
||||
|
||||
import AppleSecretGenerator from './components/apple-secret-generator';
|
||||
import { Card, CardGroup, Info, QuickLink, QuickLinks } from './components/mdx';
|
||||
import ApiSequenceDiagram from './components/mdx/api-sequence-diagram';
|
||||
import { AuthCard } from './components/mdx/auth-card';
|
||||
import { DynamicCodeblock } from './components/mdx/dynamic-code-block';
|
||||
import { EmbeddedLink } from './components/mdx/embedded-link';
|
||||
@@ -29,21 +28,21 @@ import { Accordion, AccordionGroup, ClickableTableOfContents, Icon, Markdown, Pa
|
||||
import { PropTable } from './components/prop-table';
|
||||
|
||||
import { ImageZoom } from 'fumadocs-ui/components/image-zoom';
|
||||
import DocsSelector from './components/homepage/iconHover';
|
||||
import { AppCard, AppGrid } from './components/mdx/app-card';
|
||||
import { SignInDemo, SignInExtraInfo, SignInPasswordFirstTab, SignInStackAuth } from './components/stack-auth/sign-in';
|
||||
import { SignInDemo, SignInPasswordFirstTab, SignInStackAuth } from './components/stack-auth/sign-in';
|
||||
import { AccountSettingsStackAuth } from './components/stack-auth/stack-account-settings';
|
||||
import { TeamSwitcherDemo } from './components/stack-auth/stack-team-switcher';
|
||||
import { StackUserButton } from './components/stack-auth/stack-user-button';
|
||||
import { UserButtonDemo } from './components/stack-auth/stack-user-button-demo';
|
||||
import { Step, Steps } from './components/steps';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs';
|
||||
|
||||
export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...defaultMdxComponents,
|
||||
...components,
|
||||
...CodeBlock,
|
||||
//SignIn
|
||||
// Components
|
||||
Card,
|
||||
CardGroup,
|
||||
QuickLink,
|
||||
@@ -54,7 +53,6 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
SignInDemo,
|
||||
AuthCard,
|
||||
AccountSettingsStackAuth,
|
||||
SignInExtraInfo,
|
||||
StackUserButton,
|
||||
UserButtonDemo,
|
||||
TeamSwitcherDemo,
|
||||
@@ -69,7 +67,6 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
WebhooksAPIPage,
|
||||
TypeTable,
|
||||
PropTable,
|
||||
ApiSequenceDiagram,
|
||||
// SDK Documentation Components
|
||||
Markdown,
|
||||
ParamField,
|
||||
@@ -99,7 +96,6 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
// App Components
|
||||
AppCard,
|
||||
AppGrid,
|
||||
DocsSelector,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
img: (props) => <ImageZoom {...(props as any)} />,
|
||||
} as MDXComponents;
|
||||
|
||||
Reference in New Issue
Block a user