quickstart guide
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.47.2",
|
||||
"@useautumn/react": "latest",
|
||||
"@wooorm/starry-night": "^3.5.0",
|
||||
"@wooorm/starry-night": "^3.6.0",
|
||||
"axios": "^1.7.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -48,14 +48,17 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.0.3",
|
||||
"next-themes": "^0.4.4",
|
||||
"ora": "^8.1.1",
|
||||
"pointer-sdk": "^0.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti-explosion": "^2.1.2",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"sonner": "^1.7.4",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
@@ -4,5 +4,21 @@ import ConnectStripe from "@/views/onboarding/ConnectStripe";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export default async function StripePage() {
|
||||
return <ConnectStripe />;
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<div className="w-[430px] shadow-lg rounded-2xl border flex flex-col p-8 bg-white gap-4">
|
||||
<div>
|
||||
<p className="text-md font-bold text-t2">
|
||||
Please connect your Stripe account
|
||||
</p>
|
||||
<p className="text-t3 text-xs mt-1">
|
||||
Your credentials will be encrypted and stored safely
|
||||
</p>
|
||||
</div>
|
||||
<ConnectStripe />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default async function RootLayout({
|
||||
<ClerkProvider>
|
||||
<NextUIProvider>
|
||||
<SidebarProvider>
|
||||
{org_id && !path.includes("/demo") && (
|
||||
{!path.includes("/demo") && (
|
||||
<HomeSidebar
|
||||
user={sessionClaims?.user as any}
|
||||
org={org}
|
||||
@@ -73,15 +73,22 @@ export default async function RootLayout({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{path.includes("/onboarding") ? (
|
||||
children
|
||||
) : (
|
||||
<div className="w-full h-full overflow-scroll bg-stone-50 p-6 flex justify-center">
|
||||
<div className="w-full h-fit max-w-[1048px] flex flex-col gap-4">
|
||||
{children}
|
||||
<div className="w-full h-full overflow-scroll bg-stone-50 p-6 flex justify-center">
|
||||
<div className="hidden md:flex w-full h-fit max-w-[1048px] flex-col gap-4">
|
||||
{children}
|
||||
</div>
|
||||
<div className="md:hidden w-full h-full flex items-center justify-center">
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm text-center">
|
||||
<h2 className="text-xl font-semibold mb-2">
|
||||
Autumn is coming to mobile soon
|
||||
</h2>
|
||||
<p className="text-gray-600">
|
||||
We're currently designed for larger screens. Come back
|
||||
on your desktop?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
{/* </AutumnProvider> */}
|
||||
</SidebarProvider>
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import OnboardingView from "@/views/onboarding/OnboardingView";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "@clerk/nextjs/server";
|
||||
|
||||
async function OnboardingPage() {
|
||||
// const onboardingView = await OnboardingView();
|
||||
|
||||
const headersList = await headers();
|
||||
const env = (headersList.get("env") as AppEnv) || AppEnv.Sandbox;
|
||||
|
||||
const { sessionClaims } = await auth();
|
||||
return <OnboardingView sessionClaims={sessionClaims} />;
|
||||
return <OnboardingView sessionClaims={sessionClaims} env={env} />;
|
||||
}
|
||||
|
||||
export default OnboardingPage;
|
||||
|
||||
67
frontend/src/components/general/CodeDisplay.tsx
Normal file
67
frontend/src/components/general/CodeDisplay.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { common, createStarryNight } from "@wooorm/starry-night";
|
||||
import { toHtml } from "hast-util-to-html";
|
||||
import { Root } from "hast-util-to-html/lib/types";
|
||||
import React from "react";
|
||||
import "@wooorm/starry-night/style/dark";
|
||||
import CopyButton from "@/components/general/CopyButton";
|
||||
|
||||
interface CodeDisplayProps {
|
||||
code: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
const getStarryNight = async () => {
|
||||
return await createStarryNight(common);
|
||||
};
|
||||
|
||||
export const CodeDisplay: React.FC<CodeDisplayProps> = ({ code, language }) => {
|
||||
const [highlightedCode, setHighlightedCode] = React.useState("");
|
||||
|
||||
// Format language name to look nicer
|
||||
const formatLanguage = (lang: string) => {
|
||||
return lang.charAt(0).toUpperCase() + lang.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const highlight = async () => {
|
||||
const starryNight = await getStarryNight();
|
||||
const scope = starryNight.flagToScope(language);
|
||||
|
||||
if (scope) {
|
||||
const tree = starryNight.highlight(code, scope);
|
||||
const html = toHtml(tree as Root);
|
||||
// Wrap the HTML in a div with inline styles
|
||||
const styledHtml = `<div style="white-space: pre; display: inline-block; width: 10px;">${html}</div>`;
|
||||
setHighlightedCode(styledHtml);
|
||||
} else {
|
||||
setHighlightedCode(
|
||||
`<div style="white-space: pre; display: inline-block; width: 10px;">${code}</div>`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
highlight();
|
||||
}, [code, language]);
|
||||
|
||||
return (
|
||||
<div className="text-sm overflow-x-auto bg-slate-900 rounded-sm px-2 pt-2 [&::-webkit-scrollbar]:bg-transparent [&::-webkit-scrollbar-track]:bg-transparent">
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="text-xs text-zinc-400 font-mono">
|
||||
{formatLanguage(language)}
|
||||
</div>
|
||||
<CopyButton
|
||||
text={code}
|
||||
className="text-white hover:text-white/80 hover:bg-zinc-800"
|
||||
/>
|
||||
</div>
|
||||
<pre className="overflow-x-auto px-4 py-1 [&::-webkit-scrollbar]:bg-transparent [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-zinc-600 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar]:h-2">
|
||||
<div className="max-w-full">
|
||||
<code
|
||||
className={`language-${language} text-white`}
|
||||
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
||||
/>
|
||||
</div>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -3,9 +3,9 @@ import { Toaster } from "react-hot-toast";
|
||||
export const CustomToaster = () => {
|
||||
return (
|
||||
<Toaster
|
||||
position="bottom-center"
|
||||
position="top-center"
|
||||
toastOptions={{
|
||||
duration: 2000,
|
||||
duration: 4000,
|
||||
style: { fontSize: "14px" },
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@ const buttonVariants = cva(
|
||||
gradientPrimary:
|
||||
"bg-gradient-to-b font-semibold border border-primary from-primary/65 to-primary text-white hover:from-primary hover:to-primary shadow-sm shadow-purple-500/50",
|
||||
gradientSecondary:
|
||||
"border border-stone-300 font-semibold bg-gradient-to-b from-white to-stone-100 text-t1 hover:from-stone-300 hover:to-stone-400 shadow-sm text-xs",
|
||||
"border border-stone-300 font-semibold bg-gradient-to-b from-white to-stone-100 text-t1 hover:from-stone-300 hover:to-stone-400 shadow-sm",
|
||||
},
|
||||
size: {
|
||||
default: "h-8 px-3 flex items-center gap-1",
|
||||
|
||||
31
frontend/src/components/ui/sonner.tsx
Normal file
31
frontend/src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client"
|
||||
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner } from "sonner"
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-white group-[.toaster]:text-zinc-950 group-[.toaster]:border-zinc-200 group-[.toaster]:shadow-lg dark:group-[.toaster]:bg-zinc-950 dark:group-[.toaster]:text-zinc-50 dark:group-[.toaster]:border-zinc-800",
|
||||
description: "group-[.toast]:text-zinc-500 dark:group-[.toast]:text-zinc-400",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-zinc-900 group-[.toast]:text-zinc-50 dark:group-[.toast]:bg-zinc-50 dark:group-[.toast]:text-zinc-900",
|
||||
cancelButton:
|
||||
"group-[.toast]:bg-zinc-100 group-[.toast]:text-zinc-500 dark:group-[.toast]:bg-zinc-800 dark:group-[.toast]:text-zinc-400",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toaster }
|
||||
33
frontend/src/components/ui/step.tsx
Normal file
33
frontend/src/components/ui/step.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCoin } from "@fortawesome/pro-duotone-svg-icons";
|
||||
|
||||
interface StepProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
function Step({ title, children, className, description }: StepProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative pl-8 pb-8 border-l-2 border-purple-100 gap-4 flex flex-col",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div className="absolute -left-[17px] -top-1 flex items-center justify-center w-8 h-8 rounded-full bg-stone-50">
|
||||
<FontAwesomeIcon icon={faCoin} className="text-primary w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-t1 text-lg font-medium">{title}</h1>
|
||||
{description && <p className="text-t3">{description}</p>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Step;
|
||||
@@ -18,16 +18,22 @@ export default clerkMiddleware(async (auth, req) => {
|
||||
const { sessionClaims }: { sessionClaims: any } = await auth();
|
||||
|
||||
if (!sessionClaims?.org_id && !req.nextUrl.pathname.includes("/onboarding")) {
|
||||
console.log("Redirecting to onboarding");
|
||||
console.log(req.nextUrl.pathname, "Redirecting to onboarding");
|
||||
const onboardingUrl = new URL("/onboarding", req.url);
|
||||
if (req.nextUrl.pathname !== "/") {
|
||||
onboardingUrl.searchParams.set(
|
||||
"toast",
|
||||
"Please create an organization to continue"
|
||||
);
|
||||
}
|
||||
return NextResponse.redirect(onboardingUrl);
|
||||
}
|
||||
|
||||
if (sessionClaims?.org_id && req.nextUrl.pathname.includes("/onboarding")) {
|
||||
const url = new URL("/", req.url);
|
||||
console.log("Redirecting to home");
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
// if (sessionClaims?.org_id && req.nextUrl.pathname.includes("/onboarding")) {
|
||||
// const url = new URL("/", req.url);
|
||||
// console.log("Redirecting to home");
|
||||
// return NextResponse.redirect(url);
|
||||
// }
|
||||
|
||||
if (path === "/") {
|
||||
return NextResponse.redirect(new URL("/customers", req.url));
|
||||
|
||||
@@ -118,7 +118,7 @@ export const CustomerEntitlementsList = ({
|
||||
<TableHead className="">
|
||||
{featureType === FeatureType.Metered && "Balance"}
|
||||
</TableHead>
|
||||
<TableHead className="min-w-0 w-24">
|
||||
<TableHead className="min-w-0 w-28">
|
||||
{featureType === FeatureType.Metered && "Next Reset"}
|
||||
</TableHead>
|
||||
{/* <TableHead className="">Status</TableHead> */}
|
||||
|
||||
@@ -17,7 +17,8 @@ import { useAxiosInstance } from "@/services/useAxiosInstance";
|
||||
import { useDevContext } from "./DevContext";
|
||||
|
||||
const CreateAPIKey = () => {
|
||||
const { env, mutate } = useDevContext();
|
||||
const { env, mutate, onboarding, apiKeyName, setApiCreated, apiCreated } =
|
||||
useDevContext();
|
||||
const axiosInstance = useAxiosInstance({ env });
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -39,13 +40,15 @@ const CreateAPIKey = () => {
|
||||
}, [copied]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
console.log("creating api key", apiKeyName ? apiKeyName : name);
|
||||
setLoading(true);
|
||||
try {
|
||||
const { api_key } = await DevService.createAPIKey(axiosInstance, {
|
||||
name,
|
||||
name: apiKeyName ? apiKeyName : name,
|
||||
});
|
||||
|
||||
setApiKey(api_key);
|
||||
setApiCreated && setApiCreated(true);
|
||||
await mutate();
|
||||
} catch (error) {
|
||||
console.log("Error:", error);
|
||||
@@ -58,7 +61,17 @@ const CreateAPIKey = () => {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="" startIcon={<Plus size={15} />} variant="dashed">
|
||||
<Button
|
||||
className={`${onboarding ? "w-fit" : "w-full"}`}
|
||||
startIcon={<Plus size={15} />}
|
||||
variant={onboarding ? "gradientPrimary" : "dashed"}
|
||||
disabled={apiCreated ? true : false}
|
||||
onClick={() => {
|
||||
if (apiKeyName) {
|
||||
handleCreate();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create API Key
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
99
frontend/src/views/features/FeaturesTable.tsx
Normal file
99
frontend/src/views/features/FeaturesTable.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
formatUnixToDateTime,
|
||||
formatUnixToDateTimeString,
|
||||
} from "@/utils/formatUtils/formatDateUtils";
|
||||
import { Feature, FeatureType, Product } from "@autumn/shared";
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { FeatureRowToolbar } from "./FeatureRowToolbar";
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@/components/ui/table";
|
||||
import { navigateTo } from "@/utils/genUtils";
|
||||
import { useFeaturesContext } from "./FeaturesContext";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import UpdateFeature from "./UpdateFeature";
|
||||
import { FeatureTypeBadge } from "./FeatureTypeBadge";
|
||||
|
||||
export const FeaturesTable = () => {
|
||||
const { env, features, onboarding } = useFeaturesContext();
|
||||
const router = useRouter();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedFeature, setSelectedFeature] = useState<any>(null);
|
||||
|
||||
const getMeteredEventNames = (feature: Feature) => {
|
||||
if (feature.type !== FeatureType.Metered) return "";
|
||||
|
||||
if (!feature.config.filters || feature.config.filters.length === 0)
|
||||
return "";
|
||||
|
||||
return feature.config.filters[0].value.join(", ");
|
||||
};
|
||||
|
||||
const handleRowClick = (id: string) => {
|
||||
const feature = features.find((feature: Feature) => feature.id === id);
|
||||
setSelectedFeature(feature);
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UpdateFeature
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
selectedFeature={selectedFeature}
|
||||
setSelectedFeature={setSelectedFeature}
|
||||
/>
|
||||
<Table>
|
||||
<TableHeader className="rounded-full">
|
||||
<TableRow className="border-none">
|
||||
<TableHead className="">Name</TableHead>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
{!onboarding && <TableHead>Event Names</TableHead>}
|
||||
{!onboarding && (
|
||||
<TableHead className="min-w-0 w-28">Created At</TableHead>
|
||||
)}
|
||||
<TableHead className="min-w-0 w-10"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{features.map((feature: Feature) => (
|
||||
<TableRow
|
||||
key={feature.internal_id}
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleRowClick(feature.id)}
|
||||
>
|
||||
<TableCell>{feature.name}</TableCell>
|
||||
<TableCell className="font-mono">{feature.id}</TableCell>
|
||||
<TableCell>
|
||||
<FeatureTypeBadge type={feature.type} />
|
||||
</TableCell>
|
||||
{!onboarding && (
|
||||
<TableCell>{getMeteredEventNames(feature)}</TableCell>
|
||||
)}
|
||||
{!onboarding && (
|
||||
<TableCell className="min-w-20 w-24">
|
||||
<span>{formatUnixToDateTime(feature.created_at).date}</span>{" "}
|
||||
<span className="text-t3">
|
||||
{formatUnixToDateTime(feature.created_at).time}
|
||||
</span>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell className="min-w-4 w-6">
|
||||
<FeatureRowToolbar feature={feature} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,7 @@ import { Feature, FeatureType } from "@autumn/shared";
|
||||
import { CreateFeature } from "./CreateFeature";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import { FeaturesTable } from "./FeaturesTable";
|
||||
import {
|
||||
Table,
|
||||
TableHeader,
|
||||
@@ -25,8 +26,8 @@ import UpdateFeature from "./UpdateFeature";
|
||||
import { CustomToaster } from "@/components/general/CustomToaster";
|
||||
|
||||
function FeaturesView({ env }: { env: AppEnv }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedFeature, setSelectedFeature] = useState<any>(null);
|
||||
// const [open, setOpen] = useState(false);
|
||||
// const [selectedFeature, setSelectedFeature] = useState<any>(null);
|
||||
|
||||
const { data, isLoading, error, mutate } = useAxiosSWR({
|
||||
url: `/features`,
|
||||
@@ -38,26 +39,26 @@ function FeaturesView({ env }: { env: AppEnv }) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
const handleRowClick = (id: string) => {
|
||||
const feature = data?.features.find(
|
||||
(feature: Feature) => feature.id === id
|
||||
);
|
||||
setSelectedFeature(feature);
|
||||
setOpen(true);
|
||||
};
|
||||
// const handleRowClick = (id: string) => {
|
||||
// const feature = data?.features.find(
|
||||
// (feature: Feature) => feature.id === id
|
||||
// );
|
||||
// setSelectedFeature(feature);
|
||||
// setOpen(true);
|
||||
// };
|
||||
|
||||
const features = data?.features.filter(
|
||||
(feature: Feature) => feature.type !== "credit_system"
|
||||
);
|
||||
|
||||
const getMeteredEventNames = (feature: Feature) => {
|
||||
if (feature.type !== FeatureType.Metered) return "";
|
||||
// const getMeteredEventNames = (feature: Feature) => {
|
||||
// if (feature.type !== FeatureType.Metered) return "";
|
||||
|
||||
if (!feature.config.filters || feature.config.filters.length === 0)
|
||||
return "";
|
||||
// if (!feature.config.filters || feature.config.filters.length === 0)
|
||||
// return "";
|
||||
|
||||
return feature.config.filters[0].value.join(", ");
|
||||
};
|
||||
// return feature.config.filters[0].value.join(", ");
|
||||
// };
|
||||
|
||||
return (
|
||||
<FeaturesContext.Provider
|
||||
@@ -75,8 +76,9 @@ function FeaturesView({ env }: { env: AppEnv }) {
|
||||
Define the metered and boolean features your users are entitled to
|
||||
</p>
|
||||
</div>
|
||||
<FeaturesTable />
|
||||
|
||||
<UpdateFeature
|
||||
{/* <UpdateFeature
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
selectedFeature={selectedFeature}
|
||||
@@ -128,7 +130,7 @@ function FeaturesView({ env }: { env: AppEnv }) {
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Table> */}
|
||||
|
||||
<CreateFeature />
|
||||
</FeaturesContext.Provider>
|
||||
|
||||
@@ -30,7 +30,7 @@ function LoadingScreen() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center flex-col gap-4">
|
||||
<div className="flex h-screen overflow-hidden w-full items-center justify-center flex-col gap-4">
|
||||
<LoaderCircle className="animate-spin text-primary" size={30} />
|
||||
<p className="text-t2 font-mono text-xs font-medium">{loadingText}</p>
|
||||
</div>
|
||||
|
||||
@@ -34,8 +34,17 @@ import { cn } from "@/lib/utils";
|
||||
import { useSession } from "@clerk/nextjs";
|
||||
import { useAxiosSWR } from "@/services/useAxiosSwr";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import SmallSpinner from "@/components/general/SmallSpinner";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCcStripe, faStripeS } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
function ConnectStripe() {
|
||||
function ConnectStripe({
|
||||
className,
|
||||
onboarding,
|
||||
}: {
|
||||
className?: string;
|
||||
onboarding?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const redirect = searchParams.get("redirect");
|
||||
@@ -43,8 +52,8 @@ function ConnectStripe() {
|
||||
|
||||
const [testApiKey, setTestApiKey] = useState("");
|
||||
const [liveApiKey, setLiveApiKey] = useState("");
|
||||
const [successUrl, setSuccessUrl] = useState("");
|
||||
const [defaultCurrency, setDefaultCurrency] = useState("");
|
||||
const [successUrl, setSuccessUrl] = useState("https://useautumn.com");
|
||||
const [defaultCurrency, setDefaultCurrency] = useState("USD");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const {
|
||||
@@ -59,7 +68,7 @@ function ConnectStripe() {
|
||||
const org = orgData?.org;
|
||||
|
||||
const handleConnectStripe = async () => {
|
||||
if (!testApiKey || !liveApiKey || !successUrl || !defaultCurrency) {
|
||||
if (!testApiKey || !successUrl || !defaultCurrency) {
|
||||
toast.error("Please fill in all fields");
|
||||
return;
|
||||
}
|
||||
@@ -74,17 +83,15 @@ function ConnectStripe() {
|
||||
try {
|
||||
await OrgService.connectStripe(axiosInstance, {
|
||||
testApiKey,
|
||||
liveApiKey,
|
||||
liveApiKey: onboarding ? testApiKey : liveApiKey,
|
||||
successUrl,
|
||||
defaultCurrency,
|
||||
});
|
||||
|
||||
toast.success("Successfully connected to Stripe");
|
||||
|
||||
if (redirect) {
|
||||
await mutateOrg();
|
||||
if (redirect && !onboarding) {
|
||||
navigateTo(redirect, router, AppEnv.Live);
|
||||
} else {
|
||||
router.push("/");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Failed to connect Stripe", error);
|
||||
@@ -108,19 +115,23 @@ function ConnectStripe() {
|
||||
};
|
||||
|
||||
if (isOrgLoading) {
|
||||
return <LoadingScreen />;
|
||||
return <SmallSpinner />;
|
||||
}
|
||||
|
||||
if (org?.stripe_connected) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<p className="text-md font-medium text-t3">
|
||||
Stripe already connected 🎉🎉🎉
|
||||
</p>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-4",
|
||||
className,
|
||||
onboarding && "flex-row justify-between items-center"
|
||||
)}
|
||||
>
|
||||
<p className="text-t3 text-sm">Stripe Connected ✅</p>
|
||||
<Button
|
||||
onClick={handleDisconnectStripe}
|
||||
variant="gradientSecondary"
|
||||
className="mt-4"
|
||||
className={`${onboarding ? "w-fit" : ""}`}
|
||||
isLoading={isDisconnecting}
|
||||
>
|
||||
Disconnect Stripe
|
||||
@@ -130,80 +141,59 @@ function ConnectStripe() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn("flex flex-col font-regular gap-4", className)}>
|
||||
<CustomToaster />
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<div className="w-[430px] shadow-lg rounded-2xl border flex flex-col p-8 bg-white">
|
||||
<p className="text-md font-bold text-t2">
|
||||
Please connect your Stripe account
|
||||
</p>
|
||||
<p className="text-t3 text-xs mt-1">
|
||||
Your credentials will be encrypted and stored safely
|
||||
</p>
|
||||
<div className="flex flex-col font-regular mt-4 gap-4">
|
||||
<div>
|
||||
<FieldLabel>Test API Key</FieldLabel>
|
||||
<Input
|
||||
value={testApiKey}
|
||||
onChange={(e) => setTestApiKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col font-regular gap-4">
|
||||
<div>
|
||||
<FieldLabel>Stripe Test Secret API Key</FieldLabel>
|
||||
<Input
|
||||
value={testApiKey}
|
||||
placeholder="sk_test_..."
|
||||
onChange={(e) => setTestApiKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FieldLabel>Live API Key</FieldLabel>
|
||||
<Input
|
||||
value={liveApiKey}
|
||||
onChange={(e) => setLiveApiKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<FieldLabel>Success URL</FieldLabel>
|
||||
<Input
|
||||
value={successUrl}
|
||||
onChange={(e) => setSuccessUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FieldLabel>Default Currency</FieldLabel>
|
||||
<CurrencySelect
|
||||
defaultCurrency={defaultCurrency}
|
||||
setDefaultCurrency={setDefaultCurrency}
|
||||
/>
|
||||
{/* <Select
|
||||
value={defaultCurrency}
|
||||
onValueChange={(value) => setDefaultCurrency(value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{stripeCurrencyCodes.map((currency) => (
|
||||
<SelectItem
|
||||
key={currency.code}
|
||||
value={currency.code}
|
||||
onClick={() => setDefaultCurrency(currency.code)}
|
||||
>
|
||||
{currency.currency} - {currency.code}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select> */}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-4">
|
||||
<Button
|
||||
className="w-fit"
|
||||
onClick={handleConnectStripe}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect Stripe
|
||||
</Button>
|
||||
</div>
|
||||
{!onboarding && (
|
||||
<div>
|
||||
<FieldLabel>Stripe Live Secret API Key</FieldLabel>
|
||||
<Input
|
||||
value={liveApiKey}
|
||||
placeholder="sk_live_..."
|
||||
onChange={(e) => setLiveApiKey(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2 w-full">
|
||||
<div className="w-full truncate">
|
||||
<FieldLabel>Success URL after Stripe payment</FieldLabel>
|
||||
<Input
|
||||
value={successUrl}
|
||||
onChange={(e) => setSuccessUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-1/4 min-w-32">
|
||||
<FieldLabel>Currency</FieldLabel>
|
||||
<CurrencySelect
|
||||
defaultCurrency={defaultCurrency}
|
||||
setDefaultCurrency={setDefaultCurrency}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="w-fit"
|
||||
variant="gradientPrimary"
|
||||
onClick={handleConnectStripe}
|
||||
disabled={org?.stripe_connected}
|
||||
isLoading={isLoading}
|
||||
startIcon={<FontAwesomeIcon icon={faStripeS} className="mr-2" />}
|
||||
>
|
||||
Connect Stripe
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
1
frontend/src/views/onboarding/ConnectStripeOAuth.tsx
Normal file
1
frontend/src/views/onboarding/ConnectStripeOAuth.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export default function ConnectStripeOAuth() {}
|
||||
@@ -1,38 +1,131 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import FieldLabel from "@/components/general/modal-components/FieldLabel";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { slugify } from "@/utils/formatUtils/formatTextUtils";
|
||||
import { CustomToaster } from "@/components/general/CustomToaster";
|
||||
import { toast } from "react-hot-toast";
|
||||
import {
|
||||
OrganizationList,
|
||||
OrganizationSwitcher,
|
||||
useOrganizationList,
|
||||
} from "@clerk/nextjs";
|
||||
import { useOrganizationList } from "@clerk/nextjs";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Step from "@/components/ui/step";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import ConnectStripe from "./ConnectStripe";
|
||||
import { FeaturesTable } from "../features/FeaturesTable";
|
||||
import { AppEnv, Feature } from "@autumn/shared";
|
||||
import { useAxiosSWR } from "@/services/useAxiosSwr";
|
||||
import { FeaturesContext } from "../features/FeaturesContext";
|
||||
import SmallSpinner from "@/components/general/SmallSpinner";
|
||||
import ConfettiExplosion from "react-confetti-explosion";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { navigateTo } from "@/utils/genUtils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import LoadingScreen from "../general/LoadingScreen";
|
||||
import { CreateFeature } from "../features/CreateFeature";
|
||||
import { ProductsContext } from "../products/ProductsContext";
|
||||
import { ProductsTable } from "../products/ProductsTable";
|
||||
import CreateProduct from "../products/CreateProduct";
|
||||
import CreateAPIKey from "../developer/CreateAPIKey";
|
||||
import { DevContext } from "../developer/DevContext";
|
||||
import { CodeDisplay } from "@/components/general/CodeDisplay";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faBuilding,
|
||||
faExternalLinkAlt,
|
||||
} from "@fortawesome/pro-duotone-svg-icons";
|
||||
let SUPABASE_URL = "https://tqjsbqmimvflvkwdoucx.supabase.co";
|
||||
let SUPABASE_KEY =
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRxanNicW1pbXZmbHZrd2RvdWN4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzYyNjU0NTEsImV4cCI6MjA1MTg0MTQ1MX0.ndNu1-ObwQy5rzbmqQPvNRCG6z4GYkZKy_WkGo3AXNs";
|
||||
|
||||
function OnboardingView({ sessionClaims }: { sessionClaims: any }) {
|
||||
const { org_id, user } = sessionClaims || {};
|
||||
let supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
|
||||
|
||||
function OnboardingView({
|
||||
sessionClaims,
|
||||
env,
|
||||
}: {
|
||||
sessionClaims: any;
|
||||
env: AppEnv;
|
||||
}) {
|
||||
const { org_id, user, org } = sessionClaims || {};
|
||||
const searchParams = useSearchParams();
|
||||
const { createOrganization, setActive } = useOrganizationList();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const [isExploding, setIsExploding] = useState(false);
|
||||
const [orgId, setOrgId] = useState(org_id);
|
||||
const [apiKeyName, setApiKeyName] = useState("");
|
||||
const [apiCreated, setApiCreated] = useState(false);
|
||||
//get features for the org
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
mutate: featuresMutate,
|
||||
} = useAxiosSWR({
|
||||
url: `/features`,
|
||||
env: env,
|
||||
withAuth: true,
|
||||
});
|
||||
|
||||
const features = data?.features.filter(
|
||||
(feature: Feature) => feature.type !== "credit_system"
|
||||
);
|
||||
|
||||
//get products for the org
|
||||
const {
|
||||
data: productData,
|
||||
mutate: productMutate,
|
||||
isLoading: productLoading,
|
||||
} = useAxiosSWR({
|
||||
url: `/products/data`,
|
||||
env: env,
|
||||
withAuth: true,
|
||||
});
|
||||
|
||||
//supabase realtime to update products and features when org is created
|
||||
useEffect(() => {
|
||||
if (!orgId) return;
|
||||
|
||||
supabase
|
||||
.channel(orgId)
|
||||
.on(
|
||||
"broadcast",
|
||||
{
|
||||
event: "org.created",
|
||||
},
|
||||
async (payload) => {
|
||||
console.log("org created");
|
||||
await featuresMutate();
|
||||
await productMutate();
|
||||
}
|
||||
)
|
||||
.subscribe();
|
||||
}, [orgId, featuresMutate, productMutate]);
|
||||
|
||||
useEffect(() => {
|
||||
const toastMessage = searchParams.get("toast");
|
||||
if (toastMessage) {
|
||||
toast.error(toastMessage);
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
const handleCreateOrg = async () => {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
if (!createOrganization) {
|
||||
toast.error("Error creating organization");
|
||||
return;
|
||||
}
|
||||
|
||||
await createOrganization({ name: fields.name, slug: fields.slug });
|
||||
await setActive({ organization: fields.slug });
|
||||
window.location.href = '/sandbox/features';
|
||||
const org = await createOrganization({
|
||||
name: fields.name,
|
||||
});
|
||||
setOrgId(org.id);
|
||||
console.log("org id", org.id);
|
||||
await setActive({ organization: org.id });
|
||||
toast.success(`Created your organization: ${org.name}`);
|
||||
|
||||
// window.location.href = "/sandbox/products";
|
||||
setIsExploding(true);
|
||||
} catch (error: any) {
|
||||
if (error.message) {
|
||||
toast.error(error.message);
|
||||
@@ -41,35 +134,74 @@ function OnboardingView({ sessionClaims }: { sessionClaims: any }) {
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
if (env !== AppEnv.Sandbox) window.location.href = "/sandbox/onboarding";
|
||||
};
|
||||
|
||||
const [slugEditted, setSlugEditted] = useState(false);
|
||||
// const [slugEditted, setSlugEditted] = useState(false);
|
||||
const [fields, setFields] = useState({
|
||||
name: "",
|
||||
name: org?.name || "",
|
||||
slug: "",
|
||||
});
|
||||
|
||||
if (!org_id && Object.keys(user.organizations).length == 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen w-screen bg-stone-50">
|
||||
<CustomToaster />
|
||||
<div className="w-[430px] shadow-lg rounded-2xl border flex flex-col p-8 bg-white gap-4">
|
||||
<p className="text-lg font-bold text-t2">Create an organization</p>
|
||||
<div>
|
||||
{/* <FieldLabel>Name</FieldLabel> */}
|
||||
<Input
|
||||
placeholder="Your unique Organization Name"
|
||||
value={fields.name}
|
||||
onChange={(e) => {
|
||||
const newFields = { ...fields, name: e.target.value };
|
||||
if (!slugEditted) {
|
||||
newFields.slug = slugify(e.target.value);
|
||||
return (
|
||||
<>
|
||||
<CustomToaster />
|
||||
<div className="flex flex-col p-8">
|
||||
<Step title="Create your organization">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<div className="text-t2 flex flex-col gap-2 w-full lg:w-1/3">
|
||||
<p className="flex items-center">
|
||||
<span>👋</span>
|
||||
<span className="font-bold bg-gradient-to-r from-orange-500 via-pink-500 to-primary w-fit bg-clip-text text-transparent">
|
||||
Welcome to Autumn
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
Create an organization to get started and integrate pricing
|
||||
within 5 minutes.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full lg:w-2/3 min-w-md max-w-xl flex gap-2 bg-white p-4 rounded-sm border">
|
||||
<Input
|
||||
placeholder="Org name"
|
||||
value={org?.name || fields.name}
|
||||
disabled={!!org?.name}
|
||||
onChange={(e) => {
|
||||
const newFields = { ...fields, name: e.target.value };
|
||||
setFields(newFields);
|
||||
// if (!slugEditted) {
|
||||
// newFields.slug = slugify(e.target.value);
|
||||
// }
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="w-fit"
|
||||
disabled={!!org?.name}
|
||||
onClick={handleCreateOrg}
|
||||
isLoading={loading}
|
||||
variant="gradientPrimary"
|
||||
startIcon={
|
||||
<FontAwesomeIcon icon={faBuilding} className="mr-2" />
|
||||
}
|
||||
setFields(newFields);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
Create Organization
|
||||
</Button>
|
||||
{isExploding && (
|
||||
<ConfettiExplosion
|
||||
force={0.8}
|
||||
duration={3000}
|
||||
particleCount={250}
|
||||
zIndex={1000}
|
||||
width={1600}
|
||||
onComplete={() => {
|
||||
console.log("complete");
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div>
|
||||
</Step>
|
||||
{/* <div>
|
||||
<FieldLabel>Slug</FieldLabel>
|
||||
<Input
|
||||
placeholder="Organization Slug"
|
||||
@@ -80,25 +212,218 @@ function OnboardingView({ sessionClaims }: { sessionClaims: any }) {
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
className="w-fit"
|
||||
onClick={handleCreateOrg}
|
||||
isLoading={loading}
|
||||
variant="gradientPrimary"
|
||||
>
|
||||
Create Organization
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{org?.id && (
|
||||
<>
|
||||
<Step title="Connect your Stripe test account">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<p className="text-t2 flex-col gap-2 w-full lg:w-1/3">
|
||||
<span>
|
||||
Paste in your{" "}
|
||||
<a
|
||||
className="text-primary underline font-semibold"
|
||||
href="https://dashboard.stripe.com/test/apikeys"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Stripe Test Key
|
||||
<FontAwesomeIcon
|
||||
className="ml-1 h-2.5 w-2.5"
|
||||
icon={faExternalLinkAlt}
|
||||
/>
|
||||
</a>{" "}
|
||||
</span>
|
||||
{/* <span>
|
||||
You can add your live key later under the 'Connect to
|
||||
Stripe' tab.
|
||||
</span> */}
|
||||
</p>
|
||||
<ConnectStripe
|
||||
className="w-full lg:w-2/3 min-w-md max-w-xl bg-white rounded-sm border p-6"
|
||||
onboarding={true}
|
||||
/>
|
||||
</div>
|
||||
</Step>
|
||||
<Step title="Set up your pricing models">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<div className="flex flex-col gap-2 text-t2 w-full lg:w-1/3">
|
||||
<p>
|
||||
<span className="font-bold">Features</span> are the benefits
|
||||
your users are entitled to.{" "}
|
||||
<span className="font-bold">Products</span> are how you
|
||||
charge for them.
|
||||
</p>
|
||||
<p>
|
||||
Define your own application features, and create the pricing
|
||||
you want: subscriptions, usage-based, overages, credits, or
|
||||
a a combination!
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full lg:w-2/3 min-w-md max-w-xl flex flex-col gap-6">
|
||||
<FeaturesContext.Provider
|
||||
value={{
|
||||
features: features,
|
||||
dbConns: data?.dbConns,
|
||||
env,
|
||||
mutate: featuresMutate,
|
||||
onboarding: true,
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<SmallSpinner />
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-t2 font-medium text-md">Features</p>
|
||||
<FeaturesTable />
|
||||
<CreateFeature />
|
||||
</div>
|
||||
)}
|
||||
</FeaturesContext.Provider>
|
||||
<ProductsContext.Provider
|
||||
value={{
|
||||
...productData,
|
||||
env,
|
||||
mutate: productMutate,
|
||||
onboarding: true,
|
||||
}}
|
||||
>
|
||||
{productLoading ? (
|
||||
<SmallSpinner />
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="text-t2 font-medium text-md">Products</p>
|
||||
<ProductsTable products={productData?.products} />
|
||||
<div>
|
||||
<CreateProduct />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ProductsContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</Step>
|
||||
<Step title="Create an Autumn API Key">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<p className="text-t2 w-full lg:w-1/3">
|
||||
Generate an API key to start integrating Autumn into your
|
||||
application.
|
||||
</p>
|
||||
<div className="w-full lg:w-2/3 min-w-md max-w-xl flex gap-2 bg-white p-4 rounded-sm border">
|
||||
<DevContext.Provider
|
||||
value={{
|
||||
env,
|
||||
mutate: () => {},
|
||||
onboarding: true,
|
||||
apiKeyName,
|
||||
setApiKeyName,
|
||||
apiCreated,
|
||||
setApiCreated,
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder="API Key Name"
|
||||
value={apiKeyName}
|
||||
disabled={apiCreated}
|
||||
onChange={(e) => setApiKeyName(e.target.value)}
|
||||
/>
|
||||
<CreateAPIKey />
|
||||
</DevContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</Step>
|
||||
<Step title="Attach a Product">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<p className="flex flex-col gap-2 text-t2 w-full lg:w-1/3">
|
||||
Call this endpoint when a user wants to purchase one of the
|
||||
products we defined above.
|
||||
<span>
|
||||
Autumn will return a Stripe checkout URL that you should
|
||||
redirect the user to.
|
||||
</span>
|
||||
</p>
|
||||
<div className="w-full lg:w-2/3 min-w-md max-w-xl">
|
||||
<CodeDisplay
|
||||
code={`const response = await fetch('https://api.useautumn.com/v1/attach', {
|
||||
method: "POST",
|
||||
headers: {Authorization: 'Bearer <Autumn API Key>', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
"customer_id": internal_user_id, //Use your internal user ID
|
||||
"product_id": "pro"
|
||||
})
|
||||
})`}
|
||||
language="javascript"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Step>
|
||||
<Step title="Check if user has access to a feature and send usage events">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<p className="text-t2 flex flex-col gap-2 w-full lg:w-1/3">
|
||||
<span>
|
||||
Check whether a user has access to any of the features we
|
||||
defined above.
|
||||
</span>
|
||||
<span>
|
||||
If it's a metered (usage-based) feature, send us the usage
|
||||
data.
|
||||
</span>
|
||||
</p>
|
||||
<div className="w-full lg:w-2/3 min-w-md max-w-xl flex flex-col gap-2">
|
||||
<h2 className="text-t2 font-medium text-md">Check Access</h2>
|
||||
<CodeDisplay
|
||||
code={`const response = await fetch('https://api.useautumn.com/v1/entitled', {
|
||||
method: "POST",
|
||||
headers: {Authorization: 'Bearer <Autumn API Key>', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
"customer_id": internal_user_id, //Use your internal user ID
|
||||
"feature_id": "chat-messages"
|
||||
})
|
||||
})`}
|
||||
language="javascript"
|
||||
/>
|
||||
|
||||
<h2 className="text-t2 font-medium text-md mt-4">
|
||||
Send Usage (if metered)
|
||||
</h2>
|
||||
<CodeDisplay
|
||||
code={`await fetch('https://api.useautumn.com/v1/events', {
|
||||
method: "POST",
|
||||
headers: {Authorization: 'Bearer <Autumn API Key>', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
"customer_id": internal_user_id, //Use your internal user ID
|
||||
"event_name": "chat-message"
|
||||
})
|
||||
})`}
|
||||
language="javascript"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Step>
|
||||
<Step title="Done!">
|
||||
<div className="flex gap-8 w-full justify-between flex-col lg:flex-row">
|
||||
<p className="text-t2 gap-2 w-full lg:w-1/3">
|
||||
You're all set! Go to the Customers tab to manage your users,
|
||||
and read our{" "}
|
||||
<a
|
||||
className="text-primary underline font-semibold break-none"
|
||||
href="https://docs.useautumn.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Documentation
|
||||
<FontAwesomeIcon
|
||||
className="ml-1 h-2.5 w-2.5"
|
||||
icon={faExternalLinkAlt}
|
||||
/>
|
||||
</a>{" "}
|
||||
to learn more about what you can do with Autumn.
|
||||
</p>
|
||||
</div>
|
||||
</Step>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen w-screen bg-stone-50">
|
||||
<OrganizationList hidePersonal={true} />
|
||||
</div>
|
||||
);
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default OnboardingView;
|
||||
|
||||
@@ -114,7 +114,7 @@ function CreateProduct() {
|
||||
setFields({ ...fields, is_add_on: e as boolean })
|
||||
}
|
||||
/>
|
||||
<p className="mt-[1px]">This product is an add on</p>
|
||||
<p className="mt-[1px]">This product is an add-on</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-1 text-t2">
|
||||
<Checkbox
|
||||
@@ -123,7 +123,9 @@ function CreateProduct() {
|
||||
setFields({ ...fields, is_default: e as boolean })
|
||||
}
|
||||
/>
|
||||
<p className="mt-[1px]">This product is the default product</p>
|
||||
<p className="mt-[1px]">
|
||||
Add this product to customers by default on creation
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useProductsContext } from "./ProductsContext";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export const ProductsTable = ({ products }: { products: Product[] }) => {
|
||||
const { env } = useProductsContext();
|
||||
const { env, onboarding } = useProductsContext();
|
||||
const router = useRouter();
|
||||
return (
|
||||
<Table>
|
||||
@@ -29,9 +29,11 @@ export const ProductsTable = ({ products }: { products: Product[] }) => {
|
||||
<TableHead className="">Name</TableHead>
|
||||
<TableHead>Product ID</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Group</TableHead>
|
||||
<TableHead>Created At</TableHead>
|
||||
<TableHead></TableHead>
|
||||
{!onboarding && <TableHead>Group</TableHead>}
|
||||
{!onboarding && (
|
||||
<TableHead className="min-w-0 w-28">Created At</TableHead>
|
||||
)}
|
||||
<TableHead className="min-w-0 w-10"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -41,12 +43,8 @@ export const ProductsTable = ({ products }: { products: Product[] }) => {
|
||||
className="cursor-pointer"
|
||||
onClick={() => navigateTo(`/products/${product.id}`, router, env)}
|
||||
>
|
||||
<TableCell className="font-medium">
|
||||
{product.name}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono">
|
||||
{product.id}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{product.name}</TableCell>
|
||||
<TableCell className="font-mono">{product.id}</TableCell>
|
||||
<TableCell className="min-w-32">
|
||||
{product.is_default ? (
|
||||
<Badge variant="outline">Default</Badge>
|
||||
@@ -56,17 +54,16 @@ export const ProductsTable = ({ products }: { products: Product[] }) => {
|
||||
<></>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{product.group}</TableCell>
|
||||
<TableCell className="min-w-20 w-24">
|
||||
<span>
|
||||
{formatUnixToDateTime(product.created_at).date}
|
||||
</span>
|
||||
{" "}
|
||||
<span className="text-t3">
|
||||
{formatUnixToDateTime(product.created_at).time}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="min-w-4 w-6">
|
||||
{!onboarding && <TableCell>{product.group}</TableCell>}
|
||||
{!onboarding && (
|
||||
<TableCell>
|
||||
<span>{formatUnixToDateTime(product.created_at).date}</span>{" "}
|
||||
<span className="text-t3">
|
||||
{formatUnixToDateTime(product.created_at).time}
|
||||
</span>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<ProductRowToolbar product={product} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { EnvDropdown } from "./EnvDropdown";
|
||||
import { AppEnv } from "@autumn/shared";
|
||||
|
||||
import SidebarBottom from "./SidebarBottom";
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
function HomeSidebar({
|
||||
user,
|
||||
@@ -40,8 +41,8 @@ function HomeSidebar({
|
||||
}) {
|
||||
return (
|
||||
<Sidebar collapsible="icon" className=" bg-zinc-100">
|
||||
<SidebarTop orgName={org.name} env={env} />
|
||||
<SidebarContent >
|
||||
<SidebarTop orgName={org.name || " "} env={env} />
|
||||
<SidebarContent>
|
||||
<SidebarGroup className="py-0">
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
|
||||
@@ -25,12 +25,10 @@ export function SidebarTop({ orgName, env }: { orgName: string; env: AppEnv }) {
|
||||
const [hidePlaceholder, setHidePlaceholder] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (organization) {
|
||||
setCurOrgId(organization.id);
|
||||
}
|
||||
if (organization) setCurOrgId(organization.id);
|
||||
|
||||
if (curOrgId !== null && organization?.id !== curOrgId) {
|
||||
window.location.href = "/sandbox/customers";
|
||||
if (organization && curOrgId !== null && organization?.id !== curOrgId) {
|
||||
window.location.href = "/sandbox/products";
|
||||
router.refresh();
|
||||
}
|
||||
}, [organization, curOrgId, router]);
|
||||
@@ -65,11 +63,12 @@ export function SidebarTop({ orgName, env }: { orgName: string; env: AppEnv }) {
|
||||
{!isLoaded && <SmallSpinner />}
|
||||
</div>
|
||||
)}
|
||||
{isLoaded && (
|
||||
{isLoaded && organization && (
|
||||
<OrganizationSwitcher
|
||||
appearance={{
|
||||
elements: {
|
||||
organizationSwitcherTrigger: "pl-0 pr-1 max-w-[160px]",
|
||||
organizationSwitcherTrigger:
|
||||
"flex pl-0 pr-1 max-w-[160px]",
|
||||
},
|
||||
}}
|
||||
hidePersonal={true}
|
||||
@@ -92,7 +91,7 @@ export function SidebarTop({ orgName, env }: { orgName: string; env: AppEnv }) {
|
||||
{/* <span className="w-[70px] overflow-hidden text-ellipsis whitespace-nowrap">
|
||||
{organization?.id}
|
||||
</span> */}
|
||||
<CopyButton text={organization?.id || ""} />
|
||||
<CopyButton text={organization?.id || ""} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const TabButton = ({
|
||||
const path = usePathname();
|
||||
const isActive = path.includes(value);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<SidebarMenuItem key={value}>
|
||||
<SidebarMenuButton asChild>
|
||||
|
||||
@@ -107,6 +107,13 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
plugins: [require("tailwindcss-animate"), nextui(nextUiConfig)],
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
nextui(nextUiConfig),
|
||||
function ({ addBase, theme }) {
|
||||
addBase({
|
||||
body: { fontSize: theme("fontSize.sm") },
|
||||
});
|
||||
},
|
||||
],
|
||||
} satisfies Config;
|
||||
|
||||
450
package-lock.json
generated
450
package-lock.json
generated
@@ -43,7 +43,7 @@
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/supabase-js": "^2.47.2",
|
||||
"@useautumn/react": "latest",
|
||||
"@wooorm/starry-night": "^3.5.0",
|
||||
"@wooorm/starry-night": "^3.6.0",
|
||||
"axios": "^1.7.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -55,14 +55,17 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.0.3",
|
||||
"next-themes": "^0.4.4",
|
||||
"ora": "^8.1.1",
|
||||
"pointer-sdk": "^0.0.9",
|
||||
"react": "^18.2.0",
|
||||
"react-confetti-explosion": "^2.1.2",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-hotkeys-hook": "^4.6.1",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"sonner": "^1.7.4",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -5123,20 +5126,6 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/@wooorm/starry-night": {
|
||||
"version": "3.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"import-meta-resolve": "^4.0.0",
|
||||
"vscode-oniguruma": "^2.0.0",
|
||||
"vscode-textmate": "^9.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"dev": true,
|
||||
@@ -6460,14 +6449,6 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/import-meta-resolve": {
|
||||
"version": "4.1.0",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"dev": true,
|
||||
@@ -7046,13 +7027,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"frontend/node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/object-hash": {
|
||||
"version": "3.0.0",
|
||||
"license": "MIT",
|
||||
@@ -7394,15 +7368,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"funding": [
|
||||
@@ -7457,10 +7422,6 @@
|
||||
"react-dom": ">=16.8.1"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"frontend/node_modules/react-remove-scroll": {
|
||||
"version": "2.6.0",
|
||||
"license": "MIT",
|
||||
@@ -8197,14 +8158,6 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0"
|
||||
}
|
||||
},
|
||||
"frontend/node_modules/vscode-oniguruma": {
|
||||
"version": "2.0.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"frontend/node_modules/vscode-textmate": {
|
||||
"version": "9.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"frontend/node_modules/which-boxed-primitive": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
@@ -11354,6 +11307,22 @@
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wooorm/starry-night": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@wooorm/starry-night/-/starry-night-3.6.0.tgz",
|
||||
"integrity": "sha512-AoDrqWZCZVymY48BClDaSuLgOe5vnl99S4hrqqg5OJ44x9NHo4s5LzOypKC3e6jGIUqHDfaDMvou8pelkq3dCw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"import-meta-resolve": "^4.0.0",
|
||||
"vscode-oniguruma": "^2.0.0",
|
||||
"vscode-textmate": "^9.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
|
||||
@@ -12637,6 +12606,27 @@
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-jss": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/css-jss/-/css-jss-10.10.0.tgz",
|
||||
"integrity": "sha512-YyMIS/LsSKEGXEaVJdjonWe18p4vXLo8CMA4FrW/kcaEyqdIGKCFXao31gbJddXEdIxSXFFURWrenBJPlKTgAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "^10.10.0",
|
||||
"jss-preset-default": "^10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-vendor": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz",
|
||||
"integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.3",
|
||||
"is-in-browser": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
|
||||
@@ -13902,6 +13892,15 @@
|
||||
"integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-to-text": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz",
|
||||
@@ -13996,6 +13995,12 @@
|
||||
"integrity": "sha512-inh5wue5XdfObhu/IGEMiA1nUXigSGcaKNemcbLRKa7jXYGDZXr3LoT9pTIzq2hPEbld7w/qv9h+ikWGz8fL1g==",
|
||||
"license": "Unlicense"
|
||||
},
|
||||
"node_modules/hyphenate-style-name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
@@ -14035,6 +14040,16 @@
|
||||
"module-details-from-path": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/import-meta-resolve": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
||||
"integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
@@ -14299,6 +14314,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/is-in-browser": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
|
||||
"integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-ip": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-ip/-/is-ip-5.0.1.tgz",
|
||||
@@ -14522,6 +14543,172 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jss": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz",
|
||||
"integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"csstype": "^3.0.2",
|
||||
"is-in-browser": "^1.1.3",
|
||||
"tiny-warning": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/jss"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-camel-case": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz",
|
||||
"integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"hyphenate-style-name": "^1.0.3",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-compose": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-compose/-/jss-plugin-compose-10.10.0.tgz",
|
||||
"integrity": "sha512-F5kgtWpI2XfZ3Z8eP78tZEYFdgTIbpA/TMuX3a8vwrNolYtN1N4qJR/Ob0LAsqIwCMLojtxN7c7Oo/+Vz6THow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-default-unit": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz",
|
||||
"integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-expand": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-expand/-/jss-plugin-expand-10.10.0.tgz",
|
||||
"integrity": "sha512-ymT62W2OyDxBxr7A6JR87vVX9vTq2ep5jZLIdUSusfBIEENLdkkc0lL/Xaq8W9s3opUq7R0sZQpzRWELrfVYzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-extend": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-extend/-/jss-plugin-extend-10.10.0.tgz",
|
||||
"integrity": "sha512-sKYrcMfr4xxigmIwqTjxNcHwXJIfvhvjTNxF+Tbc1NmNdyspGW47Ey6sGH8BcQ4FFQhLXctpWCQSpDwdNmXSwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-global": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz",
|
||||
"integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-nested": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz",
|
||||
"integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-props-sort": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz",
|
||||
"integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-rule-value-function": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz",
|
||||
"integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-rule-value-observable": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-observable/-/jss-plugin-rule-value-observable-10.10.0.tgz",
|
||||
"integrity": "sha512-ZLMaYrR3QE+vD7nl3oNXuj79VZl9Kp8/u6A1IbTPDcuOu8b56cFdWRZNZ0vNr8jHewooEeq2doy8Oxtymr2ZPA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"symbol-observable": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-template": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-template/-/jss-plugin-template-10.10.0.tgz",
|
||||
"integrity": "sha512-ocXZBIOJOA+jISPdsgkTs8wwpK6UbsvtZK5JI7VUggTD6LWKbtoxUzadd2TpfF+lEtlhUmMsCkTRNkITdPKa6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-plugin-vendor-prefixer": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz",
|
||||
"integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"css-vendor": "^2.0.8",
|
||||
"jss": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jss-preset-default": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-10.10.0.tgz",
|
||||
"integrity": "sha512-GL175Wt2FGhjE+f+Y3aWh+JioL06/QWFgZp53CbNNq6ZkVU0TDplD8Bxm9KnkotAYn3FlplNqoW5CjyLXcoJ7Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"jss": "10.10.0",
|
||||
"jss-plugin-camel-case": "10.10.0",
|
||||
"jss-plugin-compose": "10.10.0",
|
||||
"jss-plugin-default-unit": "10.10.0",
|
||||
"jss-plugin-expand": "10.10.0",
|
||||
"jss-plugin-extend": "10.10.0",
|
||||
"jss-plugin-global": "10.10.0",
|
||||
"jss-plugin-nested": "10.10.0",
|
||||
"jss-plugin-props-sort": "10.10.0",
|
||||
"jss-plugin-rule-value-function": "10.10.0",
|
||||
"jss-plugin-rule-value-observable": "10.10.0",
|
||||
"jss-plugin-template": "10.10.0",
|
||||
"jss-plugin-vendor-prefixer": "10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ksuid": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ksuid/-/ksuid-3.0.0.tgz",
|
||||
@@ -16148,6 +16335,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-themes": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.4.tgz",
|
||||
"integrity": "sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
@@ -16238,6 +16435,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
@@ -16860,6 +17066,17 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/property-information": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
|
||||
@@ -17045,6 +17262,26 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-confetti-explosion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-confetti-explosion/-/react-confetti-explosion-2.1.2.tgz",
|
||||
"integrity": "sha512-4UzDFBajAGXmF9TSJoRMO2QOBCIXc66idTxH8l7Mkul48HLGtk+tMzK9HYDYsy7Zmw5sEGchi2fbn4AJUuLrZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"react-jss": "^10.9.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.x",
|
||||
"react-dom": "^18.x"
|
||||
}
|
||||
},
|
||||
"node_modules/react-display-name": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.5.tgz",
|
||||
"integrity": "sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
@@ -17058,6 +17295,49 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-jss": {
|
||||
"version": "10.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-jss/-/react-jss-10.10.0.tgz",
|
||||
"integrity": "sha512-WLiq84UYWqNBF6579/uprcIUnM1TSywYq6AIjKTTTG5ziJl9Uy+pwuvpN3apuyVwflMbD60PraeTKT7uWH9XEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"@emotion/is-prop-valid": "^0.7.3",
|
||||
"css-jss": "10.10.0",
|
||||
"hoist-non-react-statics": "^3.2.0",
|
||||
"is-in-browser": "^1.1.3",
|
||||
"jss": "10.10.0",
|
||||
"jss-preset-default": "10.10.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"shallow-equal": "^1.2.0",
|
||||
"theming": "^3.3.0",
|
||||
"tiny-warning": "^1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-jss/node_modules/@emotion/is-prop-valid": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz",
|
||||
"integrity": "sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "0.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-jss/node_modules/@emotion/memoize": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz",
|
||||
"integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-markdown": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.3.tgz",
|
||||
@@ -17572,6 +17852,12 @@
|
||||
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shallow-equal": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz",
|
||||
"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.33.5",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
|
||||
@@ -17874,6 +18160,16 @@
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sonner": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz",
|
||||
"integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -18218,6 +18514,15 @@
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz",
|
||||
@@ -18296,6 +18601,24 @@
|
||||
"b4a": "^1.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/theming": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/theming/-/theming-3.3.0.tgz",
|
||||
"integrity": "sha512-u6l4qTJRDaWZsqa8JugaNt7Xd8PPl9+gonZaIe28vAhqgHMIG/DOyFPqiKN/gQLQYj05tHv+YQdNILL4zoiAVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"prop-types": "^15.5.8",
|
||||
"react-display-name": "^0.2.4",
|
||||
"tiny-warning": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
@@ -18320,6 +18643,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-warning": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -18712,6 +19041,18 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-oniguruma": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz",
|
||||
"integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-textmate": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz",
|
||||
"integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
@@ -20513,13 +20854,6 @@
|
||||
"version": "2.1.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"server/node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"server/node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -15,6 +15,13 @@ import { AppEnv } from "@autumn/shared";
|
||||
export const orgRouter = express.Router();
|
||||
|
||||
orgRouter.get("", async (req: any, res) => {
|
||||
if (!req.orgId) {
|
||||
res.status(400).json({
|
||||
message: "Missing orgId",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const org = await OrgService.getFullOrg({
|
||||
sb: req.sb,
|
||||
orgId: req.orgId,
|
||||
|
||||
Reference in New Issue
Block a user