Installation

Terminal
npm install @runonatlas/next

Initialization

1

Create the Atlas Provider

To initialize the application, you need to create a client provider component and wrap your application with it. Since Atlas needs to authenticate with your backend and verify that users are only accessing what they can, you will need to place it under your authentication provider.

It’s very important that you provide all the required props:

  • getAuth: A function that returns the authentication token. This is only necessary if the authentication with the backend is done with tokens and headers (for example, Clerk).
  • loginCallback: A function that is called whenever the user tries to do an action that requires authentication.
  • userId: The ID of the user.
  • userEmail: The email of the user. Optional.
  • userName: The name of the user. Optional.
  • isUserLoading: A boolean that indicates whether the user is still loading. Optional. Helps Atlas to avoid flickering when the user status changes.

You then need to wrap your application with the AtlasProvider, wherever you have the rest of your providers and under your authentication provider.

For example, with Clerk:

"use client";
import { useAuth, useUser } from "@clerk/nextjs";
import { AtlasProvider } from "@runonatlas/next/client";
import { redirect } from "next/navigation";

const loginCallback = () => {
  redirect("/login");
};

export function AtlasClientProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const { userId, getToken, isLoaded } = useAuth();
  const { user } = useUser();

  return (
    <AtlasProvider
      getAuth={getToken}
      loginCallback={loginCallback}
      userId={userId}
      userEmail={user?.primaryEmailAddress?.emailAddress}
      userName={user?.fullName}
      isUserLoading={!isLoaded}
    >
      {children}
    </AtlasProvider>
  );
}
2

Create the Atlas API routes

You will need to initialize the client in the backend. To do so, you will need to:

  1. Set the Atlas API key in the environment variables.
  2. Create the Atlas Next Server Client, providing a callback that returns the user that performed the request. This callback will depend on the authentication provider you are using.
  3. Use the Atlas Next Server Client to create the API routes that will be used to access the Atlas API. They should be in the src/app/api/atlas-api/[slug]/route.ts file.

The result should be like this:

ATLAS_API_KEY="INSERT_YOUR_ATLAS_API_KEY"

Use our Atlas widgets

Remember that you need to have followed the installation guide before you can use any of these widgets.

You can use any of our Atlas widgets in your React application by importing them from @runonatlas/next/client.

PricingComponent Widget

The PricingComponent widget shows the plans in your Atlas pricing model and allows users to purhcase a subscription to a plan via Atlas’ in-line checkout.

IMPORTANT: The successUrl must be absolute. For example, /success is not valid, but https://your-app.com/success is valid.

"use client";
import { PricingComponent } from "@runonatlas/next/client";

/**
 * IMPORTANT:
 * Replace the `successUrl` prop with values specific to your application.
 */
export default function PricingPage() {
  return (
    <PricingComponent
      // Where to redirect after successful checkout.
      successUrl={"https://your-app.com/customer-portal"}
    />
  );
}

CustomerPortalComponent Widget

The CustomerPortalComponent widget shows the current user what plan they are subscribed to, a history of their payments, and allows them to cancel or change their plan at any time.

IMPORTANT: The successUrl must be absolute. For example, /success is not valid, but https://your-app.com/success is valid.

"use client";
import { CustomerPortalComponent } from "@runonatlas/next/client";

/**
 * IMPORTANT:
 * Replace the `successUrl` prop with values specific to your application.
 */
export default function CustomerPortalPage() {
  return (
    <CustomerPortalComponent
      // Where to redirect after successful checkout.
      successUrl={"https://your-app.com/customer-portal"}
    />
  );
}

Restrict user access based on their subscriptions

You can prevent users from accessing restricted parts of your application based on their subscription. To do so, it is important that you do it both in the UI and in the backend:

UI Protection

Using hooks

You can use the useFeaturesAllowed hook to check if a user has access to a feature.

import { useFeaturesAllowed } from "@runonatlas/next/client";

export default function Home() {
  const { isAllowed } = useFeaturesAllowed(["feature-1", "feature-2"]);

  return (
    <div>
      Our system has determined that...
      {isAllowed
        ? "You have access to these features!"
        : "You don't have access to these features. Please upgrade your subscription!"}
    </div>
  );
}

Using components

You can use the FeatureProtect component to check if a user has access to a feature.

import { FeatureProtect } from "@runonatlas/next/client";

export default function Home() {
  return (
    <FeatureProtect
      disallowedFallback={
        <div>Oh, you don't have access to these features. Upgrade now!</div>
      }
      features={["feature-1", "feature-2"]}
    >
      <div>Cool features</div>
    </FeatureProtect>
  );
}

Backend Protection

Using Atlas in the backend is very easy. You just need to use the atlasServerClient that we created before to check if a user has access to a feature. For example, if you are using Clerk:

src/app/api/example/route.ts
import { atlasServerClient } from "@/atlas/server";
import { auth } from "@clerk/nextjs/server";

export async function GET() {
  const { userId } = await auth();
  if (!userId) {
    return new Response("Unauthorized", { status: 401 });
  }

  const { ok } = await atlasServerClient.areFeaturesAllowed(userId, [
    "data-explorer",
  ]);

  if (!ok) {
    return new Response("You don't have this feature enabled", { status: 401 });
  }

  return new Response("Hello from the backend!!");
}

Limit-based features

Sometimes, just having a feature as enabled or disabled is not enough, and our pricing models require limits to be set. For example, 5 users per account, or 20GB of storage. Setting this up with Atlas is very easy. And, if at some point the limits change, you won’t need to change the code again!

Configuring the backend

The backend needs to understand what the limits are and how to check if the limit has been reached. To do so, you need to use the atlasServerClient and provide callbacks per each limit that you might want to use.

For example, given a feature whose id is data-explorer, you can provide a callback to check if the limit has been reached:

src/atlas/server.ts
import { AtlasNextServerClient } from "@runonatlas/next/server";
import { auth } from "@clerk/nextjs/server";
import { prisma } from "./db";

export const atlasServerClient = new AtlasNextServerClient(
  async () => {
    const { userId } = await auth();
    return userId;
  },
  {
    limits: {
      "data-explorer": (userId: string) =>
        prisma.explorations.count({
          where: {
            userId,
          },
        }),
    },
  }
);

Now, every time you the application needs to check if the data-explorer feature is available, Atlas will use the callback to compute it.

Explanation in the UI

When using both the <FeatureProtect> component and the useFeaturesAllowed() hook, it will automatically check if the user has access to the features you are protecting AND if the limit has not been reached.

However, it is possible that you want to show your user why the access was denied! To to do this, we give you the reasons why the access was denied. For example, with the hook useFeaturesAllowed:

src/app/home/page.tsx
import { useFeaturesAllowed } from "@runonatlas/next/client";

export default function Home() {
  const { isAllowed, reasons } = useFeaturesAllowed(["feature-1", "feature-2"]);

  if (!isAllowed) {
    return (
      <div>
        Our system has determined that...
        {reasons.map((reason) => {
          if (reason.reason === "notIncluded") {
            return (
              <div key={reason.slug}>
                You need to upgrade your plan to access {reason.slug}
              </div>
            );
          }

          if (reason.reason === "limitReached") {
            return (
              <div key={reason.slug}>
                You have reached the limit for {reason.slug}. You currently have{" "}
                {reason.currentUsage}/{reason.limit}.
              </div>
            );
          }
        })}
      </div>
    );
  }

  return <div>Cool feature</div>;
}

In the <FeatureProtect> component, instead of directly providing the FallbackComponent, you can actually provide a function that returns the component to be rendered when the access is denied. For example:

src/app/home/page.tsx
import { FeatureProtect } from "@runonatlas/next/client";

export default function Home() {
  return (
    <FeatureProtect
      disallowedFallback={(reasons) => {
        return (
          <div>
            Our system has determined that...
            {reasons.map((reason) => {
              if (reason.reason === "notIncluded") {
                return (
                  <div key={reason.slug}>
                    You need to upgrade your plan to access {reason.slug}
                  </div>
                );
              }

              if (reason.reason === "limitReached") {
                return (
                  <div key={reason.slug}>
                    You have reached the limit for {reason.slug}. You currently
                    have {reason.currentUsage}/{reason.limit}.
                  </div>
                );
              }
            })}
          </div>
        );
      }}
      features={["feature-1", "feature-2"]}
    >
      <div>Cool features</div>
    </FeatureProtect>
  );
}