feat: cleaning up ai tools
20
vite/public/frameworks/Symbol Alternative.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-testid="geist-icon" height="16" stroke-linejoin="round" style="color:currentColor" viewBox="0 0 16 16" width="16"><g clip-path="url(#clip0_53_108)">
|
||||
<circle cx="8" cy="8" r="7.375" fill="black" stroke="var(--ds-gray-1000)" strokewidth="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.63 11V5" stroke="url(#paint0_linear_53_108vsxrmxu21)" strokewidth="1.25" stroke-miterlimit="1.41421"/>
|
||||
<path fill-rule="evenodd" cliprule="evenodd" d="M5.995 5.00087V5H4.745V11H5.995V6.96798L12.3615 14.7076C12.712 14.4793 13.0434 14.2242 13.353 13.9453L5.99527 5.00065L5.995 5.00087Z" fill="url(#paint1_linear_53_108vsxrmxu21)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_53_108vsxrmxu21" x1="11.13" y1="5" x2="11.13" y2="11" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="0.609375" stop-color="white" stop-opacity="0.57"/>
|
||||
<stop offset="0.796875" stop-color="white" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_53_108vsxrmxu21" x1="9.9375" y1="9.0625" x2="13.5574" y2="13.3992" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_53_108">
|
||||
<rect width="16" height="16" fill="red"/>
|
||||
</clipPath>
|
||||
</defs></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
vite/public/frameworks/better-auth.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
vite/public/frameworks/clerk.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
vite/public/frameworks/elysia.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
vite/public/frameworks/express.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
vite/public/frameworks/hono.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
vite/public/frameworks/nextjs.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
6
vite/public/frameworks/react-router.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="602" height="360" viewBox="0 0 602 360" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M481.36 180C481.36 196.572 474.639 211.572 463.757 222.42C452.875 233.28 437.845 240 421.24 240C404.635 240 389.605 246.708 378.735 257.568C367.853 268.428 361.12 283.428 361.12 300C361.12 316.572 354.399 331.572 343.517 342.42C332.635 353.28 317.605 360 301 360C284.395 360 269.365 353.28 258.495 342.42C247.613 331.572 240.88 316.572 240.88 300C240.88 283.428 247.613 268.428 258.495 257.568C269.365 246.708 284.395 240 301 240C317.605 240 332.635 233.28 343.517 222.42C354.399 211.572 361.12 196.572 361.12 180C361.12 146.856 334.21 120 301 120C284.395 120 269.365 113.28 258.495 102.42C247.613 91.572 240.88 76.572 240.88 60C240.88 43.428 247.613 28.428 258.495 17.568C269.365 6.708 284.395 0 301 0C334.21 0 361.12 26.856 361.12 60C361.12 76.572 367.853 91.572 378.735 102.42C389.605 113.28 404.635 120 421.24 120C454.45 120 481.36 146.856 481.36 180Z" fill="#F44250"/>
|
||||
<path d="M240.88 180C240.88 146.862 213.964 120 180.76 120C147.557 120 120.64 146.862 120.64 180C120.64 213.137 147.557 240 180.76 240C213.964 240 240.88 213.137 240.88 180Z" fill="#121212"/>
|
||||
<path d="M120.64 300C120.64 266.863 93.7234 240 60.52 240C27.3167 240 0.400024 266.863 0.400024 300C0.400024 333.138 27.3167 360 60.52 360C93.7234 360 120.64 333.138 120.64 300Z" fill="#121212"/>
|
||||
<path d="M601.6 300C601.6 266.863 574.683 240 541.48 240C508.277 240 481.36 266.863 481.36 300C481.36 333.138 508.277 360 541.48 360C574.683 360 601.6 333.138 601.6 300Z" fill="#121212"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
vite/public/frameworks/react.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
vite/public/frameworks/supabase.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
15
vite/public/frameworks/vite.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -24,13 +24,17 @@ TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> & {
|
||||
variant?: "default" | "onboarding";
|
||||
}
|
||||
>(({ className, variant = "default", ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-2 py-1 text-sm font-medium ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:text-primary hover:bg-stone-100 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-300 dark:data-[state=active]:bg-zinc-950 dark:data-[state=active]:text-zinc-50",
|
||||
className
|
||||
className,
|
||||
variant === "onboarding" &&
|
||||
"data-[state=active]:bg-stone-200 data-[state=active]:text-t2 data-[state=active]:font-medium"
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -21,13 +21,13 @@ export const CreateSecretKey = ({
|
||||
setApiKey: (apiKey: string) => void;
|
||||
number: number;
|
||||
}) => {
|
||||
let env = useEnv();
|
||||
let [apiKeyName, setApiKeyName] = useState("");
|
||||
let [apiCreated, setApiCreated] = useState(false);
|
||||
const env = useEnv();
|
||||
const [apiKeyName, setApiKeyName] = useState("");
|
||||
const [apiCreated, setApiCreated] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
let axiosInstance = useAxiosInstance({ env });
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
|
||||
const handleCreate = async () => {
|
||||
console.log("creating api key", apiKeyName ? apiKeyName : name);
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import { StepHeader } from "./StepHeader";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
import { ExternalLink, Download } from "lucide-react";
|
||||
import { ExternalLink, Download, Info } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
} from "@/components/ui/accordion";
|
||||
import FieldLabel from "@/components/general/modal-components/FieldLabel";
|
||||
|
||||
export const AITools = () => {
|
||||
// MCP configuration for Autumn
|
||||
@@ -33,7 +40,41 @@ export const AITools = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<StepHeader number={1} title="Add to AI tools" />
|
||||
{/* <StepHeader number={1} title="Add to AI tools" /> */}
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full"
|
||||
defaultValue="item-1"
|
||||
>
|
||||
<AccordionItem value="item-1" className="border">
|
||||
<AccordionTrigger className="bg-stone-100 p-2">
|
||||
<div className="flex items-center gap-2 text-t2">
|
||||
<Info size={14} />
|
||||
Add Autumn to your AI tools
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance text-t3 p-4 bg-white">
|
||||
<div>
|
||||
<FieldLabel>Cursor</FieldLabel>
|
||||
<Button
|
||||
onClick={handleCursorInstall}
|
||||
variant="outline"
|
||||
className="w-fit border-none bg-zinc-800 hover:bg-zinc-900 text-white rounded-xs hover:text-white"
|
||||
startIcon={
|
||||
<img
|
||||
src="/cursor.png"
|
||||
className="w-4 h-4 mr-1"
|
||||
alt="Cursor"
|
||||
/>
|
||||
}
|
||||
>
|
||||
Open in Cursor
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<p className="text-sm text-t3">
|
||||
If you're using Cursor or Claude Code, you can install our MCP server to
|
||||
use AI to integrate Autumn.
|
||||
@@ -41,16 +82,6 @@ export const AITools = () => {
|
||||
|
||||
{/* One-click install for Cursor */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleCursorInstall}
|
||||
variant="outline"
|
||||
className="w-fit text-t2"
|
||||
startIcon={
|
||||
<img src="/cursor.png" className="w-4 h-4 mr-1" alt="Cursor" />
|
||||
}
|
||||
>
|
||||
Open in Cursor
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
|
||||
@@ -1,38 +1,35 @@
|
||||
import { AITools } from "./AITools";
|
||||
import { SelectStack } from "./SelectStack";
|
||||
import { useState } from "react";
|
||||
|
||||
import { IntegrateContext } from "./IntegrateContext";
|
||||
import { notNullish } from "@/utils/genUtils";
|
||||
import { Install } from "./integration-steps/Install";
|
||||
import { AutumnHandler } from "./integration-steps/AutumnHandler";
|
||||
import { parseAsString, parseAsJson, useQueryStates } from "nuqs";
|
||||
import {
|
||||
parseAsString,
|
||||
parseAsJson,
|
||||
useQueryStates,
|
||||
parseAsBoolean,
|
||||
} from "nuqs";
|
||||
import { AddAutumnProvider } from "./integration-steps/AddAutumnProvider";
|
||||
import { CheckoutPricingTable } from "./integration-steps/CheckoutPricingTable";
|
||||
import { EnvStep } from "./integration-steps/EnvStep";
|
||||
|
||||
export default function IntegrateAutumn() {
|
||||
const [stack, setStack] = useState<{
|
||||
frontend: string;
|
||||
backend: string;
|
||||
auth: string;
|
||||
customerType: string;
|
||||
}>({
|
||||
frontend: "",
|
||||
backend: "",
|
||||
auth: "",
|
||||
customerType: "",
|
||||
});
|
||||
|
||||
const [queryStates, setQueryStates] = useQueryStates({
|
||||
reactTypescript: parseAsBoolean.withDefault(true),
|
||||
frontend: parseAsString.withDefault(""),
|
||||
backend: parseAsString.withDefault(""),
|
||||
auth: parseAsString.withDefault(""),
|
||||
customerType: parseAsString.withDefault(""),
|
||||
});
|
||||
|
||||
const stackSelected = Object.values(stack).every(notNullish);
|
||||
const stackSelected = Object.values(queryStates).every(notNullish);
|
||||
|
||||
return (
|
||||
<IntegrateContext.Provider value={{ queryStates, setQueryStates }}>
|
||||
<div className="w-full h-full p-10 flex flex-col items-center justify-start">
|
||||
<div className="max-w-[700px] w-full flex flex-col gap-6">
|
||||
<div className="max-w-[600px] w-full flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-xl">Integrate Autumn</p>
|
||||
<p className="text-t3">
|
||||
@@ -44,13 +41,17 @@ export default function IntegrateAutumn() {
|
||||
<div className="flex flex-col gap-8 pb-40">
|
||||
<AITools />
|
||||
<SelectStack />
|
||||
{stackSelected && (
|
||||
{stackSelected && queryStates.reactTypescript && (
|
||||
<>
|
||||
<EnvStep />
|
||||
<Install />
|
||||
<AutumnHandler />
|
||||
<AddAutumnProvider />
|
||||
<CheckoutPricingTable />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="flex flex-col gap-4">
|
||||
<StepHeader number={2} title="Add your secret key" />
|
||||
<p className="text-md text-t3">
|
||||
|
||||
@@ -8,176 +8,78 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useIntegrateContext } from "./IntegrateContext";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Building,
|
||||
CircleUserRound,
|
||||
Code,
|
||||
Fingerprint,
|
||||
Info,
|
||||
InfoIcon,
|
||||
ScanFace,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { SelectFrameworks } from "./select-stack/SelectFrameworks";
|
||||
import { InfoBox } from "./components/InfoBox";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export const SelectStack = () => {
|
||||
const { queryStates, setQueryStates } = useIntegrateContext();
|
||||
|
||||
const frontendOptions = [
|
||||
{ value: "nextjs", label: "Next.js" },
|
||||
{ value: "vite", label: "Vite SPA" },
|
||||
{ value: "tanstack", label: "Tanstack Start" },
|
||||
{ value: "non-react", label: "Non-React" },
|
||||
];
|
||||
|
||||
const backendOptions = [
|
||||
{ value: "nextjs", label: "Next.js" },
|
||||
{ value: "react_router", label: "React Router 7" },
|
||||
{ value: "hono", label: "Hono" },
|
||||
{ value: "express", label: "Express" },
|
||||
{ value: "elysia", label: "Elysia" },
|
||||
{ value: "supabase", label: "Supabase" },
|
||||
{ value: "convex", label: "Convex" },
|
||||
];
|
||||
|
||||
const authOptions = [
|
||||
{ value: "better_auth", label: "Better Auth" },
|
||||
{ value: "supabase", label: "Supabase Auth" },
|
||||
{ value: "clerk", label: "Clerk" },
|
||||
{ value: "other", label: "Other" },
|
||||
];
|
||||
|
||||
const customerOptions = [
|
||||
{ value: "user", label: "Users" },
|
||||
{ value: "org", label: "Organizations" },
|
||||
// { value: "other", label: "Other (eg. Projects / Workspaces)" },
|
||||
];
|
||||
|
||||
const tabClassName = `rounded-xs h-8 data-[state=active]:bg-stone-100 data-[state=active]:text-t2 data-[state=active]:shadow-inner data-[state=active]:border`;
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<StepHeader number={2} title="Select your stack" />
|
||||
<p className="text-sm text-t3">
|
||||
Help us customize the integration guide for your specific tech stack.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-[600px]">
|
||||
{/* Frontend Framework */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Frontend</label>
|
||||
<Select
|
||||
value={queryStates.frontend}
|
||||
onValueChange={(value) =>
|
||||
setQueryStates({ ...queryStates, frontend: value })
|
||||
}
|
||||
<div className="flex flex-col gap-2">
|
||||
<StepHeader number={2} title="Select your stack" />
|
||||
<p className="text-sm text-t3">
|
||||
Help us customize the integration guide for your specific tech stack.
|
||||
Click{" "}
|
||||
<span
|
||||
onClick={() => {
|
||||
setQueryStates({ ...queryStates, reactTypescript: false });
|
||||
}}
|
||||
className="underline cursor-pointer"
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select framework" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{frontendOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Backend Framework */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Backend</label>
|
||||
<Select
|
||||
value={queryStates.backend}
|
||||
onValueChange={(value) =>
|
||||
setQueryStates({ ...queryStates, backend: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select backend" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{backendOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Auth Provider */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Auth Provider</label>
|
||||
<Select
|
||||
value={queryStates.auth}
|
||||
onValueChange={(value) =>
|
||||
setQueryStates({ ...queryStates, auth: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select auth provider" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{authOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Customer Type */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">
|
||||
Your customers are
|
||||
</label>
|
||||
<Select
|
||||
value={queryStates.customerType}
|
||||
onValueChange={(value) =>
|
||||
setQueryStates({ ...queryStates, customerType: value })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select customer type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{customerOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
here
|
||||
</span>{" "}
|
||||
if you're not using a React + Typescript backend stack.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* {(stack.frontend ||
|
||||
stack.backend ||
|
||||
stack.auth ||
|
||||
stack.customerType) && (
|
||||
<div className="mt-4 p-3 bg-zinc-50 dark:bg-zinc-900 rounded-md border border-zinc-200 dark:border-zinc-800">
|
||||
<h4 className="text-sm font-medium text-t2 mb-2">Selected Stack:</h4>
|
||||
<div className="text-xs text-t3 space-y-1">
|
||||
{stack.frontend && (
|
||||
<p>
|
||||
<strong>Frontend:</strong>{" "}
|
||||
{frontendOptions.find((f) => f.value === stack.frontend)?.label}
|
||||
</p>
|
||||
)}
|
||||
{stack.backend && (
|
||||
<p>
|
||||
<strong>Backend:</strong>{" "}
|
||||
{backendOptions.find((b) => b.value === stack.backend)?.label}
|
||||
</p>
|
||||
)}
|
||||
{stack.auth && (
|
||||
<p>
|
||||
<strong>Auth:</strong>{" "}
|
||||
{authOptions.find((a) => a.value === stack.auth)?.label}
|
||||
</p>
|
||||
)}
|
||||
{stack.customerType && (
|
||||
<p>
|
||||
<strong>Customers:</strong>{" "}
|
||||
{
|
||||
customerOptions.find((c) => c.value === stack.customerType)
|
||||
?.label
|
||||
}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
{queryStates.reactTypescript ? (
|
||||
<SelectFrameworks />
|
||||
) : (
|
||||
<>
|
||||
<InfoBox>
|
||||
<p className="text-t2 text-sm">
|
||||
This onboarding guide shows how to set up Autumn using our
|
||||
frontend components / hooks on React and server-side framework
|
||||
adaptors.
|
||||
<br />
|
||||
<br />
|
||||
In your case, you should integrate with Autumn's API directly on
|
||||
the backend. We have SDKs for Typescript and Python. Learn how to
|
||||
do so{" "}
|
||||
<a
|
||||
className="text-t2 font-medium underline"
|
||||
href="https://docs.useautumn.com/setup/backend"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</InfoBox>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-fit"
|
||||
onClick={() => {
|
||||
setQueryStates({ ...queryStates, reactTypescript: true });
|
||||
}}
|
||||
>
|
||||
Learn how to integrate Autumn with React + Typescript
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
15
vite/src/views/onboarding2/integrate/StackEnums.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export enum Backend {
|
||||
Nextjs = "nextjs",
|
||||
ReactRouter = "react-router",
|
||||
Express = "express",
|
||||
Elysia = "elysia",
|
||||
Hono = "hono",
|
||||
Other = "other",
|
||||
}
|
||||
|
||||
export enum Frontend {
|
||||
Nextjs = "nextjs",
|
||||
ReactRouter = "react-router",
|
||||
Vite = "vite",
|
||||
Other = "other",
|
||||
}
|
||||
@@ -3,14 +3,14 @@ export const StepHeader = ({
|
||||
title,
|
||||
}: {
|
||||
number: number;
|
||||
title: string;
|
||||
title: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-6 h-6 border-1 rounded-full bg-gradient-to-b from-stone-100 to-stone-100 text-primary font-bold flex items-center justify-center text-sm">
|
||||
{number}
|
||||
</div>
|
||||
<p className="text-md">{title}</p>
|
||||
<div className="text-md">{title}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const CodeSpan = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<span className="bg-stone-200 font-mono text-t2 px-1 py-0.5 rounded-md">
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
27
vite/src/views/onboarding2/integrate/components/InfoBox.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Info } from "lucide-react";
|
||||
|
||||
export const InfoBox = ({
|
||||
classNames,
|
||||
children,
|
||||
}: {
|
||||
classNames?: {
|
||||
infoIcon?: string;
|
||||
infoBox?: string;
|
||||
};
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-stone-100 border p-4 text-t2 flex gap-2",
|
||||
classNames?.infoBox
|
||||
)}
|
||||
>
|
||||
<div className={cn("pt-0.5 mr-1", classNames?.infoIcon)}>
|
||||
<Info size={14} />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
import CodeBlock from "@/views/onboarding/components/CodeBlock";
|
||||
import { CodeSpan } from "../components/CodeSpan";
|
||||
import { StepHeader } from "../StepHeader";
|
||||
import { useIntegrateContext } from "../IntegrateContext";
|
||||
import { Backend, Frontend } from "../StackEnums";
|
||||
import { InfoBox } from "../components/InfoBox";
|
||||
|
||||
const nextjsAutumnProvider = ({
|
||||
includeBackendUrl,
|
||||
}: {
|
||||
includeBackendUrl: boolean;
|
||||
}) => {
|
||||
const backendUrlStr = includeBackendUrl
|
||||
? ` backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL}`
|
||||
: "";
|
||||
return `// layout.tsx
|
||||
|
||||
import { AutumnProvider, PricingTable } from "autumn-js/react";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<AutumnProvider${backendUrlStr}>
|
||||
{children}
|
||||
</AutumnProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}`;
|
||||
};
|
||||
|
||||
const rr7AutumnProvider = ({
|
||||
includeBackendUrl,
|
||||
}: {
|
||||
includeBackendUrl: boolean;
|
||||
}) => {
|
||||
const backendUrlStr = includeBackendUrl
|
||||
? ` backendUrl={import.meta.env.VITE_BACKEND_URL}`
|
||||
: "";
|
||||
return `// root.tsx
|
||||
|
||||
import { AutumnProvider } from "autumn-js/react";
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
<AutumnProvider${backendUrlStr}>{children}</AutumnProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}`;
|
||||
};
|
||||
|
||||
const viteAutumnProvider = () => {
|
||||
return `// main.tsx
|
||||
import { AutumnProvider } from "autumn-js/react";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<AutumnProvider backendUrl={import.meta.env.VITE_BACKEND_URL}>
|
||||
<App />
|
||||
</AutumnProvider>
|
||||
);`;
|
||||
};
|
||||
|
||||
const otherAutumnProvider = () => {
|
||||
return `// main.tsx
|
||||
import { AutumnProvider } from "autumn-js/react";
|
||||
|
||||
// 1. Simply wrap the root of your app in the AutumnProvider component
|
||||
// 2. Pass in your server's URL to the backendUrl prop
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<Layout>
|
||||
<AutumnProvider backendUrl={import.meta.env.VITE_BACKEND_URL}>
|
||||
<App />
|
||||
</AutumnProvider>
|
||||
</Layout>
|
||||
);`;
|
||||
};
|
||||
|
||||
const getSnippet = (queryStates: any) => {
|
||||
const { backend, frontend } = queryStates;
|
||||
if (frontend === Frontend.Nextjs) {
|
||||
return nextjsAutumnProvider({
|
||||
includeBackendUrl: backend !== Backend.Nextjs,
|
||||
});
|
||||
} else if (frontend === Frontend.ReactRouter) {
|
||||
return rr7AutumnProvider({
|
||||
includeBackendUrl: backend === Backend.Nextjs,
|
||||
});
|
||||
} else if (frontend === Frontend.Vite) {
|
||||
return viteAutumnProvider();
|
||||
} else {
|
||||
return otherAutumnProvider();
|
||||
}
|
||||
};
|
||||
|
||||
export const AddAutumnProvider = () => {
|
||||
const { queryStates } = useIntegrateContext();
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<StepHeader
|
||||
number={4}
|
||||
title={
|
||||
<p>
|
||||
Wrap your React app in <CodeSpan>{"<AutumnProvider />"}</CodeSpan>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<p className="text-t2 text-sm">
|
||||
This allows you to use our React hooks and components in your app. If
|
||||
your server URL is different to your client, you will need to pass in
|
||||
the backend URL as a prop.
|
||||
</p>
|
||||
<CodeBlock
|
||||
snippets={[
|
||||
{
|
||||
title: "React App",
|
||||
language: "javascript",
|
||||
displayLanguage: "javascript",
|
||||
content: getSnippet(queryStates),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{queryStates.auth === "supabase" && (
|
||||
<InfoBox classNames={{ infoIcon: "!pt-1.5" }}>
|
||||
<p className="text-t2 text-sm leading-5">
|
||||
The examples above assume that Supabase auth is implemented using
|
||||
server-side cookie authentication, following the guide{" "}
|
||||
<a
|
||||
className="text-t2 font-medium underline"
|
||||
href="https://supabase.com/docs/guides/auth/server-side/creating-a-client"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
<br />
|
||||
<br />
|
||||
If you need to set <CodeSpan>{"Bearer <auth_token>"}</CodeSpan> in
|
||||
your request headers to authenticate, you can use the{" "}
|
||||
<CodeSpan>getBearerToken</CodeSpan> prop in the{" "}
|
||||
<CodeSpan>AutumnProvider</CodeSpan> component.
|
||||
</p>
|
||||
</InfoBox>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -12,22 +12,36 @@ import {
|
||||
nextjsSupabaseOrg,
|
||||
nextjsSupabaseUser,
|
||||
} from "./snippets/nextjsHandler";
|
||||
import { rr7BetterAuth, rr7Clerk, rr7Supabase } from "./snippets/rr7Handler";
|
||||
import {
|
||||
rr7BetterAuth,
|
||||
rr7Clerk,
|
||||
rr7Other,
|
||||
rr7Supabase,
|
||||
} from "./snippets/rr7Handler";
|
||||
import {
|
||||
honoBetterAuth,
|
||||
honoClerk,
|
||||
honoSupabase,
|
||||
honoOther,
|
||||
} from "./snippets/honoHandler";
|
||||
import {
|
||||
expressBetterAuth,
|
||||
expressClerk,
|
||||
expressOther,
|
||||
expressSupabase,
|
||||
} from "./snippets/expressHandler";
|
||||
import { elysiaBetterAuth } from "./snippets/elysiaHandler";
|
||||
import {
|
||||
elysiaBetterAuth,
|
||||
elysiaClerk,
|
||||
elysiaOther,
|
||||
} from "./snippets/elysiaHandler";
|
||||
import { general } from "./snippets/general";
|
||||
import { CodeSpan } from "../components/CodeSpan";
|
||||
import { Backend } from "../StackEnums";
|
||||
|
||||
const snippet = () => {
|
||||
return {
|
||||
nextjs: {
|
||||
[Backend.Nextjs]: {
|
||||
["better_auth"]: {
|
||||
user: nextjsBetterAuthUser,
|
||||
org: nextjsBetterAuthOrg,
|
||||
@@ -45,7 +59,7 @@ const snippet = () => {
|
||||
org: nextjsOther,
|
||||
},
|
||||
},
|
||||
["react_router"]: {
|
||||
[Backend.ReactRouter]: {
|
||||
["better_auth"]: {
|
||||
user: rr7BetterAuth("user"),
|
||||
org: rr7BetterAuth("org"),
|
||||
@@ -58,8 +72,12 @@ const snippet = () => {
|
||||
user: rr7Clerk("user"),
|
||||
org: rr7Clerk("org"),
|
||||
},
|
||||
["other"]: {
|
||||
user: rr7Other("user"),
|
||||
org: rr7Other("org"),
|
||||
},
|
||||
},
|
||||
["hono"]: {
|
||||
[Backend.Hono]: {
|
||||
["clerk"]: {
|
||||
user: honoClerk("user"),
|
||||
org: honoClerk("org"),
|
||||
@@ -72,8 +90,12 @@ const snippet = () => {
|
||||
user: honoBetterAuth("user"),
|
||||
org: honoBetterAuth("org"),
|
||||
},
|
||||
["other"]: {
|
||||
user: honoOther("user"),
|
||||
org: honoOther("org"),
|
||||
},
|
||||
},
|
||||
["express"]: {
|
||||
[Backend.Express]: {
|
||||
["better_auth"]: {
|
||||
user: expressBetterAuth("user"),
|
||||
org: expressBetterAuth("org"),
|
||||
@@ -86,12 +108,24 @@ const snippet = () => {
|
||||
user: expressSupabase("user"),
|
||||
org: expressSupabase("org"),
|
||||
},
|
||||
["other"]: {
|
||||
user: expressOther("user"),
|
||||
org: expressOther("org"),
|
||||
},
|
||||
},
|
||||
["elysia"]: {
|
||||
[Backend.Elysia]: {
|
||||
["better_auth"]: {
|
||||
user: elysiaBetterAuth("user"),
|
||||
org: elysiaBetterAuth("org"),
|
||||
},
|
||||
["clerk"]: {
|
||||
user: elysiaClerk("user"),
|
||||
org: elysiaClerk("org"),
|
||||
},
|
||||
["other"]: {
|
||||
user: elysiaOther("user"),
|
||||
org: elysiaOther("org"),
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
};
|
||||
@@ -106,21 +140,25 @@ export const AutumnHandler = () => {
|
||||
|
||||
const templates = snippet();
|
||||
|
||||
console.log("Backend Lang", backendLang);
|
||||
console.log("Auth Provider", authProvider);
|
||||
let template = templates[backendLang]?.[authProvider]?.[customerType] || "";
|
||||
|
||||
const template =
|
||||
templates[backendLang]?.[authProvider]?.[customerType] || "";
|
||||
template = template.trim("\n");
|
||||
|
||||
console.log("Template", template);
|
||||
if (!template) {
|
||||
return general();
|
||||
}
|
||||
|
||||
// Trim \n from the template (only left and right)
|
||||
return template.trim("\n");
|
||||
return template;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<StepHeader number={3} title="Install autumn-js" />
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<StepHeader number={3} title="Mount Autumn handler" />
|
||||
<p className="text-t2 text-sm">
|
||||
<CodeSpan>autumnHandler</CodeSpan> mounts routes on the{" "}
|
||||
<CodeSpan>/api/autumn/*</CodeSpan> paths which allows our React hooks
|
||||
and components to interact with the Autumn API directly.
|
||||
</p>
|
||||
|
||||
<CodeBlock
|
||||
snippets={[
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import CodeBlock from "@/views/onboarding/components/CodeBlock";
|
||||
import { CodeSpan } from "../components/CodeSpan";
|
||||
import { StepHeader } from "../StepHeader";
|
||||
import { Frontend } from "../StackEnums";
|
||||
import { useIntegrateContext } from "../IntegrateContext";
|
||||
import { InfoBox } from "../components/InfoBox";
|
||||
|
||||
const nextjsSnippet = () => {
|
||||
return `import { PricingTable } from "autumn-js/react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="h-screen w-screen flex justify-center items-center p-10">
|
||||
<div className="w-full max-w-[800px]">
|
||||
<PricingTable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}`;
|
||||
};
|
||||
|
||||
const getSnippet = (queryStates: any) => {
|
||||
return nextjsSnippet();
|
||||
};
|
||||
|
||||
export const CheckoutPricingTable = () => {
|
||||
const { queryStates } = useIntegrateContext();
|
||||
return (
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<StepHeader
|
||||
number={6}
|
||||
title={
|
||||
<p>
|
||||
Drop in <CodeSpan>{"<PricingTable />"}</CodeSpan>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<p className="text-t2 text-sm">
|
||||
Display a pricing table with the plans you have created and let your
|
||||
customers choose a plan.
|
||||
</p>
|
||||
<CodeBlock
|
||||
snippets={[
|
||||
{
|
||||
title: "React App",
|
||||
language: "javascript",
|
||||
displayLanguage: "javascript",
|
||||
content: getSnippet(queryStates),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<InfoBox classNames={{ infoIcon: "!pt-1.5" }}>
|
||||
<p className="text-t2 text-sm leading-5">
|
||||
{
|
||||
"Our <PricingTable /> component is completely customisable by installing it as a shadcn component. "
|
||||
}
|
||||
Learn how to do so{" "}
|
||||
<a
|
||||
className="text-t2 font-medium underline"
|
||||
href="https://docs.useautumn.com/setup/shadcn"
|
||||
target="_blank"
|
||||
>
|
||||
here
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
import { DevContext } from "@/views/developer/DevContext";
|
||||
|
||||
import Step from "@/components/general/OnboardingStep";
|
||||
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { DevService } from "@/services/DevService";
|
||||
import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CheckIcon, CopyIcon, PlusIcon } from "lucide-react";
|
||||
|
||||
export const CreateSecretKey = ({
|
||||
apiKey,
|
||||
setApiKey,
|
||||
}: {
|
||||
apiKey: string;
|
||||
setApiKey: (apiKey: string) => void;
|
||||
}) => {
|
||||
const env = useEnv();
|
||||
// const [apiKeyName, setApiKeyName] = useState("");
|
||||
const [apiCreated, setApiCreated] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
|
||||
const handleCreate = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { api_key } = await DevService.createAPIKey(axiosInstance, {
|
||||
name: "Autumn Onboarding",
|
||||
});
|
||||
|
||||
setApiKey(api_key);
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
toast.error("Failed to create API key");
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DevContext.Provider
|
||||
value={{
|
||||
mutate: () => {},
|
||||
onboarding: true,
|
||||
apiCreated,
|
||||
setApiCreated,
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{apiKey ? (
|
||||
<div className="flex gap-2">
|
||||
<Input value={apiKey} disabled className="w-[600px]" />
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="text-xs text-t3 flex gap-2 rounded-md shadow-none"
|
||||
endIcon={
|
||||
copied ? <CheckIcon size={12} /> : <CopyIcon size={12} />
|
||||
}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1000);
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
{/* <Input
|
||||
placeholder="Secret API Key Name"
|
||||
className="w-full"
|
||||
value={apiKeyName}
|
||||
disabled={apiCreated}
|
||||
onChange={(e) => setApiKeyName(e.target.value)}
|
||||
/> */}
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
isLoading={loading}
|
||||
variant="outline"
|
||||
startIcon={<PlusIcon size={14} />}
|
||||
>
|
||||
Create Secret Key
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DevContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
/* <div className="border rounded-sm px-2 py-1">
|
||||
{env === AppEnv.Sandbox ? (
|
||||
<CopyPublishableKey
|
||||
type="Sandbox"
|
||||
value={productData?.org?.test_pkey}
|
||||
/>
|
||||
) : (
|
||||
<CopyPublishableKey
|
||||
type="Production"
|
||||
value={productData?.org?.live_pkey}
|
||||
/>
|
||||
)}
|
||||
</div> */
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import CodeBlock from "@/views/onboarding/components/CodeBlock";
|
||||
import { CodeSpan } from "../components/CodeSpan";
|
||||
import { StepHeader } from "../StepHeader";
|
||||
import { useState } from "react";
|
||||
import { CreateSecretKey } from "./CreateSecretKey";
|
||||
|
||||
export const EnvStep = () => {
|
||||
const [apiKey, setApiKey] = useState("");
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<StepHeader
|
||||
number={2}
|
||||
title={
|
||||
<p>
|
||||
Add the Autumn secret key to your <CodeSpan>{".env"}</CodeSpan>{" "}
|
||||
file
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CreateSecretKey apiKey={apiKey} setApiKey={setApiKey} />
|
||||
|
||||
<CodeBlock
|
||||
snippets={[
|
||||
{
|
||||
title: ".env",
|
||||
language: "bash",
|
||||
displayLanguage: "bash",
|
||||
content: `AUTUMN_SECRET_KEY=${apiKey || "am_sk_12345"}`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ export const elysiaBetterAuth = (customerType: "user" | "org") => {
|
||||
return `import { autumnHandler } from "autumn-js/elysia";
|
||||
import { auth } from "./auth";
|
||||
|
||||
const app = new Elysia({ adapter: node() })
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
.mount(auth.handler)
|
||||
.use(
|
||||
@@ -19,8 +19,60 @@ ${betterAuthSnippet(customerType, "context.headers", 4)}
|
||||
`;
|
||||
};
|
||||
|
||||
// export const elysiaOther = (customerType: "user" | "org") => {
|
||||
// return `
|
||||
export const elysiaClerk = (customerType: "user" | "org") => {
|
||||
return `import { clerkPlugin } from "elysia-clerk";
|
||||
import { autumnHandler } from "autumn-js/backend";
|
||||
|
||||
// `;
|
||||
// };
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
.use(clerkPlugin())
|
||||
.all("*", async (ctx: any) => {
|
||||
console.log("Request received");
|
||||
})
|
||||
.all("/api/autumn/*", async (ctx: any) => {
|
||||
const auth = ctx.auth();
|
||||
|
||||
let body = null;
|
||||
if (ctx.request.method !== "GET") {
|
||||
body = await ctx.request.json();
|
||||
}
|
||||
|
||||
const { statusCode, response } = await autumnHandler({
|
||||
customerId: ${customerType === "user" ? "auth.userId" : "auth.orgId"},
|
||||
customerData: { name: "", email: "" },
|
||||
request: {
|
||||
url: ctx.request.url,
|
||||
method: ctx.request.method,
|
||||
body: body,
|
||||
},
|
||||
});
|
||||
|
||||
ctx.set.status = statusCode;
|
||||
return response;
|
||||
})
|
||||
.listen(8000);`;
|
||||
};
|
||||
|
||||
export const elysiaOther = (customerType: "user" | "org") => {
|
||||
return `import { autumnHandler } from "autumn-js/elysia";
|
||||
import { auth } from "./auth";
|
||||
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
.mount(auth.handler)
|
||||
.use(
|
||||
autumnHandler({
|
||||
identify: async (context) => {
|
||||
const customerId = "your_customer_id"; // Authenticate and get customer ID from your DB
|
||||
|
||||
return {
|
||||
customerId,
|
||||
customerData: { name: "", email: "" },
|
||||
};
|
||||
},
|
||||
})
|
||||
)
|
||||
.listen(8000);
|
||||
|
||||
`;
|
||||
};
|
||||
|
||||
@@ -46,3 +46,22 @@ app.use(
|
||||
})
|
||||
);`;
|
||||
};
|
||||
|
||||
export const expressOther = (customerType: "user" | "org") => {
|
||||
return `import { autumnHandler } from "autumn-js/express";
|
||||
|
||||
app.use(express.json()); // need to parse request body before autumnHandler
|
||||
app.use(
|
||||
"/api/autumn",
|
||||
autumnHandler({
|
||||
identify: async (req, res) => {
|
||||
const customerId = "your_customer_id"; // Get customer id from your database
|
||||
|
||||
return {
|
||||
customerId,
|
||||
customerData: { name: "", email: "" },
|
||||
};
|
||||
},
|
||||
})
|
||||
);`;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
export const general = () => {
|
||||
return `import { autumnHandler } from "autumn-js/backend";
|
||||
|
||||
// 1. autumnHandler takes in request properties and returns a response
|
||||
// 2. Simply mount the handler onto the /api/autumn/* path in your backend
|
||||
// 3. Call autumnHandler and pass in the required parameters
|
||||
// 4. Return the response from the autumnHandler
|
||||
|
||||
// Example using autumnHandler with Hono & Clerk
|
||||
import { autumnHandler } from "autumn-js/backend";
|
||||
import { clerkMiddleware, getAuth } from "@hono/clerk-auth";
|
||||
|
||||
app.use("*", clerkMiddleware());
|
||||
app.use(
|
||||
"/api/autumn/*",
|
||||
async (c) => {
|
||||
const auth = getAuth(c);
|
||||
|
||||
if (!auth?.userId) {
|
||||
return c.json({ message: "Unauthorized" }, 401);
|
||||
}
|
||||
|
||||
let body = null;
|
||||
if (c.req.method !== "GET") {
|
||||
body = await c.req.json();
|
||||
}
|
||||
|
||||
const { statusCode, response } = await autumnHandler({
|
||||
customerId: auth.userId,
|
||||
customerData: { name: "", email: "" },
|
||||
request: {
|
||||
url: c.req.url,
|
||||
method: c.req.method,
|
||||
body: body,
|
||||
},
|
||||
});
|
||||
|
||||
return c.json(response, statusCode);
|
||||
}
|
||||
);`;
|
||||
};
|
||||
@@ -104,3 +104,23 @@ ${betterAuthSnippet(customerType, "c.req.raw.headers", 3)}
|
||||
})
|
||||
);`;
|
||||
};
|
||||
export const honoOther = (customerType: "user" | "org") => {
|
||||
return `// index.ts
|
||||
|
||||
import { autumnHandler } from "autumn-js/hono";
|
||||
import { auth } from "@/lib/auth"
|
||||
|
||||
app.use(
|
||||
"/api/autumn/*",
|
||||
autumnHandler({
|
||||
identify: async (c: Context) => {
|
||||
const customerId = "your_customer_id"; // Get customer id from your database
|
||||
|
||||
return {
|
||||
customerId,
|
||||
customerData: { name: "", email: "" },
|
||||
};
|
||||
},
|
||||
})
|
||||
);`;
|
||||
};
|
||||
|
||||
@@ -50,8 +50,8 @@ export const { GET, POST } = autumnHandler({
|
||||
export const nextjsSupabaseUser = `
|
||||
// app/api/autumn/[...all]/route.ts
|
||||
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { autumnHandler } from "autumn-js/next";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export const { GET, POST } = autumnHandler({
|
||||
identify: async () => {
|
||||
@@ -75,8 +75,8 @@ export const { GET, POST } = autumnHandler({
|
||||
export const nextjsSupabaseOrg = `
|
||||
// app/api/autumn/[...all]/route.ts
|
||||
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { autumnHandler } from "autumn-js/next";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export const { GET, POST } = autumnHandler({
|
||||
identify: async () => {
|
||||
@@ -150,13 +150,10 @@ import { autumnHandler } from "autumn-js/next";
|
||||
|
||||
export const { GET, POST } = autumnHandler({
|
||||
identify: async (request) => {
|
||||
|
||||
// Authenticate the request and get the customer ID
|
||||
const customerId = "customer_id";
|
||||
|
||||
// Authenticate the request and get the customer ID
|
||||
const customerId = "customer_id";
|
||||
return {
|
||||
customerId,
|
||||
// To store the customer name and email
|
||||
customerData: { name: "", email: "" },
|
||||
};
|
||||
},
|
||||
|
||||
@@ -71,3 +71,23 @@ const handler = autumnHandler({
|
||||
export const loader = handler.loader;
|
||||
export const action = handler.action;`;
|
||||
};
|
||||
export const rr7Other = (customerType: "user" | "org") => {
|
||||
return `// app/routes/api.autumn.tsx
|
||||
|
||||
import { autumnHandler } from "autumn-js/react-router";
|
||||
|
||||
const handler = autumnHandler({
|
||||
secretKey: process.env.AUTUMN_SECRET_KEY!,
|
||||
identify: async (args) => {
|
||||
const customerId = "your_customer_id"; // Get customer id from your database
|
||||
|
||||
return {
|
||||
customerId,
|
||||
customerData: { name: "", email: "" },
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const loader = handler.loader;
|
||||
export const action = handler.action;`;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { Code, Fingerprint, CircleUserRound, Building } from "lucide-react";
|
||||
import { useIntegrateContext } from "../IntegrateContext";
|
||||
import { Backend, Frontend } from "../StackEnums";
|
||||
|
||||
export const SelectFrameworks = () => {
|
||||
const { queryStates, setQueryStates } = useIntegrateContext();
|
||||
|
||||
const iconSize = 12;
|
||||
const frontendOptions = [
|
||||
{ value: Frontend.Nextjs, label: "Next.js", logo: "nextjs.png" },
|
||||
{ value: Frontend.ReactRouter, label: "RR7", logo: "react-router.svg" },
|
||||
{ value: Frontend.Vite, label: "Vite SPA", logo: "vite.svg" },
|
||||
{
|
||||
value: Frontend.Other,
|
||||
label: "Other",
|
||||
icon: <Code size={iconSize} className="text-t2" />,
|
||||
},
|
||||
];
|
||||
|
||||
const backendOptions = [
|
||||
{ value: Backend.Nextjs, label: "Next.js", logo: "nextjs.png" },
|
||||
{
|
||||
value: Backend.ReactRouter,
|
||||
label: "RR7",
|
||||
logo: "react-router.svg",
|
||||
},
|
||||
{ value: Backend.Hono, label: "Hono", logo: "hono.png" },
|
||||
{ value: Backend.Express, label: "Express", logo: "express.png" },
|
||||
{ value: Backend.Elysia, label: "Elysia", logo: "elysia.png" },
|
||||
{
|
||||
value: Backend.Other,
|
||||
label: "Other",
|
||||
icon: <Code size={iconSize} className="text-t2" />,
|
||||
},
|
||||
];
|
||||
|
||||
const authOptions = [
|
||||
{ value: "better_auth", label: "Better Auth", logo: "better-auth.png" },
|
||||
{ value: "supabase", label: "Supabase", logo: "supabase.png" },
|
||||
{ value: "clerk", label: "Clerk", logo: "clerk.png" },
|
||||
{
|
||||
value: "other",
|
||||
label: "Other",
|
||||
icon: <Fingerprint size={iconSize} className="text-t2" />,
|
||||
},
|
||||
];
|
||||
|
||||
const customerOptions = [
|
||||
{
|
||||
value: "user",
|
||||
label: "Users",
|
||||
icon: <CircleUserRound size={iconSize} className="text-t2" />,
|
||||
},
|
||||
{
|
||||
value: "org",
|
||||
label: "Orgs",
|
||||
icon: <Building size={iconSize} className="text-t2" />,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Frontend</label>
|
||||
<div className="flex flex-row gap-2">
|
||||
{frontendOptions.map((option) => {
|
||||
return (
|
||||
<FrameworkContainer
|
||||
option={option}
|
||||
setQueryStates={setQueryStates}
|
||||
queryStates={queryStates}
|
||||
type="frontend"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Backend</label>
|
||||
<div className="flex flex-row gap-2">
|
||||
{backendOptions.map((option) => {
|
||||
return (
|
||||
<FrameworkContainer
|
||||
option={option}
|
||||
setQueryStates={setQueryStates}
|
||||
queryStates={queryStates}
|
||||
type="backend"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Auth Provider</label>
|
||||
<div className="flex flex-row gap-2">
|
||||
{authOptions.map((option) => {
|
||||
return (
|
||||
<FrameworkContainer
|
||||
option={option}
|
||||
setQueryStates={setQueryStates}
|
||||
queryStates={queryStates}
|
||||
type="auth"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-sm font-medium text-t2">Customer Type</label>
|
||||
<div className="flex flex-row gap-2">
|
||||
{customerOptions.map((option) => {
|
||||
return (
|
||||
<FrameworkContainer
|
||||
option={option}
|
||||
setQueryStates={setQueryStates}
|
||||
queryStates={queryStates}
|
||||
type="customerType"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FrameworkContainer = ({
|
||||
option,
|
||||
setQueryStates,
|
||||
queryStates,
|
||||
type,
|
||||
}: {
|
||||
option: {
|
||||
value: string;
|
||||
label: string;
|
||||
logo?: string;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
setQueryStates: (queryStates: any) => void;
|
||||
queryStates: any;
|
||||
type: "backend" | "auth" | "customerType" | "frontend";
|
||||
}) => {
|
||||
const isSelected = queryStates[type] === option.value;
|
||||
return (
|
||||
<div
|
||||
key={option.value}
|
||||
className={`flex text-sm justify-center items-center gap-2 h-8 px-2 rounded-xs cursor-pointer ${
|
||||
isSelected
|
||||
? "shadow-inner bg-stone-100 border border-zinc-200"
|
||||
: "border border-transparent"
|
||||
}`}
|
||||
onClick={() => setQueryStates({ ...queryStates, [type]: option.value })}
|
||||
>
|
||||
{option.logo ? (
|
||||
<img
|
||||
src={`/frameworks/${option.logo}`}
|
||||
alt={option.label}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
) : option.icon ? (
|
||||
<>{option.icon}</>
|
||||
) : (
|
||||
<Code className="text-t2" />
|
||||
)}
|
||||
<span className="text-t2 text-sm font-medium whitespace-nowrap">
|
||||
{option.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||