diff --git a/apps/www/app/code-examples.tsx b/apps/www/app/code-examples.tsx index 5462c6ff23..ac5127708c 100644 --- a/apps/www/app/code-examples.tsx +++ b/apps/www/app/code-examples.tsx @@ -1,7 +1,18 @@ "use client"; -import { Editor } from "@/components/analytics/analytics-bento"; import { PrimaryButton, SecondaryButton } from "@/components/button"; import { SectionTitle } from "@/components/section"; +import type { LangIconProps } from "@/components/svg/lang-icons"; +import { + CurlIcon, + ElixirIcon, + GoIcon, + JavaIcon, + PythonIcon, + RustIcon, + TSIcon, +} from "@/components/svg/lang-icons"; +import { CodeEditor } from "@/components/ui/code-editor"; +import { CopyCodeSnippetButton } from "@/components/ui/copy-code-button"; import { MeteorLines } from "@/components/ui/meteorLines"; import { cn } from "@/lib/utils"; import * as TabsPrimitive from "@radix-ui/react-tabs"; @@ -57,142 +68,6 @@ const editorTheme = { ], } satisfies PrismTheme; -type IconProps = { - active: boolean; -}; - -const JavaIcon: React.FC = ({ active }) => ( - - - - - - -); - -const ElixirIcon: React.FC = ({ active }) => ( - - - -); - -const RustIcon: React.FC = ({ active }) => ( - - - - - - -); - -const CurlIcon: React.FC = ({ active }) => ( - - - - - - - - - - - - -); - -const PythonIcon: React.FC = ({ active }) => ( - - - - - - - - - -); - -const TSIcon: React.FC = ({ active }) => ( - - - - - - - -); - -const GoIcon: React.FC = ({ active }) => ( - - - -); - const typescriptCodeBlock = `import { verifyKey } from '@unkey/api'; const { result, error } = await verifyKey({ @@ -503,7 +378,7 @@ public class APIController { type Framework = { name: string; - Icon: React.FC; + Icon: React.FC; codeBlock: string; editorLanguage: string; }; @@ -514,31 +389,31 @@ const languagesList = { name: "Typescript", Icon: TSIcon, codeBlock: typescriptCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, { name: "Next.js", Icon: TSIcon, codeBlock: nextJsCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, { name: "Nuxt", codeBlock: nuxtCodeBlock, Icon: TSIcon, - editorLanguage: "ts", + editorLanguage: "tsx", }, { name: "Hono", Icon: TSIcon, codeBlock: honoCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, { name: "Ratelimiting", Icon: TSIcon, codeBlock: tsRatelimitCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, ], Python: [ @@ -574,13 +449,13 @@ const languagesList = { name: "Verify key", Icon: JavaIcon, codeBlock: javaVerifyKeyCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, { name: "Create key", Icon: JavaIcon, codeBlock: javaCreateKeyCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, ], Elixir: [ @@ -588,7 +463,7 @@ const languagesList = { name: "Verify key", Icon: ElixirIcon, codeBlock: elixirCodeBlock, - editorLanguage: "ts", + editorLanguage: "tsx", }, ], Rust: [ @@ -644,7 +519,7 @@ type Props = { type Language = "Typescript" | "Python" | "Rust" | "Golang" | "Curl" | "Elixir" | "Java"; type LanguagesList = { name: Language; - Icon: React.FC; + Icon: React.FC; }; const languages = [ { name: "Typescript", Icon: TSIcon }, @@ -696,7 +571,6 @@ export const CodeExamples: React.FC = ({ className }) => { return currentFramework?.codeBlock || ""; } - const [copied, setCopied] = useState(false); return (
= ({ className }) => { setFramework={setFramework} />
- - + />
diff --git a/apps/www/components/analytics/analytics-bento.tsx b/apps/www/components/analytics/analytics-bento.tsx index f0bd5b417d..65d9444c2d 100644 --- a/apps/www/components/analytics/analytics-bento.tsx +++ b/apps/www/components/analytics/analytics-bento.tsx @@ -1,19 +1,35 @@ "use client"; +import type { LangIconProps } from "@/components/svg/lang-icons"; +import { CurlIcon, GoIcon, PythonIcon, RustIcon, TSIcon } from "@/components/svg/lang-icons"; +import { CopyCodeSnippetButton } from "@/components/ui/copy-code-button"; import { cn } from "@/lib/utils"; import { motion } from "framer-motion"; import { Wand2 } from "lucide-react"; -import { Highlight, type PrismTheme } from "prism-react-renderer"; +import type { PrismTheme } from "prism-react-renderer"; import { useState } from "react"; import { PrimaryButton } from "../button"; import { AnalyticsStars } from "../svg/analytics-stars"; import { WebAppLight } from "../svg/web-app-light"; +import { CodeEditor } from "../ui/code-editor"; -export const theme = { +const theme = { plain: { color: "#F8F8F2", backgroundColor: "#282A36", }, styles: [ + { + types: ["keyword"], + style: { + color: "#9D72FF", + }, + }, + { + types: ["function"], + style: { + color: "#FB3186", + }, + }, { types: ["string"], style: { @@ -32,28 +48,15 @@ export const theme = { color: "#FB3186", }, }, + { + types: ["comment"], + style: { + color: "#4D4D4D", + }, + }, ], } satisfies PrismTheme; -const codeBlock = `curl --request GET \\ - --url https://api.unkey.dev/v1/keys.getKey \\ - --header 'Authorization: ' - { - "apiId": "api_1234", - "createdAt": 123, - "deletedAt": 123, - "expires": 123, - "id": "key_1234", - "meta": { - "roles": [ - "admin", - "user" - ], - "stripeCustomerId": "cus_1234" - } - } -`; - export function AnalyticsBento() { const [showApi, toggleShowApi] = useState(false); @@ -76,7 +79,109 @@ export function AnalyticsBento() { ); } +type Language = { + name: string; + Icon: React.FC; + codeBlock: string; +}; + +type LanguageName = "TypeScript" | "Python" | "Rust" | "Go" | "cURL"; + +const curlCodeBlock = `curl --request GET \\ + --url https://api.unkey.dev/v1/keys.getKey?keyId=key_123 \\ + --header 'Authorization: Bearer ' +`; + +const tsCodeBlock = `import { Unkey } from "@unkey/api"; + +const unkey = new Unkey({ rootKey: "" }); + +const { result, error } = await unkey.keys.get({ keyId: "key_123" }); + +if ( error ) { + // handle network error +} + +// handle request +`; + +const pythonCodeBlock = `import asyncio +import os +import unkey + +async def main() -> None: + client = unkey.Client("") + await client.start() + + result = await client.keys.get_key("key_123") + + if result.is_ok: + data = result.unwrap() + print(data.id) + else: + print(result.unwrap_err()) + + await client.close() +`; + +const goCodeBlock = `package main + +import ( + "context" + "log" + + unkeygo "github.com/unkeyed/unkey-go" + "github.com/unkeyed/unkey-go/models/operations" +) + +func main() { + s := unkeygo.New( + unkeygo.WithSecurity(""), + ) + + ctx := context.Background() + res, err := s.Keys.GetKey(ctx, operations.GetKeyRequest{ + KeyID: "key_123", + }) + if err != nil { + log.Fatal(err) + } + if res.Key != nil { + // handle response + } +} +`; + +const rustCodeBlock = `use unkey::models::GetKeyRequest; +use unkey::Client; + +async fn get_key() { + let c = Client::new(""); + let req = GetKeyRequest::new("key_123"); + + match c.get_key(req).await { + Ok(res) => println!("{res:?}"), + Err(err) => eprintln!("{err:?}"), + } +} +`; + +const languagesList = { + cURL: { Icon: CurlIcon, name: "cURL", codeBlock: curlCodeBlock, editorLanguage: "tsx" }, + TypeScript: { Icon: TSIcon, name: "TypeScript", codeBlock: tsCodeBlock, editorLanguage: "tsx" }, + Python: { + Icon: PythonIcon, + name: "Python", + codeBlock: pythonCodeBlock, + editorLanguage: "python", + }, + Go: { Icon: GoIcon, name: "Go", codeBlock: goCodeBlock, editorLanguage: "go" }, + Rust: { Icon: RustIcon, name: "Rust", codeBlock: rustCodeBlock, editorLanguage: "rust" }, +}; + function AnalyticsApiView() { + const [language, setLanguage] = useState("cURL"); + return ( -
-
-
- -
cURL
-
-
-
- +
+ +
+ +
); } +function LanguageSwitcher({ + languages, + currentLanguage, + setLanguage, +}: { + languages: Language[]; + currentLanguage: LanguageName; + setLanguage: React.Dispatch>; +}) { + return ( +
+
+ {languages.map(({ Icon, name }) => ( + + ))} +
+
+ ); +} + function AnalyticsWebAppView() { function Tab({ backgroundColor, @@ -332,7 +478,7 @@ function AnalyticsWebAppView() { whileInView="visible" className="w-full overflow-x-hidden" > -
+
@@ -650,140 +796,6 @@ function AnalyticsWebAppView() { ); } -export function Editor({ - codeBlock, - language, - theme, -}: { codeBlock: string; language: string; theme?: PrismTheme }) { - return ( - - {({ tokens, getLineProps, getTokenProps }) => { - const amountLines = tokens.length; - const gutterPadLength = Math.max(String(amountLines).length, 2); - return ( -
-            {tokens.map((line, i) => {
-              const lineNumber = i + 1;
-              const paddedLineGutter = String(lineNumber).padStart(gutterPadLength, " ");
-              return (
-                // biome-ignore lint/suspicious/noArrayIndexKey: I got nothing better right now
-                
- {paddedLineGutter} - {line.map((token, key) => ( - - ))} -
- ); - })} -
- ); - }} -
- ); -} - -export function TerminalIcon({ className }: { className?: string }) { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - export function BentoText() { return (
diff --git a/apps/www/components/svg/lang-icons.tsx b/apps/www/components/svg/lang-icons.tsx new file mode 100644 index 0000000000..88c362df6f --- /dev/null +++ b/apps/www/components/svg/lang-icons.tsx @@ -0,0 +1,135 @@ +export type LangIconProps = { + active: boolean; +}; + +export const JavaIcon: React.FC = ({ active }) => ( + + + + + + +); + +export const ElixirIcon: React.FC = ({ active }) => ( + + + +); + +export const RustIcon: React.FC = ({ active }) => ( + + + + + + +); + +export const CurlIcon: React.FC = ({ active }) => ( + + + + + + + + + + + + +); + +export const PythonIcon: React.FC = ({ active }) => ( + + + + + + + + + +); + +export const TSIcon: React.FC = ({ active }) => ( + + + + + + + +); + +export const GoIcon: React.FC = ({ active }) => ( + + + +); diff --git a/apps/www/components/ui/code-editor.tsx b/apps/www/components/ui/code-editor.tsx new file mode 100644 index 0000000000..d6637d5e6c --- /dev/null +++ b/apps/www/components/ui/code-editor.tsx @@ -0,0 +1,36 @@ +import { Highlight, type PrismTheme } from "prism-react-renderer"; + +export function CodeEditor({ + codeBlock, + language, + theme, +}: { codeBlock: string; language: string; theme?: PrismTheme }) { + return ( + + {({ tokens, getLineProps, getTokenProps }) => { + const lineCount = tokens.length; + const gutterPadLength = Math.max(String(lineCount).length, 2); + return ( +
+            {tokens.map((line, i) => {
+              const lineNumber = i + 1;
+              const paddedLineGutter = String(lineNumber).padStart(gutterPadLength, " ");
+              return (
+                // biome-ignore lint/suspicious/noArrayIndexKey: I got nothing better right now
+                
+ {paddedLineGutter} + {line.map((token, key) => ( + + ))} +
+ ); + })} +
+ ); + }} +
+ ); +} diff --git a/apps/www/components/ui/copy-code-button.tsx b/apps/www/components/ui/copy-code-button.tsx new file mode 100644 index 0000000000..f2eb008dbd --- /dev/null +++ b/apps/www/components/ui/copy-code-button.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; + +type Props = { + textToCopy: string; + className?: string; +}; + +export function CopyCodeSnippetButton(props: Props) { + const [copied, setCopied] = useState(false); + + return ( + + ); +} + +function CheckmarkCircle() { + return ( + + + + + ); +} + +function CopyIcon() { + return ( + + + + + + + + + + ); +}