mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
[Docs][Content] - Updates Example Pages -> Vite Vanilla JS examples (#1050)
<!-- Make sure you've read the CONTRIBUTING.md guidelines: https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md --> feat(docs): centralize code examples with dynamic variant tabs This PR introduces a system for managing code examples centrally while supporting file-based variant tabs (like html/script) within a single code block. ## Changes - **Extended variant system**: `PlatformCodeblock` now supports custom variant names beyond just 'server'/'client', enabling tabs for any file grouping (e.g., html/script pairs) - **Vite example migration**: Moved vite-example.mdx code to `code-examples/vite-example.ts` with html/script variants - **LLM copy support**: The "Copy Markdown" button now expands `PlatformCodeblock` components to inline the actual code, so LLMs receive the full code examples instead of component references ## How it works Code examples with variants are now displayed with filename-based tabs: - Define examples with `variant: 'html'` and `variant: 'script'` in `code-examples/` - The tab labels automatically use the `filename` property - When copying markdown for LLMs, all variants are included with their filenames <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **Documentation** * Added a Vite JavaScript example guide with grouped auth examples (init, index, password, OTP, OAuth) and wired examples into the getting-started navigation. * Removed the previous multi-page example guide and replaced it with the new Vite-focused page. * Documentation generation now expands platform code blocks to inline concrete examples for clearer rendered docs. * **Refactor** * Improved code-example variant handling to support flexible variant names for better tabbed/code-sample organization. <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:
parent
8c4b8c5544
commit
f3d0a68720
@ -1,10 +1,12 @@
|
||||
import { CodeExample } from '../lib/code-examples';
|
||||
import { apiKeysExamples } from './api-keys';
|
||||
import { setupExamples } from './setup';
|
||||
import { viteExamples } from './vite-example';
|
||||
|
||||
const allExamples: Record<string, Record<string, Record<string, CodeExample[]>>> = {
|
||||
'setup': setupExamples,
|
||||
'apps': apiKeysExamples,
|
||||
'getting-started': viteExamples,
|
||||
// Add more sections here as needed:
|
||||
// 'auth': authExamples,
|
||||
// 'customization': customizationExamples,
|
||||
|
||||
445
docs/code-examples/vite-example.ts
Normal file
445
docs/code-examples/vite-example.ts
Normal file
@ -0,0 +1,445 @@
|
||||
import { CodeExample } from '../lib/code-examples';
|
||||
|
||||
export const viteExamples = {
|
||||
'vite-example': {
|
||||
'init-app': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
code: `import { StackClientApp } from "@stackframe/js";
|
||||
|
||||
// Add type declaration for Vite's import.meta.env
|
||||
declare global {
|
||||
interface ImportMeta {
|
||||
env: {
|
||||
VITE_STACK_API_URL: string;
|
||||
VITE_STACK_PROJECT_ID: string;
|
||||
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const stackClientApp = new StackClientApp({
|
||||
baseUrl: import.meta.env.VITE_STACK_API_URL,
|
||||
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
||||
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
tokenStore: "cookie",
|
||||
urls: {
|
||||
oauthCallback: window.location.origin + "/oauth",
|
||||
},
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'stack/client.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
|
||||
'index-page': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'html',
|
||||
code: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stack Auth JS Examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stack Auth JS Examples</h1>
|
||||
|
||||
<div id="authOptions">
|
||||
<p>Choose an authentication example:</p>
|
||||
<ul>
|
||||
<li><a href="/password-sign-in">Sign in with Password</a></li>
|
||||
<li><a href="/password-sign-up">Create Account with Password</a></li>
|
||||
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
|
||||
<li><a href="/oauth">Sign in with Google</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="userInfo" style="display: none;">
|
||||
<h2>User Information</h2>
|
||||
<p>Email: <span id="userEmail"></span></p>
|
||||
<button id="signOut">Sign Out</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/index-script.ts"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
highlightLanguage: 'html',
|
||||
filename: 'index.html'
|
||||
},
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'script',
|
||||
code: `import { stackClientApp } from "./stack/client";
|
||||
|
||||
const updateUIState = (user: any | null) => {
|
||||
const authOptions = document.getElementById("authOptions");
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
const userEmailSpan = document.getElementById("userEmail");
|
||||
|
||||
if (user) {
|
||||
if (authOptions) authOptions.style.display = "none";
|
||||
if (userInfo) userInfo.style.display = "block";
|
||||
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
||||
} else {
|
||||
if (authOptions) authOptions.style.display = "block";
|
||||
if (userInfo) userInfo.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then(updateUIState);
|
||||
|
||||
// Handle Sign Out
|
||||
document.getElementById("signOut")?.addEventListener("click", async () => {
|
||||
const user = await stackClientApp.getUser();
|
||||
if (user) {
|
||||
await user.signOut();
|
||||
updateUIState(null);
|
||||
}
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'index-script.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
|
||||
'password-sign-in': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'html',
|
||||
code: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Sign In</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Password Sign In</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="loginForm">
|
||||
<h2>Sign In</h2>
|
||||
<input type="email" id="emailInput" placeholder="Email" />
|
||||
<input type="password" id="passwordInput" placeholder="Password" />
|
||||
<button id="signIn">Sign In</button>
|
||||
<div>
|
||||
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/password-sign-in-script.ts"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
highlightLanguage: 'html',
|
||||
filename: 'password-sign-in.html'
|
||||
},
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'script',
|
||||
code: `import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("loginForm")?.classList.add("hidden");
|
||||
document.getElementById("signUpForm")?.classList.remove("hidden");
|
||||
});
|
||||
|
||||
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("loginForm")?.classList.remove("hidden");
|
||||
document.getElementById("signUpForm")?.classList.add("hidden");
|
||||
});
|
||||
|
||||
document.getElementById("signIn")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign in failed. Please check your email and password and try again.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signUp")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signUpWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign up failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signInResult = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'password-sign-in-script.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
|
||||
'password-sign-up': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'html',
|
||||
code: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Sign Up</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Password Sign Up</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="signUpForm">
|
||||
<h2>Sign Up</h2>
|
||||
<input type="email" id="signUpEmail" placeholder="Email" />
|
||||
<input type="password" id="signUpPassword" placeholder="Password" />
|
||||
<button id="signUp">Sign Up</button>
|
||||
<div>
|
||||
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/password-sign-up-script.ts"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
highlightLanguage: 'html',
|
||||
filename: 'password-sign-up.html'
|
||||
},
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'script',
|
||||
code: `import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signUp")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signUpWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign up failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signInResult = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
window.location.href = "/password-sign-in";
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'password-sign-up-script.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
|
||||
'otp-sign-in': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'html',
|
||||
code: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OTP Sign In</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OTP Sign In</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="otpForm">
|
||||
<h2>Sign In with Email Code</h2>
|
||||
<div id="emailStep">
|
||||
<input type="email" id="emailInput" placeholder="Email" />
|
||||
<button id="sendCode">Send Code</button>
|
||||
</div>
|
||||
|
||||
<div id="codeStep" style="display: none;">
|
||||
<p>Enter the code sent to your email</p>
|
||||
<input type="text" id="codeInput" placeholder="Enter code" />
|
||||
<button id="verifyCode">Verify Code</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/otp-sign-in-script.ts"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
highlightLanguage: 'html',
|
||||
filename: 'otp-sign-in.html'
|
||||
},
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'script',
|
||||
code: `import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("sendCode")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
||||
|
||||
await stackClientApp.sendMagicLinkEmail({
|
||||
email: emailInput.value,
|
||||
});
|
||||
|
||||
document.getElementById("emailStep")!.style.display = "none";
|
||||
document.getElementById("codeStep")!.style.display = "block";
|
||||
});
|
||||
|
||||
document.getElementById("verifyCode")?.addEventListener("click", async () => {
|
||||
const codeInput = document.getElementById("codeInput") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signInWithMagicLink({
|
||||
code: codeInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Verification failed. Please check the code and try again.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'otp-sign-in-script.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
|
||||
'oauth': [
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'html',
|
||||
code: `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OAuth Authentication</title>
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OAuth Authentication</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="loginButtons">
|
||||
<h2>Sign In with OAuth</h2>
|
||||
<button id="googleSignIn">Sign in with Google</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/oauth-script.ts"></script>
|
||||
</body>
|
||||
</html>`,
|
||||
highlightLanguage: 'html',
|
||||
filename: 'oauth.html'
|
||||
},
|
||||
{
|
||||
language: 'JavaScript',
|
||||
framework: 'Vanilla JavaScript',
|
||||
variant: 'script',
|
||||
code: `import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Google Sign In
|
||||
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
|
||||
try {
|
||||
await stackClientApp.signInWithOAuth('google');
|
||||
} catch (error) {
|
||||
console.error("Google sign in failed:", error);
|
||||
alert("Failed to initialize Google sign in");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle OAuth redirect
|
||||
window.addEventListener("load", async () => {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get("code");
|
||||
const state = params.get("state");
|
||||
|
||||
if (code && state) {
|
||||
const user = await stackClientApp.callOAuthCallback();
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to handle OAuth redirect:", error);
|
||||
alert("Authentication failed. Please try again.");
|
||||
}
|
||||
});`,
|
||||
highlightLanguage: 'typescript',
|
||||
filename: 'oauth-script.ts'
|
||||
}
|
||||
] as CodeExample[],
|
||||
}
|
||||
};
|
||||
@ -1,453 +0,0 @@
|
||||
---
|
||||
title: Example-pages
|
||||
---
|
||||
|
||||
|
||||
This guide demonstrates how to integrate Stack Auth with Vite. The same principles apply to other JavaScript frameworks as well. You can find the complete example code in our [GitHub repository](https://github.com/stack-auth/stack-auth/tree/main/examples/js-example).
|
||||
|
||||
### Initialize the app
|
||||
|
||||
```typescript title="stack/client.ts"
|
||||
import { StackClientApp } from "@stackframe/js";
|
||||
|
||||
// Add type declaration for Vite's import.meta.env
|
||||
declare global {
|
||||
interface ImportMeta {
|
||||
env: {
|
||||
VITE_STACK_API_URL: string;
|
||||
VITE_STACK_PROJECT_ID: string;
|
||||
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const stackClientApp = new StackClientApp({
|
||||
baseUrl: import.meta.env.VITE_STACK_API_URL,
|
||||
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
|
||||
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
|
||||
tokenStore: "cookie",
|
||||
urls: {
|
||||
oauthCallback: window.location.origin + "/oauth",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Index page with user information
|
||||
|
||||
<Tabs defaultValue="html">
|
||||
<TabsList>
|
||||
<TabsTrigger value="html">index.html</TabsTrigger>
|
||||
<TabsTrigger value="script">index-script.ts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="html">
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stack Auth JS Examples</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stack Auth JS Examples</h1>
|
||||
|
||||
<div id="authOptions">
|
||||
<p>Choose an authentication example:</p>
|
||||
<ul>
|
||||
<li><a href="/password-sign-in">Sign in with Password</a></li>
|
||||
<li><a href="/password-sign-up">Create Account with Password</a></li>
|
||||
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
|
||||
<li><a href="/oauth">Sign in with Google</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="userInfo" style="display: none;">
|
||||
<h2>User Information</h2>
|
||||
<p>Email: <span id="userEmail"></span></p>
|
||||
<button id="signOut">Sign Out</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/index-script.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="script">
|
||||
```typescript
|
||||
import { stackClientApp } from "./stack/client";
|
||||
|
||||
const updateUIState = (user: any | null) => {
|
||||
const authOptions = document.getElementById("authOptions");
|
||||
const userInfo = document.getElementById("userInfo");
|
||||
const userEmailSpan = document.getElementById("userEmail");
|
||||
|
||||
if (user) {
|
||||
if (authOptions) authOptions.style.display = "none";
|
||||
if (userInfo) userInfo.style.display = "block";
|
||||
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
|
||||
} else {
|
||||
if (authOptions) authOptions.style.display = "block";
|
||||
if (userInfo) userInfo.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then(updateUIState);
|
||||
|
||||
// Handle Sign Out
|
||||
document.getElementById("signOut")?.addEventListener("click", async () => {
|
||||
const user = await stackClientApp.getUser();
|
||||
if (user) {
|
||||
await user.signOut();
|
||||
updateUIState(null);
|
||||
}
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
|
||||
### Sign in with password
|
||||
|
||||
<Tabs defaultValue="html">
|
||||
<TabsList>
|
||||
<TabsTrigger value="html">password-sign-in.html</TabsTrigger>
|
||||
<TabsTrigger value="script">password-sign-in-script.ts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="html">
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Sign In</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Password Sign In</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="loginForm">
|
||||
<h2>Sign In</h2>
|
||||
<input type="email" id="emailInput" placeholder="Email" />
|
||||
<input type="password" id="passwordInput" placeholder="Password" />
|
||||
<button id="signIn">Sign In</button>
|
||||
<div>
|
||||
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/password-sign-in-script.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="script">
|
||||
```typescript
|
||||
import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("loginForm")?.classList.add("hidden");
|
||||
document.getElementById("signUpForm")?.classList.remove("hidden");
|
||||
});
|
||||
|
||||
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
document.getElementById("loginForm")?.classList.remove("hidden");
|
||||
document.getElementById("signUpForm")?.classList.add("hidden");
|
||||
});
|
||||
|
||||
document.getElementById("signIn")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign in failed. Please check your email and password and try again.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signUp")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signUpWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign up failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signInResult = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
### Sign up with password
|
||||
|
||||
<Tabs defaultValue="html">
|
||||
<TabsList>
|
||||
<TabsTrigger value="html">password-sign-up.html</TabsTrigger>
|
||||
<TabsTrigger value="script">password-sign-up-script.ts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="html">
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Sign Up</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Password Sign Up</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="signUpForm">
|
||||
<h2>Sign Up</h2>
|
||||
<input type="email" id="signUpEmail" placeholder="Email" />
|
||||
<input type="password" id="signUpPassword" placeholder="Password" />
|
||||
<button id="signUp">Sign Up</button>
|
||||
<div>
|
||||
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/password-sign-up-script.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="script">
|
||||
```typescript
|
||||
import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("signUp")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
|
||||
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signUpWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Sign up failed. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
const signInResult = await stackClientApp.signInWithCredential({
|
||||
email: emailInput.value,
|
||||
password: passwordInput.value,
|
||||
});
|
||||
|
||||
if (signInResult.status === "error") {
|
||||
alert("Account created but sign in failed. Please sign in manually.");
|
||||
window.location.href = "/password-sign-in";
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
### Sign in with OTP/Magic Link
|
||||
|
||||
<Tabs defaultValue="html">
|
||||
<TabsList>
|
||||
<TabsTrigger value="html">otp-sign-in.html</TabsTrigger>
|
||||
<TabsTrigger value="script">otp-sign-in-script.ts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="html">
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OTP Sign In</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OTP Sign In</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="otpForm">
|
||||
<h2>Sign In with Email Code</h2>
|
||||
<div id="emailStep">
|
||||
<input type="email" id="emailInput" placeholder="Email" />
|
||||
<button id="sendCode">Send Code</button>
|
||||
</div>
|
||||
|
||||
<div id="codeStep" style="display: none;">
|
||||
<p>Enter the code sent to your email</p>
|
||||
<input type="text" id="codeInput" placeholder="Enter code" />
|
||||
<button id="verifyCode">Verify Code</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/otp-sign-in-script.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="script">
|
||||
```typescript
|
||||
import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("sendCode")?.addEventListener("click", async () => {
|
||||
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
|
||||
|
||||
await stackClientApp.sendMagicLinkEmail({
|
||||
email: emailInput.value,
|
||||
});
|
||||
|
||||
document.getElementById("emailStep")!.style.display = "none";
|
||||
document.getElementById("codeStep")!.style.display = "block";
|
||||
});
|
||||
|
||||
document.getElementById("verifyCode")?.addEventListener("click", async () => {
|
||||
const codeInput = document.getElementById("codeInput") as HTMLInputElement;
|
||||
|
||||
const result = await stackClientApp.signInWithMagicLink({
|
||||
code: codeInput.value,
|
||||
});
|
||||
|
||||
if (result.status === "error") {
|
||||
alert("Verification failed. Please check the code and try again.");
|
||||
} else {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
### OAuth sign in
|
||||
|
||||
<Tabs defaultValue="html">
|
||||
<TabsList>
|
||||
<TabsTrigger value="html">oauth.html</TabsTrigger>
|
||||
<TabsTrigger value="script">oauth-script.ts</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="html">
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OAuth Authentication</title>
|
||||
<style>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OAuth Authentication</h1>
|
||||
<p><a href="/">← Back to home</a></p>
|
||||
|
||||
<div id="loginButtons">
|
||||
<h2>Sign In with OAuth</h2>
|
||||
<button id="googleSignIn">Sign in with Google</button>
|
||||
</div>
|
||||
|
||||
<script type="module" src="/oauth-script.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="script">
|
||||
```typescript
|
||||
import { stackClientApp } from "./stack/client";
|
||||
|
||||
// Check if user is already signed in
|
||||
stackClientApp.getUser().then((user) => {
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Google Sign In
|
||||
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
|
||||
try {
|
||||
await stackClientApp.signInWithOAuth('google');
|
||||
} catch (error) {
|
||||
console.error("Google sign in failed:", error);
|
||||
alert("Failed to initialize Google sign in");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle OAuth redirect
|
||||
window.addEventListener("load", async () => {
|
||||
try {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get("code");
|
||||
const state = params.get("state");
|
||||
|
||||
if (code && state) {
|
||||
const user = await stackClientApp.callOAuthCallback();
|
||||
if (user) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to handle OAuth redirect:", error);
|
||||
alert("Authentication failed. Please try again.");
|
||||
}
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
48
docs/content/docs/(guides)/getting-started/vite-example.mdx
Normal file
48
docs/content/docs/(guides)/getting-started/vite-example.mdx
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Vite JavaScript Example
|
||||
---
|
||||
|
||||
|
||||
This guide demonstrates how to integrate Stack Auth with Vite. The same principles apply to other JavaScript frameworks as well. You can find the complete example code in our [GitHub repository](https://github.com/stack-auth/stack-auth/tree/main/examples/js-example).
|
||||
|
||||
### Initialize the app
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["init-app"]}
|
||||
/>
|
||||
|
||||
### Index page with user information
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["index-page"]}
|
||||
/>
|
||||
|
||||
### Sign in with password
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["password-sign-in"]}
|
||||
/>
|
||||
|
||||
### Sign up with password
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["password-sign-up"]}
|
||||
/>
|
||||
|
||||
### Sign in with OTP/Magic Link
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["otp-sign-in"]}
|
||||
/>
|
||||
|
||||
### OAuth sign in
|
||||
|
||||
<PlatformCodeblock
|
||||
document="getting-started/vite-example"
|
||||
examples={["oauth"]}
|
||||
/>
|
||||
@ -10,7 +10,7 @@
|
||||
"getting-started/components",
|
||||
"getting-started/users",
|
||||
"getting-started/production",
|
||||
"getting-started/example-pages",
|
||||
"getting-started/vite-example",
|
||||
"---Apps---",
|
||||
"apps/api-keys",
|
||||
"apps/emails",
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
export type CodeExample = {
|
||||
language: string;
|
||||
framework: string;
|
||||
variant?: 'server' | 'client';
|
||||
/** Variant for tabbed code within the same platform/framework (e.g., 'server'/'client' or 'html'/'script') */
|
||||
variant?: string;
|
||||
code: string;
|
||||
highlightLanguage: string;
|
||||
filename?: string;
|
||||
|
||||
@ -5,6 +5,8 @@ import { join } from 'path';
|
||||
import { remark } from 'remark';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkMdx from 'remark-mdx';
|
||||
import { getExample } from '../code-examples';
|
||||
import type { CodeExample } from './code-examples';
|
||||
import { apiSource, source } from './source';
|
||||
|
||||
const processor = remark()
|
||||
@ -150,6 +152,71 @@ function formatOpenAPISpec(spec: any): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper function to format a code example as markdown
|
||||
function formatCodeExample(example: CodeExample): string {
|
||||
const filename = example.filename ? ` title="${example.filename}"` : '';
|
||||
return `\`\`\`${example.highlightLanguage}${filename}\n${example.code}\n\`\`\``;
|
||||
}
|
||||
|
||||
// Helper function to replace PlatformCodeblock components with actual code
|
||||
function expandPlatformCodeblocks(content: string): string {
|
||||
// Match <PlatformCodeblock document="..." examples={[...]} /> or multi-line versions
|
||||
const platformCodeblockRegex = /<PlatformCodeblock\s+([^>]*?)\/>/gs;
|
||||
|
||||
return content.replace(platformCodeblockRegex, (match, propsString) => {
|
||||
// Extract document prop
|
||||
const documentMatch = propsString.match(/document=["']([^"']+)["']/);
|
||||
// Extract examples prop - handle both {["..."]} and {["...", "..."]} formats
|
||||
const examplesMatch = propsString.match(/examples=\{\s*\[([^\]]+)\]\s*\}/);
|
||||
|
||||
if (!documentMatch || !examplesMatch) {
|
||||
// Can't parse, return original
|
||||
return match;
|
||||
}
|
||||
|
||||
const documentPath = documentMatch[1];
|
||||
// Parse the examples array - handle quoted strings
|
||||
const examplesStr = examplesMatch[1];
|
||||
const exampleNames = examplesStr
|
||||
.split(',')
|
||||
.map((s: string) => s.trim().replace(/^["']|["']$/g, ''));
|
||||
|
||||
let result = '';
|
||||
|
||||
for (const exampleName of exampleNames) {
|
||||
const examples = getExample(documentPath, exampleName);
|
||||
if (!examples || examples.length === 0) {
|
||||
result += `<!-- Code example "${exampleName}" not found -->\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Group examples by variant if they have variants
|
||||
const hasVariants = examples.some(e => e.variant);
|
||||
|
||||
if (hasVariants) {
|
||||
// Show all variants
|
||||
for (const example of examples) {
|
||||
if (example.filename) {
|
||||
result += `**${example.filename}:**\n\n`;
|
||||
}
|
||||
result += formatCodeExample(example) + '\n\n';
|
||||
}
|
||||
} else {
|
||||
// No variants - just show the first example (typically there's only one per framework)
|
||||
// For LLM context, show all framework variants
|
||||
for (const example of examples) {
|
||||
if (examples.length > 1 && example.framework) {
|
||||
result += `**${example.framework}:**\n\n`;
|
||||
}
|
||||
result += formatCodeExample(example) + '\n\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getLLMText(page: InferPageType<typeof source> | InferPageType<typeof apiSource>) {
|
||||
try {
|
||||
// Check if this is an API page and extract OpenAPI content
|
||||
@ -172,6 +239,9 @@ ${openAPIContent}`;
|
||||
''
|
||||
);
|
||||
|
||||
// Expand PlatformCodeblock components to inline code
|
||||
content = expandPlatformCodeblocks(content);
|
||||
|
||||
const processed = await processor.process({
|
||||
path: page.data._file.absolutePath,
|
||||
value: content,
|
||||
|
||||
@ -11,7 +11,7 @@ import { BaseCodeblock } from './base-codeblock';
|
||||
type PlatformChangeListener = (platform: string) => void;
|
||||
type FrameworkChangeListener = (platform: string, framework: string) => void;
|
||||
|
||||
type VariantSelections = Partial<Record<string, Partial<Record<string, 'server' | 'client'>>>>;
|
||||
type VariantSelections = Partial<Record<string, Partial<Record<string, string>>>>;
|
||||
|
||||
const platformListeners = new Map<string, PlatformChangeListener[]>();
|
||||
const frameworkListeners = new Map<string, FrameworkChangeListener[]>();
|
||||
@ -126,6 +126,14 @@ export type PlatformCodeblockProps = {
|
||||
className?: string,
|
||||
}
|
||||
|
||||
type VariantConfig = {
|
||||
code: string,
|
||||
language?: string,
|
||||
filename?: string,
|
||||
};
|
||||
|
||||
type FrameworkConfig = VariantConfig | { [variantName: string]: VariantConfig };
|
||||
|
||||
/**
|
||||
* Converts CodeExample[] from code-examples.ts to the platforms format
|
||||
* Only includes platforms/frameworks defined in the global config
|
||||
@ -133,27 +141,14 @@ export type PlatformCodeblockProps = {
|
||||
function convertExamplesToPlatforms(examples: CodeExample[]) {
|
||||
const platforms: {
|
||||
[platformName: string]: {
|
||||
[frameworkName: string]: {
|
||||
code: string,
|
||||
language?: string,
|
||||
filename?: string,
|
||||
} | {
|
||||
server: {
|
||||
code: string,
|
||||
language?: string,
|
||||
filename?: string,
|
||||
},
|
||||
client: {
|
||||
code: string,
|
||||
language?: string,
|
||||
filename?: string,
|
||||
},
|
||||
},
|
||||
[frameworkName: string]: FrameworkConfig,
|
||||
},
|
||||
} = {};
|
||||
|
||||
const defaultFrameworks: { [platformName: string]: string } = {};
|
||||
const defaultVariants: VariantSelections = {};
|
||||
// Track which variants exist for each platform/framework
|
||||
const variantKeys: { [platform: string]: { [framework: string]: string[] } } = {};
|
||||
|
||||
// Initialize default frameworks from global config
|
||||
for (const platformConfig of PLATFORMS) {
|
||||
@ -180,34 +175,38 @@ function convertExamplesToPlatforms(examples: CodeExample[]) {
|
||||
}
|
||||
|
||||
if (variant) {
|
||||
// Has server/client variant - initialize if not already a variant config
|
||||
// We check if 'server' exists to determine if it's already been initialized as a variant config
|
||||
if (!('server' in (platforms[language][framework] ?? {}))) {
|
||||
platforms[language][framework] = {
|
||||
server: { code: '', language: highlightLanguage },
|
||||
client: { code: '', language: highlightLanguage }
|
||||
};
|
||||
// Has variant - initialize tracking if needed
|
||||
if (!(language in variantKeys)) {
|
||||
variantKeys[language] = {};
|
||||
}
|
||||
if (!(framework in variantKeys[language])) {
|
||||
variantKeys[language][framework] = [];
|
||||
}
|
||||
|
||||
const variantConfig = platforms[language][framework] as {
|
||||
server: { code: string, language?: string, filename?: string },
|
||||
client: { code: string, language?: string, filename?: string },
|
||||
};
|
||||
// Track this variant key
|
||||
if (!variantKeys[language][framework].includes(variant)) {
|
||||
variantKeys[language][framework].push(variant);
|
||||
}
|
||||
|
||||
// Explicitly narrow the variant type
|
||||
const variantType: 'server' | 'client' = variant;
|
||||
variantConfig[variantType] = {
|
||||
// Initialize framework config as variant object if needed
|
||||
const existingConfig = platforms[language][framework] as FrameworkConfig | undefined;
|
||||
if (existingConfig === undefined || 'code' in existingConfig) {
|
||||
platforms[language][framework] = {};
|
||||
}
|
||||
|
||||
const variantConfig = platforms[language][framework] as { [key: string]: VariantConfig };
|
||||
variantConfig[variant] = {
|
||||
code,
|
||||
language: highlightLanguage,
|
||||
filename
|
||||
};
|
||||
|
||||
// Initialize default variants
|
||||
// Initialize default variants (use first variant seen)
|
||||
if (!(language in defaultVariants)) {
|
||||
defaultVariants[language] = {};
|
||||
}
|
||||
if (!defaultVariants[language]?.[framework]) {
|
||||
defaultVariants[language]![framework] = 'server';
|
||||
defaultVariants[language]![framework] = variant;
|
||||
}
|
||||
} else {
|
||||
// No variant
|
||||
@ -233,7 +232,7 @@ function convertExamplesToPlatforms(examples: CodeExample[]) {
|
||||
? DEFAULT_PLATFORM
|
||||
: Object.keys(sortedPlatforms)[0];
|
||||
|
||||
return { platforms: sortedPlatforms, defaultPlatform, defaultFrameworks, defaultVariants };
|
||||
return { platforms: sortedPlatforms, defaultPlatform, defaultFrameworks, defaultVariants, variantKeys };
|
||||
}
|
||||
|
||||
export function PlatformCodeblock({
|
||||
@ -255,9 +254,9 @@ export function PlatformCodeblock({
|
||||
}
|
||||
|
||||
// Convert to the internal platforms format
|
||||
const { platforms, defaultPlatform, defaultFrameworks, defaultVariants } = allExamples.length > 0
|
||||
const { platforms, defaultPlatform, defaultFrameworks, defaultVariants, variantKeys } = allExamples.length > 0
|
||||
? convertExamplesToPlatforms(allExamples)
|
||||
: { platforms: {}, defaultPlatform: '', defaultFrameworks: {}, defaultVariants: {} };
|
||||
: { platforms: {}, defaultPlatform: '', defaultFrameworks: {}, defaultVariants: {}, variantKeys: {} };
|
||||
|
||||
const platformNames = Object.keys(platforms);
|
||||
const firstPlatform = defaultPlatform || platformNames[0];
|
||||
@ -366,41 +365,58 @@ export function PlatformCodeblock({
|
||||
const currentFrameworks = Object.keys(platforms[selectedPlatform] ?? {});
|
||||
const currentFramework = selectedFrameworks[selectedPlatform] || currentFrameworks[0];
|
||||
|
||||
// Helper functions for server/client variants
|
||||
const hasVariants = (platform: string, framework: string) => {
|
||||
const platformConfig = platforms[platform];
|
||||
const config = platformConfig[framework];
|
||||
if (typeof config !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'server' in config && 'client' in config;
|
||||
// Helper functions for variants (supports dynamic variant names like 'server'/'client' or 'html'/'script')
|
||||
const getVariantKeys = (platform: string, framework: string): string[] => {
|
||||
if (!(platform in variantKeys)) return [];
|
||||
const platformVariants = variantKeys[platform];
|
||||
if (!(framework in platformVariants)) return [];
|
||||
return platformVariants[framework];
|
||||
};
|
||||
|
||||
const getCurrentVariant = (): 'server' | 'client' => {
|
||||
const hasVariants = (platform: string, framework: string) => {
|
||||
return getVariantKeys(platform, framework).length > 0;
|
||||
};
|
||||
|
||||
const getCurrentVariant = (): string => {
|
||||
const platformVariants = selectedVariants[selectedPlatform];
|
||||
return platformVariants?.[currentFramework] ?? 'server';
|
||||
const keys = getVariantKeys(selectedPlatform, currentFramework);
|
||||
const selectedVariant = platformVariants?.[currentFramework];
|
||||
if (selectedVariant) return selectedVariant;
|
||||
return keys[0] || '';
|
||||
};
|
||||
|
||||
const getCurrentCodeConfig = () => {
|
||||
// Get platform config - may be undefined if platform was switched to one not in this block
|
||||
const platformConfig = platforms[selectedPlatform] as typeof platforms[string] | undefined;
|
||||
if (!platformConfig) {
|
||||
return null;
|
||||
// Get platform config - fall back to first available platform if selected doesn't exist
|
||||
let platformToUse = selectedPlatform;
|
||||
if (!(platformToUse in platforms)) {
|
||||
// Selected platform doesn't exist in this code block, fall back to first available
|
||||
platformToUse = platformNames[0];
|
||||
if (!platformToUse) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const platformConfig = platforms[platformToUse];
|
||||
|
||||
// Get framework config - fall back to first available framework if selected doesn't exist
|
||||
let frameworkToUse = currentFramework;
|
||||
const availableFrameworks = Object.keys(platformConfig);
|
||||
if (!(frameworkToUse in platformConfig)) {
|
||||
// Selected framework doesn't exist, fall back to first available
|
||||
frameworkToUse = availableFrameworks[0];
|
||||
if (!frameworkToUse) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const config = platformConfig[frameworkToUse];
|
||||
|
||||
if (hasVariants(platformToUse, frameworkToUse)) {
|
||||
const keys = getVariantKeys(platformToUse, frameworkToUse);
|
||||
const platformVariants = selectedVariants[platformToUse];
|
||||
const variant = platformVariants?.[frameworkToUse] || keys[0] || '';
|
||||
return (config as { [key: string]: VariantConfig })[variant];
|
||||
}
|
||||
|
||||
// Get the config for the current framework - may be undefined
|
||||
const config = platformConfig[currentFramework] as typeof platformConfig[string] | undefined;
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (hasVariants(selectedPlatform, currentFramework)) {
|
||||
const variant = getCurrentVariant();
|
||||
return (config as { server: { code: string, language?: string, filename?: string }, client: { code: string, language?: string, filename?: string } })[variant];
|
||||
}
|
||||
|
||||
return config as { code: string, language?: string, filename?: string };
|
||||
return config as VariantConfig;
|
||||
};
|
||||
|
||||
const currentCodeConfig = getCurrentCodeConfig();
|
||||
@ -500,7 +516,7 @@ export function PlatformCodeblock({
|
||||
});
|
||||
};
|
||||
|
||||
const handleVariantChange = (variant: 'server' | 'client') => {
|
||||
const handleVariantChange = (variant: string) => {
|
||||
setSelectedVariants(prev => ({
|
||||
...prev,
|
||||
[selectedPlatform]: {
|
||||
@ -615,20 +631,27 @@ export function PlatformCodeblock({
|
||||
hasVariants(selectedPlatform, currentFramework) ? (
|
||||
<div className="mb-3 flex">
|
||||
<div className="inline-flex items-center gap-1 rounded-full border border-fd-border/60 bg-fd-muted/20 p-1">
|
||||
{(['server', 'client'] as const).map((variant) => (
|
||||
<button
|
||||
key={variant}
|
||||
onClick={() => handleVariantChange(variant)}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium transition-colors duration-150",
|
||||
getCurrentVariant() === variant
|
||||
? "bg-fd-background text-fd-foreground shadow-sm border border-fd-border"
|
||||
: "border border-transparent text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/40"
|
||||
)}
|
||||
>
|
||||
{variant.charAt(0).toUpperCase() + variant.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
{getVariantKeys(selectedPlatform, currentFramework).map((variant) => {
|
||||
// Get the filename for this variant to use as label
|
||||
const config = platforms[selectedPlatform][currentFramework] as { [key: string]: VariantConfig } | undefined;
|
||||
const variantConfig = config?.[variant];
|
||||
const label = variantConfig?.filename || variant.charAt(0).toUpperCase() + variant.slice(1);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={variant}
|
||||
onClick={() => handleVariantChange(variant)}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium transition-colors duration-150",
|
||||
getCurrentVariant() === variant
|
||||
? "bg-fd-background text-fd-foreground shadow-sm border border-fd-border"
|
||||
: "border border-transparent text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/40"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
) : undefined
|
||||
|
||||
Loading…
Reference in New Issue
Block a user